Skip to content

Commit

Permalink
Stratio custom features
Browse files Browse the repository at this point in the history
  • Loading branch information
unai-ttxu committed Nov 21, 2024
1 parent 6fb0201 commit 4ccdb80
Show file tree
Hide file tree
Showing 26 changed files with 847 additions and 38 deletions.
2 changes: 1 addition & 1 deletion contrib/oauth2-proxy_autocomplete.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ _oauth2_proxy() {
COMPREPLY=( $(compgen -W 'X-Real-IP X-Forwarded-For X-ProxyUser-IP' -- ${cur}) )
return 0
;;
--@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|trusted-ip|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|github-repo|github-token|gitlab-group|github-user|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|ready-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|redist-cluster-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url|force-json-errors))
--@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|trusted-ip|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|github-repo|github-token|gitlab-group|github-user|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|ready-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|redist-cluster-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url|force-json-errors|sis-root-url|sign-out-url|jwt-session-key|jwt-session-key-file|clear-extra-cookie-names))
return 0
;;
esac
Expand Down
54 changes: 40 additions & 14 deletions docs/docs/configuration/overview.md

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions docs/docs/configuration/sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ data in one of the available session storage backends.
At present the available backends are (as passed to `--session-store-type`):
- [cookie](#cookie-storage) (default)
- [redis](#redis-storage)
- [jwt](#jwt-storage)

### Cookie Storage

Expand Down Expand Up @@ -97,3 +98,17 @@ Note that flags `--redis-use-sentinel=true` and `--redis-use-cluster=true` are m
Note, if Redis timeout option is set to non-zero, the `--redis-connection-idle-timeout`
must be less than [Redis timeout option](https://redis.io/docs/reference/clients/#client-timeouts). For example: if either redis.conf includes
`timeout 15` or using `CONFIG SET timeout 15` the `--redis-connection-idle-timeout` must be at least `--redis-connection-idle-timeout=14`

### JWT Storage

The JWT Storage backend stores sessions as signed JWTs.

All session information is stored in token claims and send back to the browser as a cookie,
so it is transferred with each request.

Only basic information is persisted, no OIDC tokens or access tokens are created as claims in order to limit the size of the token itself.

#### Usage

When using the jwt store, specify `--session-store-type=jwt` as well as the signing key, via
`--jwt-session-key=\"${OAUTH2_PROXY_JWT_SESSION_KEY}\"` or `--jwt-session-key-file=/etc/ssl/private/jwt_session_signing_key.pem`.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ require (
github.com/mitchellh/mapstructure v1.5.0
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
github.com/oauth2-proxy/tools/reference-gen v0.0.0-20220223111546-d3b50d1a591a
github.com/ohler55/ojg v1.24.1
github.com/onsi/ginkgo/v2 v2.20.2
github.com/onsi/gomega v1.34.2
github.com/pierrec/lz4/v4 v4.1.21
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3Tc
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw=
github.com/oauth2-proxy/tools/reference-gen v0.0.0-20220223111546-d3b50d1a591a h1:2RkJiJXdto2/qHaM7mTUKSR8yxImz0zei8LW0bcbav0=
github.com/oauth2-proxy/tools/reference-gen v0.0.0-20220223111546-d3b50d1a591a/go.mod h1:J9TATNVXZX2MAsXx9J35weO47Fp3FQtx+f48AHLFAug=
github.com/ohler55/ojg v1.24.1 h1:PaVLelrNgT5/0ppPaUtey54tOVp245z33fkhL2jljjY=
github.com/ohler55/ojg v1.24.1/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
Expand Down
28 changes: 26 additions & 2 deletions oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,22 @@ func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessionsapi.Session
return p.sessionStore.Load(req)
}

// ClearExtraCookies clears extra cookies if found in request
func (p *OAuthProxy) ClearExtraCookies(rw http.ResponseWriter, req *http.Request) {
if provider, ok := p.provider.(*providers.SISProvider); ok {
fmt.Printf(provider.ClientID)
for _, name := range provider.ClearExtraCookieNames {
c, err := req.Cookie(name)
if err != nil {
logger.Printf("Cookie %s could not be retrieved from request: %v", name, err)
continue
}
logger.Printf("Extra cookie %s found in request: %#v", name, c)
http.SetCookie(rw, cookies.MakeCookieFromOptions(req, c.Name, "", p.CookieOptions, time.Hour*-1, time.Now()))
}
}
}

// SaveSession creates a new session cookie value and sets this on the response
func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *sessionsapi.SessionState) error {
return p.sessionStore.Save(rw, req, s)
Expand Down Expand Up @@ -633,6 +649,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
return
}
p.ClearExtraCookies(rw, req)
rw.WriteHeader(code)

redirectURL, err := p.appDirector.GetRedirect(req)
Expand Down Expand Up @@ -720,11 +737,17 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
Email string `json:"email"`
Groups []string `json:"groups,omitempty"`
PreferredUsername string `json:"preferredUsername,omitempty"`
Tenant string `json:"tenant,omitempty"`
Username string `json:"username,omitempty"`
Tenants []string `json:"tenants,omitempty"`
}{
User: session.User,
Email: session.Email,
Groups: session.Groups,
PreferredUsername: session.PreferredUsername,
Tenant: session.Tenant,
Username: session.Username,
Tenants: session.Tenants,
}

if err := json.NewEncoder(rw).Encode(userInfo); err != nil {
Expand All @@ -741,15 +764,15 @@ func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
return
}
redirect = p.provider.GetSignOutURL(redirect)
err = p.ClearSessionCookie(rw, req)
if err != nil {
logger.Errorf("Error clearing session cookie: %v", err)
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
return
}

p.ClearExtraCookies(rw, req)
p.backendLogout(rw, req)

http.Redirect(rw, req, redirect, http.StatusFound)
}

Expand Down Expand Up @@ -1129,6 +1152,7 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
if err != nil {
logger.Errorf("Error clearing session cookie: %v", err)
}
p.ClearExtraCookies(rw, req)
return nil, ErrAccessDenied
}

Expand Down
11 changes: 11 additions & 0 deletions pkg/apis/options/legacy_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,9 @@ type LegacyProvider struct {
GoogleUseApplicationDefaultCredentials bool `flag:"google-use-application-default-credentials" cfg:"google_use_application_default_credentials"`
GoogleTargetPrincipal string `flag:"google-target-principal" cfg:"google_target_principal"`

SISRootURL string `flag:"sis-root-url" cfg:"sis_root_url"`
ClearExtraCookieNames []string `flag:"clear-extra-cookie-names" cfg:"clear_extra_cookie_names"`

// These options allow for other providers besides Google, with
// potential overrides.
ProviderType string `flag:"provider" cfg:"provider"`
Expand Down Expand Up @@ -563,6 +566,9 @@ func legacyProviderFlagSet() *pflag.FlagSet {
flagSet.String("client-secret", "", "the OAuth Client Secret")
flagSet.String("client-secret-file", "", "the file with OAuth Client Secret")

flagSet.String("sis-root-url", "", "Stratio SIS root url")
flagSet.StringSlice("clear-extra-cookie-names", []string{}, "Clear extra cookies after logout")

flagSet.String("provider", "google", "OAuth provider")
flagSet.String("provider-display-name", "", "Provider display name")
flagSet.StringSlice("provider-ca-file", []string{}, "One or more paths to CA certificates that should be used when connecting to the provider. If not specified, the default Go trust sources are used instead.")
Expand Down Expand Up @@ -707,6 +713,11 @@ func (l *LegacyProvider) convert() (Providers, error) {
GraphGroupField: l.AzureGraphGroupField,
}

provider.SISConfig = SISOptions{
SISRootURL: l.SISRootURL,
ClearExtraCookieNames: l.ClearExtraCookieNames,
}

switch provider.Type {
case "github":
provider.GitHubConfig = GitHubOptions{
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ func NewFlagSet() *pflag.FlagSet {
flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")
flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints")

flagSet.String("sis-root-url", "", "Stratio SIS root URL")
flagSet.String("sign-out-url", "", "Sign out endpoint")
flagSet.StringSlice("clear-extra-cookie-names", []string{}, "Clear extra cookies after logout")
flagSet.String("jwt-session-key", "", "private key in PEM format used to sign session JWT, so that you can say something like --jwt-session-key=\"${OAUTH2_PROXY_JWT_SESSION_KEY}\"")
flagSet.String("jwt-session-key-file", "", "path to the private key file in PEM format used to sign the session JWT so that you can say something like --jwt-session-key-file=/etc/ssl/private/jwt_session_signing_key.pem")

flagSet.AddFlagSet(cookieFlagSet())
flagSet.AddFlagSet(loggingFlagSet())
flagSet.AddFlagSet(templatesFlagSet())
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/options/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ type Provider struct {
OIDCConfig OIDCOptions `json:"oidcConfig,omitempty"`
// LoginGovConfig holds all configurations for LoginGov provider.
LoginGovConfig LoginGovOptions `json:"loginGovConfig,omitempty"`
// SISConfig holds all configurations for SIS provider.
SISConfig SISOptions `json:"sisConfig,omitempty"`

// ID should be a unique identifier for the provider.
// This value is required for all providers.
Expand Down Expand Up @@ -136,6 +138,9 @@ const (

// OIDCProvider is the provider type for OIDC
OIDCProvider ProviderType = "oidc"

// SISProvider is the provider type for SIS
SISProvider ProviderType = "sis"
)

type KeycloakOptions struct {
Expand Down Expand Up @@ -251,6 +256,13 @@ type LoginGovOptions struct {
PubJWKURL string `json:"pubjwkURL,omitempty"`
}

type SISOptions struct {
// SISRootURL is the OpenID Connect SISRoot URL
SISRootURL string `flag:"sis-root-url" cfg:"sign_out_url"`
// ClearExtraCookieNames sets cookie names to clear after sign out
ClearExtraCookieNames []string `flag:"clear-extra-cookie-names" cfg:"clear_extra_cookie_names"`
}

func providerDefaults() Providers {
providers := Providers{
{
Expand Down
11 changes: 11 additions & 0 deletions pkg/apis/options/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type SessionOptions struct {
Type string `flag:"session-store-type" cfg:"session_store_type"`
Cookie CookieStoreOptions `cfg:",squash"`
Redis RedisStoreOptions `cfg:",squash"`
JWT JWTStoreOptions `cfg:",squash"`
}

// CookieSessionStoreType is used to indicate the CookieSessionStore should be
Expand All @@ -15,6 +16,10 @@ var CookieSessionStoreType = "cookie"
// used for storing sessions.
var RedisSessionStoreType = "redis"

// JWTSessionStoreType is used to indicate the JWTSessionStore should be
// used for storing sessions.
var JWTSessionStoreType = "jwt"

// CookieStoreOptions contains configuration options for the CookieSessionStore.
type CookieStoreOptions struct {
Minimal bool `flag:"session-cookie-minimal" cfg:"session_cookie_minimal"`
Expand All @@ -36,6 +41,12 @@ type RedisStoreOptions struct {
IdleTimeout int `flag:"redis-connection-idle-timeout" cfg:"redis_connection_idle_timeout"`
}

// JWTStoreOptions contains configuration options for the JWTSessionStore.
type JWTStoreOptions struct {
JWTKey string `flag:"jwt-session-key" cfg:"jwt_session_key"`
JWTKeyFile string `flag:"jwt-session-key-file" cfg:"jwt_session_key_file"`
}

func sessionOptionsDefaults() SessionOptions {
return SessionOptions{
Type: CookieSessionStoreType,
Expand Down
18 changes: 16 additions & 2 deletions pkg/apis/sessions/session_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ type SessionState struct {
User string `msgpack:"u,omitempty"`
Groups []string `msgpack:"g,omitempty"`
PreferredUsername string `msgpack:"pu,omitempty"`

Tenant string `msgpack:"t,omitempty"`
Username string `msgpack:"un,omitempty"`
Tenants []string `msgpack:"tt,omitempty"`
// Internal helpers, not serialized
Clock clock.Clock `msgpack:"-"`
Lock Lock `msgpack:"-"`
Expand Down Expand Up @@ -101,7 +103,10 @@ func (s *SessionState) Age() time.Duration {

// String constructs a summary of the session state
func (s *SessionState) String() string {
o := fmt.Sprintf("Session{email:%s user:%s PreferredUsername:%s", s.Email, s.User, s.PreferredUsername)
o := fmt.Sprintf("Session{email:%s user:%s PreferredUsername:%s Username:%s", s.Email, s.User, s.PreferredUsername, s.Username)
if s.Tenant != "" {
o += fmt.Sprintf(" tenant:%s", s.Tenant)
}
if s.AccessToken != "" {
o += " token:true"
}
Expand All @@ -120,6 +125,9 @@ func (s *SessionState) String() string {
if len(s.Groups) > 0 {
o += fmt.Sprintf(" groups:%v", s.Groups)
}
if len(s.Tenants) > 0 {
o += fmt.Sprintf(" tenants:%v", s.Tenants)
}
return o + "}"
}

Expand Down Expand Up @@ -148,6 +156,12 @@ func (s *SessionState) GetClaim(claim string) []string {
return groups
case "preferred_username":
return []string{s.PreferredUsername}
case "username":
return []string{s.Username}
case "tenants":
tenants := make([]string, len(s.Tenants))
copy(tenants, s.Tenants)
return tenants
default:
return []string{}
}
Expand Down
21 changes: 14 additions & 7 deletions pkg/apis/sessions/session_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,72 +59,79 @@ func TestString(t *testing.T) {
Email: "[email protected]",
User: "some.user",
PreferredUsername: "preferred.user",
Username: "some.user",
},
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user}",
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user Username:some.user}",
},
{
name: "Full Session",
sessionState: &SessionState{
Email: "[email protected]",
User: "some.user",
PreferredUsername: "preferred.user",
Username: "some.user",
CreatedAt: &created,
ExpiresOn: &expires,
AccessToken: "access.token",
IDToken: "id.token",
RefreshToken: "refresh.token",
},
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user token:true id_token:true created:2000-01-01 00:00:00 +0000 UTC expires:2000-01-01 01:00:00 +0000 UTC refresh_token:true}",
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user Username:some.user token:true id_token:true created:2000-01-01 00:00:00 +0000 UTC expires:2000-01-01 01:00:00 +0000 UTC refresh_token:true}",
},
{
name: "With a CreatedAt",
sessionState: &SessionState{
Email: "[email protected]",
User: "some.user",
PreferredUsername: "preferred.user",
Username: "some.user",
CreatedAt: &created,
},
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user created:2000-01-01 00:00:00 +0000 UTC}",
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user Username:some.user created:2000-01-01 00:00:00 +0000 UTC}",
},
{
name: "With an ExpiresOn",
sessionState: &SessionState{
Email: "[email protected]",
User: "some.user",
PreferredUsername: "preferred.user",
Username: "some.user",
ExpiresOn: &expires,
},
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user expires:2000-01-01 01:00:00 +0000 UTC}",
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user Username:some.user expires:2000-01-01 01:00:00 +0000 UTC}",
},
{
name: "With an AccessToken",
sessionState: &SessionState{
Email: "[email protected]",
User: "some.user",
PreferredUsername: "preferred.user",
Username: "some.user",
AccessToken: "access.token",
},
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user token:true}",
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user Username:some.user token:true}",
},
{
name: "With an IDToken",
sessionState: &SessionState{
Email: "[email protected]",
User: "some.user",
PreferredUsername: "preferred.user",
Username: "some.user",
IDToken: "id.token",
},
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user id_token:true}",
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user Username:some.user id_token:true}",
},
{
name: "With a RefreshToken",
sessionState: &SessionState{
Email: "[email protected]",
User: "some.user",
PreferredUsername: "preferred.user",
Username: "some.user",
RefreshToken: "refresh.token",
},
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user refresh_token:true}",
expected: "Session{email:[email protected] user:some.user PreferredUsername:preferred.user Username:some.user refresh_token:true}",
},
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/clock/clock_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build !skip
// +build !skip

package clock_test

import (
Expand Down
3 changes: 3 additions & 0 deletions pkg/http/server_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build !skip
// +build !skip

package http

import (
Expand Down
Loading

0 comments on commit 4ccdb80

Please sign in to comment.