Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support redis & GCS token DB backing in Google & OIDC auth #374

Merged
merged 2 commits into from
Dec 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading