Skip to content

Commit

Permalink
Implement option to specify bcrypt token hash cost
Browse files Browse the repository at this point in the history
The defualt cost of 10 can be very prohibitive when the
server is hit with more than ~10 simultaneous requests and
only one CPU is available
  • Loading branch information
andsens committed Nov 1, 2023
1 parent d58fca6 commit 2386d50
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 61 deletions.
31 changes: 16 additions & 15 deletions auth_server/authn/github_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,18 @@ type ParentGitHubTeam struct {
}

type GitHubAuthConfig 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 *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"`
Organization string `yaml:"organization,omitempty"`
ClientId string `yaml:"client_id,omitempty"`
ClientSecret string `yaml:"client_secret,omitempty"`
ClientSecretFile string `yaml:"client_secret_file,omitempty"`
LevelTokenDB *LevelDBStoreConfig `yaml:"level_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 GitHubAuthRequest struct {
Expand Down Expand Up @@ -158,17 +158,18 @@ func parseLinkHeader(linkLines []string) (linkHeader, error) {
func NewGitHubAuth(c *GitHubAuthConfig) (*GitHubAuth, error) {
var db TokenDB
var err error
dbName := c.TokenDB
var dbName string

switch {
case c.GCSTokenDB != nil:
db, err = NewGCSTokenDB(c.GCSTokenDB.Bucket, c.GCSTokenDB.ClientSecretFile)
db, err = NewGCSTokenDB(c.GCSTokenDB)
dbName = "GCS: " + c.GCSTokenDB.Bucket
case c.RedisTokenDB != nil:
db, err = NewRedisTokenDB(c.RedisTokenDB)
dbName = db.(*redisTokenDB).String()
default:
db, err = NewTokenDB(c.TokenDB)
db, err = NewTokenDB(c.LevelTokenDB)
dbName = c.LevelTokenDB.Path
}

if err != nil {
Expand Down
35 changes: 18 additions & 17 deletions auth_server/authn/gitlab_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,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 *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"`
Organization string `yaml:"organization,omitempty"`
ClientId string `yaml:"client_id,omitempty"`
ClientSecret string `yaml:"client_secret,omitempty"`
ClientSecretFile string `yaml:"client_secret_file,omitempty"`
LevelTokenDB *LevelDBStoreConfig `yaml:"level_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 Down Expand Up @@ -107,17 +107,18 @@ type GitlabAuth struct {
func NewGitlabAuth(c *GitlabAuthConfig) (*GitlabAuth, error) {
var db TokenDB
var err error
dbName := c.TokenDB
var dbName string

switch {
case c.GCSTokenDB != nil:
db, err = NewGCSTokenDB(c.GCSTokenDB.Bucket, c.GCSTokenDB.ClientSecretFile)
db, err = NewGCSTokenDB(c.GCSTokenDB)
dbName = "GCS: " + c.GCSTokenDB.Bucket
case c.RedisTokenDB != nil:
db, err = NewRedisTokenDB(c.RedisTokenDB)
dbName = db.(*redisTokenDB).String()
default:
db, err = NewTokenDB(c.TokenDB)
db, err = NewTokenDB(c.LevelTokenDB)
dbName = c.LevelTokenDB.Path
}

if err != nil {
Expand Down
23 changes: 12 additions & 11 deletions auth_server/authn/google_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +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"`
GCSTokenDB *GCSStoreConfig `yaml:"gcs_token_db,omitempty"`
RedisTokenDB *RedisStoreConfig `yaml:"redis_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"`
LevelTokenDB *LevelDBStoreConfig `yaml:"level_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 @@ -131,17 +131,18 @@ type GoogleAuth struct {
func NewGoogleAuth(c *GoogleAuthConfig) (*GoogleAuth, error) {
var db TokenDB
var err error
dbName := c.TokenDB
var dbName string

switch {
case c.GCSTokenDB != nil:
db, err = NewGCSTokenDB(c.GCSTokenDB.Bucket, c.GCSTokenDB.ClientSecretFile)
db, err = NewGCSTokenDB(c.GCSTokenDB)
dbName = "GCS: " + c.GCSTokenDB.Bucket
case c.RedisTokenDB != nil:
db, err = NewRedisTokenDB(c.RedisTokenDB)
dbName = db.(*redisTokenDB).String()
default:
db, err = NewTokenDB(c.TokenDB)
db, err = NewTokenDB(c.LevelTokenDB)
dbName = c.LevelTokenDB.Path
}
if err != nil {
return nil, err
Expand Down
13 changes: 7 additions & 6 deletions auth_server/authn/oidc_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ type OIDCAuthConfig struct {
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"`
GCSTokenDB *GCSStoreConfig `yaml:"gcs_token_db,omitempty"`
RedisTokenDB *RedisStoreConfig `yaml:"redis_token_db,omitempty"`
LevelTokenDB *LevelDBStoreConfig `yaml:"level_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"`
// the URL of the docker registry. Used to generate a full docker login command after authentication
Expand Down Expand Up @@ -96,17 +96,18 @@ Creates everything necessary for OIDC auth.
func NewOIDCAuth(c *OIDCAuthConfig) (*OIDCAuth, error) {
var db TokenDB
var err error
dbName := c.TokenDB
var dbName string

switch {
case c.GCSTokenDB != nil:
db, err = NewGCSTokenDB(c.GCSTokenDB.Bucket, c.GCSTokenDB.ClientSecretFile)
db, err = NewGCSTokenDB(c.GCSTokenDB)
dbName = "GCS: " + c.GCSTokenDB.Bucket
case c.RedisTokenDB != nil:
db, err = NewRedisTokenDB(c.RedisTokenDB)
dbName = db.(*redisTokenDB).String()
default:
db, err = NewTokenDB(c.TokenDB)
db, err = NewTokenDB(c.LevelTokenDB)
dbName = c.LevelTokenDB.Path
}

if err != nil {
Expand Down
14 changes: 10 additions & 4 deletions auth_server/authn/tokendb_gcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,26 @@ import (
type GCSStoreConfig struct {
Bucket string `yaml:"bucket,omitempty"`
ClientSecretFile string `yaml:"client_secret_file,omitempty"`
TokenHashCost int `yaml:"token_hash_cost,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.
//
// Note: it's not recomanded bucket to be shared with other apps or services
func NewGCSTokenDB(bucket, clientSecretFile string) (TokenDB, error) {
gcs, err := storage.NewClient(context.Background(), option.WithServiceAccountFile(clientSecretFile))
return &gcsTokenDB{gcs, bucket}, err
func NewGCSTokenDB(options *GCSStoreConfig) (TokenDB, error) {
gcs, err := storage.NewClient(context.Background(), option.WithServiceAccountFile(options.ClientSecretFile))
tokenHashCost := options.TokenHashCost
if tokenHashCost <= 0 {
tokenHashCost = bcrypt.DefaultCost
}
return &gcsTokenDB{gcs, options.Bucket, tokenHashCost}, err
}

type gcsTokenDB struct {
gcs *storage.Client
bucket string
tokenHashCost int
}

// GetValue gets token value associated with the provided user. Each user
Expand Down Expand Up @@ -77,7 +83,7 @@ func (db *gcsTokenDB) GetValue(user string) (*TokenDBValue, error) {
func (db *gcsTokenDB) StoreToken(user string, v *TokenDBValue, updatePassword bool) (dp string, err error) {
if updatePassword {
dp = uniuri.New()
dph, _ := bcrypt.GenerateFromPassword([]byte(dp), bcrypt.DefaultCost)
dph, _ := bcrypt.GenerateFromPassword([]byte(dp), db.tokenHashCost)
v.DockerPassword = string(dph)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ const (

var ExpiredToken = errors.New("expired token")

type LevelDBStoreConfig struct {
Path string `yaml:"path,omitempty"`
TokenHashCost int `yaml:"token_hash_cost,omitempty"`
}

// TokenDB stores tokens using LevelDB
type TokenDB interface {
// GetValue takes a username returns the corresponding token
Expand Down Expand Up @@ -75,8 +80,12 @@ type TokenDBValue struct {
}

// NewTokenDB returns a new TokenDB structure
func NewTokenDB(file string) (TokenDB, error) {
db, err := leveldb.OpenFile(file, nil)
func NewTokenDB(options *LevelDBStoreConfig) (TokenDB, error) {
db, err := leveldb.OpenFile(options.Path, nil)
tokenHashCost := options.TokenHashCost
if tokenHashCost <= 0 {
tokenHashCost = bcrypt.DefaultCost
}
return &TokenDBImpl{
DB: db,
}, err
Expand Down
10 changes: 8 additions & 2 deletions auth_server/authn/tokendb_redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
type RedisStoreConfig struct {
ClientOptions *redis.Options `yaml:"redis_options,omitempty"`
ClusterOptions *redis.ClusterOptions `yaml:"redis_cluster_options,omitempty"`
TokenHashCost int `yaml:"token_hash_cost,omitempty"`
}

type RedisClient interface {
Expand All @@ -52,12 +53,17 @@ func NewRedisTokenDB(options *RedisStoreConfig) (TokenDB, error) {
} else {
client = redis.NewClient(options.ClientOptions)
}
tokenHashCost := options.TokenHashCost
if tokenHashCost <= 0 {
tokenHashCost = bcrypt.DefaultCost
}

return &redisTokenDB{client}, nil
return &redisTokenDB{client,tokenHashCost}, nil
}

type redisTokenDB struct {
client RedisClient
tokenHashCost int
}

func (db *redisTokenDB) String() string {
Expand Down Expand Up @@ -95,7 +101,7 @@ func (db *redisTokenDB) GetValue(user string) (*TokenDBValue, error) {
func (db *redisTokenDB) StoreToken(user string, v *TokenDBValue, updatePassword bool) (dp string, err error) {
if updatePassword {
dp = uniuri.New()
dph, _ := bcrypt.GenerateFromPassword([]byte(dp), bcrypt.DefaultCost)
dph, _ := bcrypt.GenerateFromPassword([]byte(dp), db.tokenHashCost)
v.DockerPassword = string(dph)
}

Expand Down
8 changes: 4 additions & 4 deletions auth_server/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func validate(c *Config) error {
}
gac.ClientSecret = strings.TrimSpace(string(contents))
}
if gac.ClientId == "" || gac.ClientSecret == "" || (gac.TokenDB == "" && (gac.GCSTokenDB == nil && gac.RedisTokenDB == nil)) {
if gac.ClientId == "" || gac.ClientSecret == "" || (gac.LevelTokenDB != nil && (gac.GCSTokenDB == nil && gac.RedisTokenDB == nil)) {
return errors.New("google_auth.{client_id,client_secret,token_db} are required")
}

Expand All @@ -215,7 +215,7 @@ func validate(c *Config) error {
}
ghac.ClientSecret = strings.TrimSpace(string(contents))
}
if ghac.ClientId == "" || ghac.ClientSecret == "" || (ghac.TokenDB == "" && (ghac.GCSTokenDB == nil && ghac.RedisTokenDB == nil)) {
if ghac.ClientId == "" || ghac.ClientSecret == "" || (ghac.LevelTokenDB != nil && (ghac.GCSTokenDB == nil && ghac.RedisTokenDB == nil)) {
return errors.New("github_auth.{client_id,client_secret,token_db} are required")
}

Expand Down Expand Up @@ -243,7 +243,7 @@ func validate(c *Config) error {
}
oidc.ClientSecret = strings.TrimSpace(string(contents))
}
if oidc.ClientId == "" || oidc.ClientSecret == "" || oidc.Issuer == "" || oidc.RedirectURL == "" || (oidc.TokenDB == "" && (oidc.GCSTokenDB == nil && oidc.RedisTokenDB == nil)) {
if oidc.ClientId == "" || oidc.ClientSecret == "" || oidc.Issuer == "" || oidc.RedirectURL == "" || (oidc.LevelTokenDB != nil && (oidc.GCSTokenDB == nil && oidc.RedisTokenDB == nil)) {
return errors.New("oidc_auth.{issuer,redirect_url,client_id,client_secret,token_db} are required")
}

Expand Down Expand Up @@ -273,7 +273,7 @@ func validate(c *Config) error {
}
glab.ClientSecret = strings.TrimSpace(string(contents))
}
if glab.ClientId == "" || glab.ClientSecret == "" || (glab.TokenDB == "" && (glab.GCSTokenDB == nil && glab.RedisTokenDB == nil)) {
if glab.ClientId == "" || glab.ClientSecret == "" || (glab.LevelTokenDB != nil && (glab.GCSTokenDB == nil && glab.RedisTokenDB == nil)) {
return errors.New("gitlab_auth.{client_id,client_secret,token_db} are required")
}

Expand Down

0 comments on commit 2386d50

Please sign in to comment.