Repository: argoproj/argo (go)

Issue:
SSO Timeout Configuration
In the new SSO implementation, it looks like the ID token never has the 'Expiry' field set, and defaults to having a 1 hour timeout. It would be more convenient if this was extended, and most convenient if it could be configured in the workflow controller configmap. 

The cookie containing this token has a 10 hour timeout. It seems like it would make the most sense for this timeout to match the token Expiry.
https://github.com/argoproj/argo/blob/d07a0e74888254fe78fa653e532c5f1963056598/server/auth/sso/sso.go#L185-L192

This is where I believe the token Expiry value would need to be set:
https://github.com/argoproj/argo/blob/d07a0e74888254fe78fa653e532c5f1963056598/server/auth/sso/sso.go#L153-L165

Related documentation:
https://godoc.org/golang.org/x/oauth2#Token

Pull request:
feat(controller): make SSO session expiry configurable
Signed-off-by: Arghya Sadhu <arghya88@gmail.com>

Checklist:

* [ ] My organization is added to [USERS.md](https://github.com/argoproj/argo/blob/master/USERS.md).

<!--

Please leave your PR in draft if you don't need a review yet. 

To fix failing `CI / Codegen`, run `make codegen`. 

-->

Fixes #3873

Patch:
diff --git a/server/auth/sso/sso.go b/server/auth/sso/sso.go
index 4a7befb99..70cd93e60 100644
--- a/server/auth/sso/sso.go
+++ b/server/auth/sso/sso.go
@@ -28,7 +28,6 @@ import (
 const (
 	Prefix                              = "Bearer v2:"
 	issuer                              = "argo-server"                // the JWT issuer
-	expiry                              = 10 * time.Hour               // how long JWT are valid for
 	secretName                          = "sso"                        // where we store SSO secret
 	cookieEncryptionPrivateKeySecretKey = "cookieEncryptionPrivateKey" // the key name for the private key in the secret
 )
@@ -52,6 +51,7 @@ type sso struct {
 	privateKey      crypto.PrivateKey
 	encrypter       jose.Encrypter
 	rbacConfig      *rbac.Config
+	expiry          time.Duration
 }
 
 func (s *sso) IsRBACEnabled() bool {
@@ -65,7 +65,15 @@ type Config struct {
 	RedirectURL  string                  `json:"redirectUrl"`
 	RBAC         *rbac.Config            `json:"rbac,omitempty"`
 	// additional scopes (on top of "openid")
-	Scopes []string `json:"scopes,omitempty"`
+	Scopes        []string        `json:"scopes,omitempty"`
+	SessionExpiry metav1.Duration `json:"sessionExpiry,omitempty"`
+}
+
+func (c Config) GetSessionExpiry() time.Duration {
+	if c.SessionExpiry.Duration > 0 {
+		return c.SessionExpiry.Duration
+	}
+	return 10 * time.Hour
 }
 
 // Abstract methods of oidc.Provider that our code uses into an interface. That
@@ -114,7 +122,6 @@ func newSso(
 	if err != nil {
 		return nil, err
 	}
-
 	var clientIDObj *apiv1.Secret
 	if c.ClientID.Name == c.ClientSecret.Name {
 		clientIDObj = clientSecretObj
@@ -174,6 +181,7 @@ func newSso(
 		privateKey:      privateKey,
 		encrypter:       encrypter,
 		rbacConfig:      c.RBAC,
+		expiry:          c.GetSessionExpiry(),
 	}, nil
 }
 
@@ -225,7 +233,7 @@ func (s *sso) HandleCallback(w http.ResponseWriter, r *http.Request) {
 		_, _ = w.Write([]byte(fmt.Sprintf("failed to get claims: %v", err)))
 		return
 	}
-	argoClaims := &types.Claims{Claims: jwt.Claims{Issuer: issuer, Subject: c.Subject, Expiry: jwt.NewNumericDate(time.Now().Add(expiry))}, Groups: c.Groups}
+	argoClaims := &types.Claims{Claims: jwt.Claims{Issuer: issuer, Subject: c.Subject, Expiry: jwt.NewNumericDate(time.Now().Add(s.expiry))}, Groups: c.Groups}
 	raw, err := jwt.Encrypted(s.encrypter).Claims(argoClaims).CompactSerialize()
 	if err != nil {
 		panic(err)
@@ -236,7 +244,7 @@ func (s *sso) HandleCallback(w http.ResponseWriter, r *http.Request) {
 		Value:    value,
 		Name:     "authorization",
 		Path:     s.baseHRef,
-		Expires:  time.Now().Add(expiry),
+		Expires:  time.Now().Add(s.expiry),
 		SameSite: http.SameSiteStrictMode,
 		Secure:   s.secure,
 	})


Test patch:
diff --git a/server/auth/sso/sso_test.go b/server/auth/sso/sso_test.go
index a3fd2f7c2..cee4facf2 100644
--- a/server/auth/sso/sso_test.go
+++ b/server/auth/sso/sso_test.go
@@ -3,6 +3,7 @@ package sso
 import (
 	"context"
 	"testing"
+	"time"
 
 	"github.com/coreos/go-oidc"
 	"github.com/stretchr/testify/assert"
@@ -62,6 +63,7 @@ func TestLoadSsoClientIdFromSecret(t *testing.T) {
 	ssoObject := ssoInterface.(*sso)
 	assert.Equal(t, "sso-client-id-value", ssoObject.config.ClientID)
 	assert.Equal(t, "sso-client-secret-value", ssoObject.config.ClientSecret)
+	assert.Equal(t, 10*time.Hour, ssoObject.expiry)
 }
 
 func TestLoadSsoClientIdFromDifferentSecret(t *testing.T) {
@@ -101,3 +103,10 @@ func TestLoadSsoClientIdFromSecretNoKeyFails(t *testing.T) {
 	assert.Error(t, err)
 	assert.Regexp(t, "key nonexistent missing in secret argo-sso-secret", err.Error())
 }
+
+func TestGetSessionExpiry(t *testing.T) {
+	config := Config{
+		SessionExpiry: metav1.Duration{Duration: 5 * time.Hour},
+	}
+	assert.Equal(t, config.GetSessionExpiry(), 5*time.Hour)
+}