Skip to content

Commit 75a2735

Browse files
authored
Add new SSO rule options (#247)
### Description Add two new SSO rule options: * `SkipLoginPage` redirects requests directly to the IDP's login page. * `Return403ForGetRequests` returns a simple 403 (Forbidden) response for GET requests. Normally, GET requests get a login page. Also, add `TokenLifetime` to OIDC, SAML, and Passkey providers. ### Type of change * [ ] New feature * [x] Feature improvement * [ ] Bug fix * [ ] Documentation * [ ] Cleanup / refactoring * [ ] Other (please explain) ### How is this change tested ? * [ ] Unit tests * [x] Manual tests (explain) * [ ] Tests are not needed
1 parent d5fa985 commit 75a2735

File tree

7 files changed

+47
-18
lines changed

7 files changed

+47
-18
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
## next
44

5+
### :star: Feature improvement
6+
7+
* Add two new SSO rule options:
8+
* `SkipLoginPage` redirects requests directly to the IDP's login page.
9+
* `Return403ForGetRequests` returns a simple 403 (Forbidden) response for GET requests. Normally, GET requests get a login page.
10+
* Add `TokenLifetime` to OIDC, SAML, and Passkey providers. This optional field set the lifetime of auth tokens set by tlsproxy.
11+
512
### :wrench: Misc
613

714
* Update go: 1.25.5

proxy/backend-sso.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ func (be *Backend) enforceSSOPolicy(w http.ResponseWriter, req *http.Request, ov
374374
// * the backend has ForceReAuth set, and the last authentication
375375
// either on a different host, or too long ago.
376376
if claims == nil || (rule.ForceReAuth != 0 && (claims["hhash"] != hex.EncodeToString(hh[:]) || time.Since(iat) > rule.ForceReAuth)) {
377-
if req.Method != http.MethodGet {
377+
if req.Method != http.MethodGet || rule.Return403ForGetRequests {
378378
be.logRequestF("REQ %s ➔ %s %s ➔ status:%d (SSO) (%q)", formatReqDesc(req), req.Method, req.RequestURI, http.StatusForbidden, userAgent(req))
379379
http.Error(w, "authentication required", http.StatusForbidden)
380380
return false
@@ -396,7 +396,7 @@ func (be *Backend) enforceSSOPolicy(w http.ResponseWriter, req *http.Request, ov
396396
http.Error(w, "internal error", http.StatusInternalServerError)
397397
return false
398398
}
399-
if _, ok := be.SSO.p.(*passkeys.Manager); ok || req.Header.Get("x-skip-login-confirmation") != "" {
399+
if _, ok := be.SSO.p.(*passkeys.Manager); ok || req.Header.Get("x-skip-login-confirmation") != "" || rule.SkipLoginPage {
400400
be.logRequestF("REQ %s ➔ %s %s ➔ status:%d (SSO) (%q)", formatReqDesc(req), req.Method, req.RequestURI, http.StatusFound, userAgent(req))
401401
http.Redirect(w, req, "/.sso/login?redirect="+token, http.StatusFound)
402402
return false

proxy/config.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,9 @@ type ConfigOIDC struct {
609609
// be valid. Only set this if all host names in the domain are served
610610
// by this proxy.
611611
Domain string `yaml:"domain,omitempty"`
612+
// TokenLifetime is the amount of time that tokens, issued by the proxy
613+
// for this provider, will be valid. The default is 20 hours.
614+
TokenLifetime time.Duration `yaml:"tokenLifetime,omitempty"`
612615
}
613616

614617
// ConfigSAML contains the parameters of a SAML identity provider.
@@ -623,6 +626,9 @@ type ConfigSAML struct {
623626
// be valid. Only set this if all host names in the domain are served
624627
// by this proxy.
625628
Domain string `yaml:"domain,omitempty"`
629+
// TokenLifetime is the amount of time that tokens, issued by the proxy
630+
// for this provider, will be valid. The default is 20 hours.
631+
TokenLifetime time.Duration `yaml:"tokenLifetime,omitempty"`
626632
}
627633

628634
// ConfigPasskey contains the parameters of a Passkey manager.
@@ -645,6 +651,9 @@ type ConfigPasskey struct {
645651
// be valid. Only set this if all host names in the domain are served
646652
// by this proxy.
647653
Domain string `yaml:"domain,omitempty"`
654+
// TokenLifetime is the amount of time that tokens, issued by the proxy
655+
// for this provider, will be valid. The default is 20 hours.
656+
TokenLifetime time.Duration `yaml:"tokenLifetime,omitempty"`
648657
}
649658

650659
// ConfigPKI defines the parameters of a local Certificate Authority.
@@ -713,6 +722,13 @@ type SSORule struct {
713722
// Scopes restricts access to user identities that have at least one of
714723
// these scopes. If empty, there are no scope restrictions.
715724
Scopes Strings `yaml:"scopes,flow,omitempty"`
725+
// SkipLoginPage indicates that when authentication is required, the
726+
// user is redirected to the provider's login page directly.
727+
SkipLoginPage bool `yaml:"skipLoginPage,omitempty"`
728+
// Return403ForGetRequests indicates that unauthorized GET requests
729+
// should return 403 (Forbidden) instead of redirecting to a login
730+
// page.
731+
Return403ForGetRequests bool `yaml:"return403ForGetRequests,omitempty"`
716732
}
717733

718734
// BackendSSO specifies the identity parameters to use for a backend.

proxy/internal/cookiemanager/cookiemanager.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,27 @@ const (
4646
tlsProxyNonce = "TLSPROXYNONCE"
4747

4848
expiredAuthTokenLeeway = 7 * 24 * time.Hour
49+
defaultTokenLifetime = 20 * time.Hour
4950
)
5051

5152
type CookieManager struct {
52-
tm *tokenmanager.TokenManager
53-
provider string
54-
domain string
55-
issuer string
53+
tm *tokenmanager.TokenManager
54+
provider string
55+
domain string
56+
issuer string
57+
tokenLifetime time.Duration
5658
}
5759

58-
func New(tm *tokenmanager.TokenManager, provider, domain, issuer string) *CookieManager {
60+
func New(tm *tokenmanager.TokenManager, provider, domain, issuer string, tokenLifetime time.Duration) *CookieManager {
61+
if tokenLifetime <= 0 {
62+
tokenLifetime = defaultTokenLifetime
63+
}
5964
return &CookieManager{
60-
tm: tm,
61-
provider: provider,
62-
domain: domain,
63-
issuer: issuer,
65+
tm: tm,
66+
provider: provider,
67+
domain: domain,
68+
issuer: issuer,
69+
tokenLifetime: tokenLifetime,
6470
}
6571
}
6672

@@ -109,7 +115,7 @@ func (cm *CookieManager) SetAuthTokenCookie(w http.ResponseWriter, req *http.Req
109115
}
110116
claims["th"] = th
111117
}
112-
token, err := cm.MintToken(claims, 20*time.Hour, cm.issuer, "")
118+
token, err := cm.MintToken(claims, cm.tokenLifetime, cm.issuer, "")
113119
if err != nil {
114120
return err
115121
}
@@ -118,7 +124,7 @@ func (cm *CookieManager) SetAuthTokenCookie(w http.ResponseWriter, req *http.Req
118124
Value: token,
119125
Domain: cm.domain,
120126
Path: "/",
121-
Expires: now.Add(20*time.Hour + expiredAuthTokenLeeway),
127+
Expires: now.Add(cm.tokenLifetime + expiredAuthTokenLeeway),
122128
SameSite: http.SameSiteLaxMode,
123129
Secure: true,
124130
HttpOnly: true,

proxy/internal/cookiemanager/cookiemanager_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestCookies(t *testing.T) {
4646
if err != nil {
4747
t.Fatalf("New: %v", err)
4848
}
49-
cm := New(tm, "idp", "example.com", "https://idp.example.com")
49+
cm := New(tm, "idp", "example.com", "https://idp.example.com", 0)
5050

5151
recorder := httptest.NewRecorder()
5252
req, _ := http.NewRequest("GET", "http://example.com", nil)

proxy/proxy.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ func (p *Proxy) Reconfigure(cfg *Config) error {
331331
for _, pp := range cfg.OIDCProviders {
332332
_, host, _, _ := hostAndPath(pp.RedirectURL)
333333
issuer := "https://" + host + "/"
334-
cm := cookiemanager.New(p.tokenManager, pp.Name, pp.Domain, issuer)
334+
cm := cookiemanager.New(p.tokenManager, pp.Name, pp.Domain, issuer, pp.TokenLifetime)
335335
oidcCfg := oidc.Config{
336336
DiscoveryURL: pp.DiscoveryURL,
337337
AuthEndpoint: pp.AuthEndpoint,
@@ -359,7 +359,7 @@ func (p *Proxy) Reconfigure(cfg *Config) error {
359359
for _, pp := range cfg.SAMLProviders {
360360
_, host, _, _ := hostAndPath(pp.ACSURL)
361361
issuer := "https://" + host + "/"
362-
cm := cookiemanager.New(p.tokenManager, pp.Name, pp.Domain, issuer)
362+
cm := cookiemanager.New(p.tokenManager, pp.Name, pp.Domain, issuer, pp.TokenLifetime)
363363
samlCfg := saml.Config{
364364
SSOURL: pp.SSOURL,
365365
EntityID: pp.EntityID,
@@ -389,7 +389,7 @@ func (p *Proxy) Reconfigure(cfg *Config) error {
389389
}
390390
_, host, _, _ := hostAndPath(pp.Endpoint)
391391
issuer := "https://" + host + "/"
392-
cm := cookiemanager.New(p.tokenManager, pp.Name, pp.Domain, issuer)
392+
cm := cookiemanager.New(p.tokenManager, pp.Name, pp.Domain, issuer, pp.TokenLifetime)
393393
cfg := passkeys.Config{
394394
Store: p.store,
395395
Other: other.identityProvider,

proxy/sso-oidc_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ func newIDPServer(t *testing.T) *idpServer {
378378
if err != nil {
379379
t.Fatalf("tokenmanager.New: %v", err)
380380
}
381-
cm := cookiemanager.New(tm, "idp", "example.com", "https://idp.example.com")
381+
cm := cookiemanager.New(tm, "idp", "example.com", "https://idp.example.com", 0)
382382
opts := oidc.ServerOptions{
383383
CookieManager: cm,
384384
Clients: []oidc.Client{

0 commit comments

Comments
 (0)