Skip to content

Commit

Permalink
Support redis & GCS token DB backing in Google & OIDC auth (cesanta#374)
Browse files Browse the repository at this point in the history
* Respect http timeout setting

* Generalize redis & gcs tokendb, support in OIDC & Google auth
  • Loading branch information
andsens authored Dec 24, 2023
1 parent 7e42167 commit 47759c9
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 88 deletions.
17 changes: 3 additions & 14 deletions auth_server/authn/github_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"time"

"github.com/cesanta/glog"
"github.com/go-redis/redis"

"github.com/cesanta/docker_auth/auth_server/api"
)
Expand Down Expand Up @@ -62,25 +61,15 @@ 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"`
GithubApiUri string `yaml:"github_api_uri,omitempty"`
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"`
Expand Down Expand Up @@ -191,7 +180,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
Expand Down
43 changes: 16 additions & 27 deletions auth_server/authn/gitlab_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"time"

"github.com/cesanta/glog"
"github.com/go-redis/redis"

"github.com/cesanta/docker_auth/auth_server/api"
)
Expand Down Expand Up @@ -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 {
Expand All @@ -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"`
Expand Down Expand Up @@ -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)
Expand All @@ -140,7 +129,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
Expand Down
33 changes: 24 additions & 9 deletions auth_server/authn/google_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 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"`
GCSTokenDB *GCSStoreConfig `yaml:"gcs_token_db,omitempty"`
RedisTokenDB *RedisStoreConfig `yaml:"redis_token_db,omitempty"`
HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"`
}

type GoogleAuthRequest struct {
Expand Down Expand Up @@ -127,16 +129,29 @@ 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,
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
}
Expand Down
44 changes: 30 additions & 14 deletions auth_server/authn/oidc_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 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"`
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.
Expand Down Expand Up @@ -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")
Expand All @@ -115,7 +131,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,
Expand Down
5 changes: 5 additions & 0 deletions auth_server/authn/tokendb_gcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
25 changes: 7 additions & 18 deletions auth_server/authn/tokendb_redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -162,4 +151,4 @@ func (db *redisTokenDB) DeleteToken(user string) error {

func (db *redisTokenDB) Close() error {
return nil
}
}
30 changes: 24 additions & 6 deletions auth_server/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -191,11 +191,20 @@ 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 = 10
gac.HTTPTimeout = time.Duration(10 * time.Second)
}
}
if ghac := c.GitHubAuth; ghac != nil {
Expand Down Expand Up @@ -234,11 +243,20 @@ 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 = 10
oidc.HTTPTimeout = time.Duration(10 * time.Second)
}
if oidc.UserClaim == "" {
oidc.UserClaim = "email"
Expand Down

0 comments on commit 47759c9

Please sign in to comment.