Skip to content

Commit

Permalink
Merge branch 'cesanta:main' into mtls
Browse files Browse the repository at this point in the history
  • Loading branch information
devon-mar authored Mar 22, 2024
2 parents 809a38d + 38e7252 commit 5dd7b10
Show file tree
Hide file tree
Showing 21 changed files with 1,076 additions and 328 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.17.x, 1.18.x, 1.19.x, 1.20.x]
go-version: [1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
4 changes: 2 additions & 2 deletions auth_server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.20-alpine3.17 as build
FROM golang:1.21-alpine3.19 as build

ARG VERSION
ENV VERSION "${VERSION}"
Expand All @@ -12,7 +12,7 @@ COPY . /build
WORKDIR /build
RUN make build

FROM alpine:3.17
FROM alpine:3.19
COPY --from=build /build/auth_server /docker_auth/
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/docker_auth/auth_server"]
Expand Down
4 changes: 3 additions & 1 deletion auth_server/authn/data/github_auth_result.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@
<body>
<p class="message">
You are successfully authenticated for the Docker Registry{{if .Organization}} with the <code>@{{.Organization}}</code> Github organization{{end}}.
Use the following username and password to login into the registry:
Log into the registry using one of these commands:
</p>
<hr>
<pre class="command"><span>$ </span>docker login -u {{.Username}} -p {{.Password}} {{if .RegistryUrl}}{{.RegistryUrl}}{{else}}docker.example.com{{end}}</pre>
<pre class="command"><span>$ </span>podman login -u {{.Username}} -p {{.Password}} {{if .RegistryUrl}}{{.RegistryUrl}}{{else}}docker.example.com{{end}}</pre>
<pre class="command"><span>$ </span>nerdctl login -u {{.Username}} -p {{.Password}} {{if .RegistryUrl}}{{.RegistryUrl}}{{else}}docker.example.com{{end}}</pre>
</body>
</html>
44 changes: 17 additions & 27 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 @@ -57,28 +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 *GitHubGCSStoreConfig `yaml:"gcs_token_db,omitempty"`
RedisTokenDB *GitHubRedisStoreConfig `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"`
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 @@ -169,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 All @@ -191,7 +181,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
50 changes: 20 additions & 30 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"`
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 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 All @@ -118,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 = NewRedisGitlabTokenDB(c.RedisTokenDB)
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 All @@ -140,7 +130,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
34 changes: 25 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"`
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 @@ -127,16 +129,30 @@ type GoogleAuth struct {
}

func NewGoogleAuth(c *GoogleAuthConfig) (*GoogleAuth, error) {
db, err := NewTokenDB(c.TokenDB)
var db TokenDB
var err error
var dbName string

switch {
case c.GCSTokenDB != nil:
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.LevelTokenDB)
dbName = c.LevelTokenDB.Path
}
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
45 changes: 31 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"`
LevelTokenDB *LevelDBStoreConfig `yaml:"level_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,26 @@ 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
var dbName string

switch {
case c.GCSTokenDB != nil:
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.LevelTokenDB)
dbName = c.LevelTokenDB.Path
}

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 +132,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
19 changes: 15 additions & 4 deletions auth_server/authn/tokendb_gcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,29 @@ import (
"github.com/cesanta/docker_auth/auth_server/api"
)

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 @@ -72,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
Loading

0 comments on commit 5dd7b10

Please sign in to comment.