From 7ca1eff2e432d63cd8d77c2f402af45ee62568b9 Mon Sep 17 00:00:00 2001 From: Anders Ingemann Date: Mon, 30 Oct 2023 16:48:54 +0100 Subject: [PATCH 1/2] Respect http timeout setting --- auth_server/authn/github_auth.go | 2 +- auth_server/authn/gitlab_auth.go | 2 +- auth_server/authn/google_auth.go | 14 +++++++------- auth_server/authn/oidc_auth.go | 4 ++-- auth_server/server/config.go | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/auth_server/authn/github_auth.go b/auth_server/authn/github_auth.go index 7787739e..73150519 100644 --- a/auth_server/authn/github_auth.go +++ b/auth_server/authn/github_auth.go @@ -191,7 +191,7 @@ func NewGitHubAuth(c *GitHubAuthConfig) (*GitHubAuth, error) { return &GitHubAuth{ config: c, db: db, - client: &http.Client{Timeout: 10 * time.Second}, + client: &http.Client{Timeout: c.HTTPTimeout}, tmpl: template.Must(template.New("github_auth").Parse(string(github_auth))), tmplResult: template.Must(template.New("github_auth_result").Parse(string(github_auth_result))), }, nil diff --git a/auth_server/authn/gitlab_auth.go b/auth_server/authn/gitlab_auth.go index 9866fb6d..9a7a7e90 100644 --- a/auth_server/authn/gitlab_auth.go +++ b/auth_server/authn/gitlab_auth.go @@ -140,7 +140,7 @@ func NewGitlabAuth(c *GitlabAuthConfig) (*GitlabAuth, error) { return &GitlabAuth{ config: c, db: db, - client: &http.Client{Timeout: 10 * time.Second}, + client: &http.Client{Timeout: c.HTTPTimeout}, tmpl: template.Must(template.New("gitlab_auth").Parse(string(gitlab_auth))), tmplResult: template.Must(template.New("gitlab_auth_result").Parse(string(gitlab_auth_result))), }, nil diff --git a/auth_server/authn/google_auth.go b/auth_server/authn/google_auth.go index a717839b..5a73c91d 100644 --- a/auth_server/authn/google_auth.go +++ b/auth_server/authn/google_auth.go @@ -33,12 +33,12 @@ import ( ) type GoogleAuthConfig struct { - Domain string `yaml:"domain,omitempty"` - ClientId string `yaml:"client_id,omitempty"` - ClientSecret string `yaml:"client_secret,omitempty"` - ClientSecretFile string `yaml:"client_secret_file,omitempty"` - TokenDB string `yaml:"token_db,omitempty"` - HTTPTimeout int `yaml:"http_timeout,omitempty"` + Domain string `yaml:"domain,omitempty"` + ClientId string `yaml:"client_id,omitempty"` + ClientSecret string `yaml:"client_secret,omitempty"` + ClientSecretFile string `yaml:"client_secret_file,omitempty"` + TokenDB string `yaml:"token_db,omitempty"` + HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` } type GoogleAuthRequest struct { @@ -136,7 +136,7 @@ func NewGoogleAuth(c *GoogleAuthConfig) (*GoogleAuth, error) { return &GoogleAuth{ config: c, db: db, - client: &http.Client{Timeout: 10 * time.Second}, + client: &http.Client{Timeout: c.HTTPTimeout}, tmpl: template.Must(template.New("google_auth").Parse(string(google_auth))), }, nil } diff --git a/auth_server/authn/oidc_auth.go b/auth_server/authn/oidc_auth.go index 624cfe9c..07d1ca84 100644 --- a/auth_server/authn/oidc_auth.go +++ b/auth_server/authn/oidc_auth.go @@ -50,7 +50,7 @@ type OIDCAuthConfig struct { // path where the tokendb should be stored within the container TokenDB string `yaml:"token_db,omitempty"` // --- optional --- - HTTPTimeout int `yaml:"http_timeout,omitempty"` + HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` // the URL of the docker registry. Used to generate a full docker login command after authentication RegistryURL string `yaml:"registry_url,omitempty"` // --- optional --- @@ -115,7 +115,7 @@ func NewOIDCAuth(c *OIDCAuthConfig) (*OIDCAuth, error) { return &OIDCAuth{ config: c, db: db, - client: &http.Client{Timeout: 10 * time.Second}, + client: &http.Client{Timeout: c.HTTPTimeout}, tmpl: template.Must(template.New("oidc_auth").Parse(string(oidcAuth))), tmplResult: template.Must(template.New("oidc_auth_result").Parse(string(oidcAuthResult))), ctx: ctx, diff --git a/auth_server/server/config.go b/auth_server/server/config.go index 9a396f71..88d4ebfe 100644 --- a/auth_server/server/config.go +++ b/auth_server/server/config.go @@ -195,7 +195,7 @@ func validate(c *Config) error { return errors.New("google_auth.{client_id,client_secret,token_db} are required.") } if gac.HTTPTimeout <= 0 { - gac.HTTPTimeout = 10 + gac.HTTPTimeout = time.Duration(10 * time.Second) } } if ghac := c.GitHubAuth; ghac != nil { @@ -238,7 +238,7 @@ func validate(c *Config) error { return errors.New("oidc_auth.{issuer,redirect_url,client_id,client_secret,token_db} are required") } if oidc.HTTPTimeout <= 0 { - oidc.HTTPTimeout = 10 + oidc.HTTPTimeout = time.Duration(10 * time.Second) } if oidc.UserClaim == "" { oidc.UserClaim = "email" From d58fca67701ade0230e7aa1d01fd41af5c595a6f Mon Sep 17 00:00:00 2001 From: Anders Ingemann Date: Mon, 30 Oct 2023 17:01:59 +0100 Subject: [PATCH 2/2] Generalize redis & gcs tokendb, support in OIDC & Google auth --- auth_server/authn/github_auth.go | 15 ++--------- auth_server/authn/gitlab_auth.go | 41 +++++++++++------------------ auth_server/authn/google_auth.go | 31 ++++++++++++++++------ auth_server/authn/oidc_auth.go | 42 +++++++++++++++++++++--------- auth_server/authn/tokendb_gcs.go | 5 ++++ auth_server/authn/tokendb_redis.go | 25 +++++------------- auth_server/server/config.go | 26 +++++++++++++++--- 7 files changed, 103 insertions(+), 82 deletions(-) diff --git a/auth_server/authn/github_auth.go b/auth_server/authn/github_auth.go index 73150519..b74d8d99 100644 --- a/auth_server/authn/github_auth.go +++ b/auth_server/authn/github_auth.go @@ -29,7 +29,6 @@ import ( "time" "github.com/cesanta/glog" - "github.com/go-redis/redis" "github.com/cesanta/docker_auth/auth_server/api" ) @@ -62,8 +61,8 @@ type GitHubAuthConfig struct { ClientSecret string `yaml:"client_secret,omitempty"` ClientSecretFile string `yaml:"client_secret_file,omitempty"` TokenDB string `yaml:"token_db,omitempty"` - GCSTokenDB *GitHubGCSStoreConfig `yaml:"gcs_token_db,omitempty"` - RedisTokenDB *GitHubRedisStoreConfig `yaml:"redis_token_db,omitempty"` + GCSTokenDB *GCSStoreConfig `yaml:"gcs_token_db,omitempty"` + RedisTokenDB *RedisStoreConfig `yaml:"redis_token_db,omitempty"` HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` RevalidateAfter time.Duration `yaml:"revalidate_after,omitempty"` GithubWebUri string `yaml:"github_web_uri,omitempty"` @@ -71,16 +70,6 @@ type GitHubAuthConfig struct { RegistryUrl string `yaml:"registry_url,omitempty"` } -type GitHubGCSStoreConfig struct { - Bucket string `yaml:"bucket,omitempty"` - ClientSecretFile string `yaml:"client_secret_file,omitempty"` -} - -type GitHubRedisStoreConfig struct { - ClientOptions *redis.Options `yaml:"redis_options,omitempty"` - ClusterOptions *redis.ClusterOptions `yaml:"redis_cluster_options,omitempty"` -} - type GitHubAuthRequest struct { Action string `json:"action,omitempty"` Code string `json:"code,omitempty"` diff --git a/auth_server/authn/gitlab_auth.go b/auth_server/authn/gitlab_auth.go index 9a7a7e90..2af8a6d3 100644 --- a/auth_server/authn/gitlab_auth.go +++ b/auth_server/authn/gitlab_auth.go @@ -29,7 +29,6 @@ import ( "time" "github.com/cesanta/glog" - "github.com/go-redis/redis" "github.com/cesanta/docker_auth/auth_server/api" ) @@ -57,20 +56,20 @@ type ParentGitlabTeam struct { } type GitlabAuthConfig struct { - Organization string `yaml:"organization,omitempty"` - ClientId string `yaml:"client_id,omitempty"` - ClientSecret string `yaml:"client_secret,omitempty"` - ClientSecretFile string `yaml:"client_secret_file,omitempty"` - TokenDB string `yaml:"token_db,omitempty"` - GCSTokenDB *GitlabGCSStoreConfig `yaml:"gcs_token_db,omitempty"` - RedisTokenDB *GitlabRedisStoreConfig `yaml:"redis_token_db,omitempty"` - HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` - RevalidateAfter time.Duration `yaml:"revalidate_after,omitempty"` - GitlabWebUri string `yaml:"gitlab_web_uri,omitempty"` - GitlabApiUri string `yaml:"gitlab_api_uri,omitempty"` - RegistryUrl string `yaml:"registry_url,omitempty"` - GrantType string `yaml:"grant_type,omitempty"` - RedirectUri string `yaml:"redirect_uri,omitempty"` + Organization string `yaml:"organization,omitempty"` + ClientId string `yaml:"client_id,omitempty"` + ClientSecret string `yaml:"client_secret,omitempty"` + ClientSecretFile string `yaml:"client_secret_file,omitempty"` + TokenDB string `yaml:"token_db,omitempty"` + GCSTokenDB *GCSStoreConfig `yaml:"gcs_token_db,omitempty"` + RedisTokenDB *RedisStoreConfig `yaml:"redis_token_db,omitempty"` + HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` + RevalidateAfter time.Duration `yaml:"revalidate_after,omitempty"` + GitlabWebUri string `yaml:"gitlab_web_uri,omitempty"` + GitlabApiUri string `yaml:"gitlab_api_uri,omitempty"` + RegistryUrl string `yaml:"registry_url,omitempty"` + GrantType string `yaml:"grant_type,omitempty"` + RedirectUri string `yaml:"redirect_uri,omitempty"` } type CodeToGitlabTokenResponse struct { @@ -85,16 +84,6 @@ type CodeToGitlabTokenResponse struct { ErrorDescription string `json:"error_description,omitempty"` } -type GitlabGCSStoreConfig struct { - Bucket string `yaml:"bucket,omitempty"` - ClientSecretFile string `yaml:"client_secret_file,omitempty"` -} - -type GitlabRedisStoreConfig struct { - ClientOptions *redis.Options `yaml:"redis_options,omitempty"` - ClusterOptions *redis.ClusterOptions `yaml:"redis_cluster_options,omitempty"` -} - type GitlabAuthRequest struct { Action string `json:"action,omitempty"` Code string `json:"code,omitempty"` @@ -125,7 +114,7 @@ func NewGitlabAuth(c *GitlabAuthConfig) (*GitlabAuth, error) { db, err = NewGCSTokenDB(c.GCSTokenDB.Bucket, c.GCSTokenDB.ClientSecretFile) dbName = "GCS: " + c.GCSTokenDB.Bucket case c.RedisTokenDB != nil: - db, err = NewRedisGitlabTokenDB(c.RedisTokenDB) + db, err = NewRedisTokenDB(c.RedisTokenDB) dbName = db.(*redisTokenDB).String() default: db, err = NewTokenDB(c.TokenDB) diff --git a/auth_server/authn/google_auth.go b/auth_server/authn/google_auth.go index 5a73c91d..c2016e65 100644 --- a/auth_server/authn/google_auth.go +++ b/auth_server/authn/google_auth.go @@ -33,12 +33,14 @@ import ( ) type GoogleAuthConfig struct { - Domain string `yaml:"domain,omitempty"` - ClientId string `yaml:"client_id,omitempty"` - ClientSecret string `yaml:"client_secret,omitempty"` - ClientSecretFile string `yaml:"client_secret_file,omitempty"` - TokenDB string `yaml:"token_db,omitempty"` - HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` + Domain string `yaml:"domain,omitempty"` + ClientId string `yaml:"client_id,omitempty"` + ClientSecret string `yaml:"client_secret,omitempty"` + ClientSecretFile string `yaml:"client_secret_file,omitempty"` + TokenDB string `yaml:"token_db,omitempty"` + GCSTokenDB *GCSStoreConfig `yaml:"gcs_token_db,omitempty"` + RedisTokenDB *RedisStoreConfig `yaml:"redis_token_db,omitempty"` + HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` } type GoogleAuthRequest struct { @@ -127,11 +129,24 @@ type GoogleAuth struct { } func NewGoogleAuth(c *GoogleAuthConfig) (*GoogleAuth, error) { - db, err := NewTokenDB(c.TokenDB) + var db TokenDB + var err error + dbName := c.TokenDB + + switch { + case c.GCSTokenDB != nil: + db, err = NewGCSTokenDB(c.GCSTokenDB.Bucket, c.GCSTokenDB.ClientSecretFile) + dbName = "GCS: " + c.GCSTokenDB.Bucket + case c.RedisTokenDB != nil: + db, err = NewRedisTokenDB(c.RedisTokenDB) + dbName = db.(*redisTokenDB).String() + default: + db, err = NewTokenDB(c.TokenDB) + } if err != nil { return nil, err } - glog.Infof("Google auth token DB at %s", c.TokenDB) + glog.Infof("Google auth token DB at %s", dbName) google_auth, _ := static.ReadFile("data/google_auth.tmpl") return &GoogleAuth{ config: c, diff --git a/auth_server/authn/oidc_auth.go b/auth_server/authn/oidc_auth.go index 07d1ca84..8ec1a205 100644 --- a/auth_server/authn/oidc_auth.go +++ b/auth_server/authn/oidc_auth.go @@ -40,27 +40,29 @@ import ( type OIDCAuthConfig struct { // --- necessary --- // URL of the authentication provider. Must be able to serve the /.well-known/openid-configuration - Issuer string `yaml:"issuer,omitempty"` + Issuer string `yaml:"issuer,omitempty"` // URL of the auth server. Has to end with /oidc_auth - RedirectURL string `yaml:"redirect_url,omitempty"` + RedirectURL string `yaml:"redirect_url,omitempty"` // ID and secret, priovided by the OIDC provider after registration of the auth server - ClientId string `yaml:"client_id,omitempty"` - ClientSecret string `yaml:"client_secret,omitempty"` - ClientSecretFile string `yaml:"client_secret_file,omitempty"` + ClientId string `yaml:"client_id,omitempty"` + ClientSecret string `yaml:"client_secret,omitempty"` + ClientSecretFile string `yaml:"client_secret_file,omitempty"` // path where the tokendb should be stored within the container - TokenDB string `yaml:"token_db,omitempty"` + TokenDB string `yaml:"token_db,omitempty"` + GCSTokenDB *GCSStoreConfig `yaml:"gcs_token_db,omitempty"` + RedisTokenDB *RedisStoreConfig `yaml:"redis_token_db,omitempty"` // --- optional --- - HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` + HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` // the URL of the docker registry. Used to generate a full docker login command after authentication - RegistryURL string `yaml:"registry_url,omitempty"` + RegistryURL string `yaml:"registry_url,omitempty"` // --- optional --- // String claim to use for the username - UserClaim string `yaml:"user_claim,omitempty"` + UserClaim string `yaml:"user_claim,omitempty"` // --- optional --- // []string to add as labels. - LabelsClaims []string `yaml:"labels_claims,omitempty"` + LabelsClaims []string `yaml:"labels_claims,omitempty"` // --- optional --- - Scopes []string `yaml:"scopes,omitempty"` + Scopes []string `yaml:"scopes,omitempty"` } // OIDCRefreshTokenResponse is sent by OIDC provider in response to the grant_type=refresh_token request. @@ -92,11 +94,25 @@ type OIDCAuth struct { Creates everything necessary for OIDC auth. */ func NewOIDCAuth(c *OIDCAuthConfig) (*OIDCAuth, error) { - db, err := NewTokenDB(c.TokenDB) + var db TokenDB + var err error + dbName := c.TokenDB + + switch { + case c.GCSTokenDB != nil: + db, err = NewGCSTokenDB(c.GCSTokenDB.Bucket, c.GCSTokenDB.ClientSecretFile) + dbName = "GCS: " + c.GCSTokenDB.Bucket + case c.RedisTokenDB != nil: + db, err = NewRedisTokenDB(c.RedisTokenDB) + dbName = db.(*redisTokenDB).String() + default: + db, err = NewTokenDB(c.TokenDB) + } + if err != nil { return nil, err } - glog.Infof("OIDC auth token DB at %s", c.TokenDB) + glog.Infof("OIDC auth token DB at %s", dbName) ctx := context.Background() oidcAuth, _ := static.ReadFile("data/oidc_auth.tmpl") oidcAuthResult, _ := static.ReadFile("data/oidc_auth_result.tmpl") diff --git a/auth_server/authn/tokendb_gcs.go b/auth_server/authn/tokendb_gcs.go index 284d1a3a..d91b1a4f 100644 --- a/auth_server/authn/tokendb_gcs.go +++ b/auth_server/authn/tokendb_gcs.go @@ -31,6 +31,11 @@ import ( "github.com/cesanta/docker_auth/auth_server/api" ) +type GCSStoreConfig struct { + Bucket string `yaml:"bucket,omitempty"` + ClientSecretFile string `yaml:"client_secret_file,omitempty"` +} + // NewGCSTokenDB return a new TokenDB structure which uses Google Cloud Storage as backend. The // created DB uses file-per-user strategy and stores credentials independently for each user. // diff --git a/auth_server/authn/tokendb_redis.go b/auth_server/authn/tokendb_redis.go index d6066e4c..9b1fd528 100644 --- a/auth_server/authn/tokendb_redis.go +++ b/auth_server/authn/tokendb_redis.go @@ -29,6 +29,11 @@ import ( "github.com/go-redis/redis" ) +type RedisStoreConfig struct { + ClientOptions *redis.Options `yaml:"redis_options,omitempty"` + ClusterOptions *redis.ClusterOptions `yaml:"redis_cluster_options,omitempty"` +} + type RedisClient interface { Get(key string) *redis.StringCmd Set(key string, value interface{}, expiration time.Duration) *redis.StatusCmd @@ -37,23 +42,7 @@ type RedisClient interface { // NewRedisTokenDB returns a new TokenDB structure which uses Redis as the storage backend. // -func NewRedisTokenDB(options *GitHubRedisStoreConfig) (TokenDB, error) { - var client RedisClient - if options.ClusterOptions != nil { - if options.ClientOptions != nil { - glog.Infof("Both redis_token_db.configs and redis_token_db.cluster_configs have been set. Only the latter will be used") - } - client = redis.NewClusterClient(options.ClusterOptions) - } else { - client = redis.NewClient(options.ClientOptions) - } - - return &redisTokenDB{client}, nil -} - -// NewRedisTokenDB returns a new TokenDB structure which uses Redis as the storage backend. -// -func NewRedisGitlabTokenDB(options *GitlabRedisStoreConfig) (TokenDB, error) { +func NewRedisTokenDB(options *RedisStoreConfig) (TokenDB, error) { var client RedisClient if options.ClusterOptions != nil { if options.ClientOptions != nil { @@ -162,4 +151,4 @@ func (db *redisTokenDB) DeleteToken(user string) error { func (db *redisTokenDB) Close() error { return nil -} \ No newline at end of file +} diff --git a/auth_server/server/config.go b/auth_server/server/config.go index 88d4ebfe..ead4f82a 100644 --- a/auth_server/server/config.go +++ b/auth_server/server/config.go @@ -171,7 +171,7 @@ func validate(c *Config) error { return fmt.Errorf("expiration must be positive, got %d", c.Token.Expiration) } if c.Users == nil && c.ExtAuth == nil && c.GoogleAuth == nil && c.GitHubAuth == nil && c.GitlabAuth == nil && c.OIDCAuth == nil && c.LDAPAuth == nil && c.MongoAuth == nil && c.XormAuthn == nil && c.PluginAuthn == nil { - return errors.New("no auth methods are configured, this is probably a mistake. Use an empty user map if you really want to deny everyone.") + return errors.New("no auth methods are configured, this is probably a mistake. Use an empty user map if you really want to deny everyone") } if c.MongoAuth != nil { if err := c.MongoAuth.Validate("mongo_auth"); err != nil { @@ -191,9 +191,18 @@ func validate(c *Config) error { } gac.ClientSecret = strings.TrimSpace(string(contents)) } - if gac.ClientId == "" || gac.ClientSecret == "" || gac.TokenDB == "" { - return errors.New("google_auth.{client_id,client_secret,token_db} are required.") + if gac.ClientId == "" || gac.ClientSecret == "" || (gac.TokenDB == "" && (gac.GCSTokenDB == nil && gac.RedisTokenDB == nil)) { + return errors.New("google_auth.{client_id,client_secret,token_db} are required") } + + if gac.ClientId == "" || gac.ClientSecret == "" || (gac.GCSTokenDB != nil && (gac.GCSTokenDB.Bucket == "" || gac.GCSTokenDB.ClientSecretFile == "")) { + return errors.New("google_auth.{client_id,client_secret,gcs_token_db{bucket,client_secret_file}} are required") + } + + if gac.ClientId == "" || gac.ClientSecret == "" || (gac.RedisTokenDB != nil && gac.RedisTokenDB.ClientOptions == nil && gac.RedisTokenDB.ClusterOptions == nil) { + return errors.New("google_auth.{client_id,client_secret,redis_token_db.{redis_options,redis_cluster_options}} are required") + } + if gac.HTTPTimeout <= 0 { gac.HTTPTimeout = time.Duration(10 * time.Second) } @@ -234,9 +243,18 @@ func validate(c *Config) error { } oidc.ClientSecret = strings.TrimSpace(string(contents)) } - if oidc.ClientId == "" || oidc.ClientSecret == "" || oidc.TokenDB == "" || oidc.Issuer == "" || oidc.RedirectURL == "" { + if oidc.ClientId == "" || oidc.ClientSecret == "" || oidc.Issuer == "" || oidc.RedirectURL == "" || (oidc.TokenDB == "" && (oidc.GCSTokenDB == nil && oidc.RedisTokenDB == nil)) { return errors.New("oidc_auth.{issuer,redirect_url,client_id,client_secret,token_db} are required") } + + if oidc.ClientId == "" || oidc.ClientSecret == "" || (oidc.GCSTokenDB != nil && (oidc.GCSTokenDB.Bucket == "" || oidc.GCSTokenDB.ClientSecretFile == "")) { + return errors.New("oidc_auth.{client_id,client_secret,gcs_token_db{bucket,client_secret_file}} are required") + } + + if oidc.ClientId == "" || oidc.ClientSecret == "" || (oidc.RedisTokenDB != nil && oidc.RedisTokenDB.ClientOptions == nil && oidc.RedisTokenDB.ClusterOptions == nil) { + return errors.New("oidc_auth.{client_id,client_secret,redis_token_db.{redis_options,redis_cluster_options}} are required") + } + if oidc.HTTPTimeout <= 0 { oidc.HTTPTimeout = time.Duration(10 * time.Second) }