Skip to content

Commit 4e49efe

Browse files
committed
docker_auth/github: store tokens in google cloud storage
Added a new implementation of TokenDB that uses Google Cloud Storage as backend for storing of the tokens. This makes container independent from the file system of the container or from the target system and is good alternative to mounted clustered file systems to container. Docs examples are updated regarding this change as specifying token_db configuration now becomes alternate between local file and gcs.
1 parent f5bf6ae commit 4e49efe

File tree

6 files changed

+173
-17
lines changed

6 files changed

+173
-17
lines changed

auth_server/authn/github_auth.go

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,21 @@ import (
3232
)
3333

3434
type GitHubAuthConfig struct {
35-
Organization string `yaml:"organization,omitempty"`
36-
ClientId string `yaml:"client_id,omitempty"`
37-
ClientSecret string `yaml:"client_secret,omitempty"`
38-
ClientSecretFile string `yaml:"client_secret_file,omitempty"`
39-
TokenDB string `yaml:"token_db,omitempty"`
40-
HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"`
41-
RevalidateAfter time.Duration `yaml:"revalidate_after,omitempty"`
42-
GithubWebUri string `yaml:"github_web_uri,omitempty"`
43-
GithubApiUri string `yaml:"github_api_uri,omitempty"`
35+
Organization string `yaml:"organization,omitempty"`
36+
ClientId string `yaml:"client_id,omitempty"`
37+
ClientSecret string `yaml:"client_secret,omitempty"`
38+
ClientSecretFile string `yaml:"client_secret_file,omitempty"`
39+
TokenDB string `yaml:"token_db,omitempty"`
40+
GCSTokenDB *GitHubGCSStoreConfig `yaml:"gcs_token_db,omitempty"`
41+
HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"`
42+
RevalidateAfter time.Duration `yaml:"revalidate_after,omitempty"`
43+
GithubWebUri string `yaml:"github_web_uri,omitempty"`
44+
GithubApiUri string `yaml:"github_api_uri,omitempty"`
45+
}
46+
47+
type GitHubGCSStoreConfig struct {
48+
Bucket string `yaml:"bucket,omitempty"`
49+
ClientSecretFile string `yaml:"client_secret_file,omitempty"`
4450
}
4551

4652
type GitHubAuthRequest struct {
@@ -62,11 +68,20 @@ type GitHubAuth struct {
6268
}
6369

6470
func NewGitHubAuth(c *GitHubAuthConfig) (*GitHubAuth, error) {
65-
db, err := NewTokenDB(c.TokenDB)
71+
var db TokenDB
72+
var err error
73+
dbName := c.TokenDB
74+
if c.GCSTokenDB == nil {
75+
db, err = NewTokenDB(c.TokenDB)
76+
} else {
77+
db, err = NewGCSTokenDB(c.GCSTokenDB.Bucket, c.GCSTokenDB.ClientSecretFile)
78+
dbName = "GCS: " + c.GCSTokenDB.Bucket
79+
}
80+
6681
if err != nil {
6782
return nil, err
6883
}
69-
glog.Infof("GitHub auth token DB at %s", c.TokenDB)
84+
glog.Infof("GitHub auth token DB at %s", dbName)
7085
return &GitHubAuth{
7186
config: c,
7287
db: db,

auth_server/authn/tokendb.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import (
2424

2525
"golang.org/x/crypto/bcrypt"
2626

27-
"github.com/dchest/uniuri"
2827
"github.com/cesanta/glog"
28+
"github.com/dchest/uniuri"
2929
"github.com/syndtr/goleveldb/leveldb"
3030
)
3131

@@ -93,7 +93,7 @@ func (db *TokenDBImpl) GetValue(user string) (*TokenDBValue, error) {
9393
err = json.Unmarshal(valueStr, &dbv)
9494
if err != nil {
9595
glog.Errorf("bad DB value for %q (%q): %s", user, string(valueStr), err)
96-
return nil, fmt.Errorf("bad DB value", err)
96+
return nil, fmt.Errorf("bad DB value due: %v", err)
9797
}
9898
return &dbv, nil
9999
}

auth_server/authn/tokendb_gcs.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
Copyright 2017 Cesanta Software Ltd.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package authn
17+
18+
import (
19+
"encoding/json"
20+
"fmt"
21+
"time"
22+
23+
"cloud.google.com/go/storage"
24+
"github.com/cesanta/glog"
25+
"github.com/dchest/uniuri"
26+
"golang.org/x/crypto/bcrypt"
27+
"golang.org/x/net/context"
28+
"google.golang.org/api/option"
29+
)
30+
31+
// NewGCSTokenDB return a new TokenDB structure which uses Google Cloud Storage as backend. The
32+
// created DB uses file-per-user strategy and stores credentials independently for each user.
33+
//
34+
// Note: it's not recomanded bucket to be shared with other apps or services
35+
func NewGCSTokenDB(bucket, clientSecretFile string) (TokenDB, error) {
36+
gcs, err := storage.NewClient(context.Background(), option.WithServiceAccountFile(clientSecretFile))
37+
return &gcsTokenDB{gcs, bucket}, err
38+
}
39+
40+
type gcsTokenDB struct {
41+
gcs *storage.Client
42+
bucket string
43+
}
44+
45+
// GetValue gets token value associated with the provided user. Each user
46+
// in the bucket is having it's own file for tokens and it's recomanded bucket
47+
// to not be shared with other apps
48+
func (db *gcsTokenDB) GetValue(user string) (*TokenDBValue, error) {
49+
rd, err := db.gcs.Bucket(db.bucket).Object(user).NewReader(context.Background())
50+
if err == storage.ErrObjectNotExist {
51+
return nil, nil
52+
}
53+
if err != nil {
54+
return nil, fmt.Errorf("could not retrieved token for user '%s' due: %v", user, err)
55+
}
56+
defer rd.Close()
57+
58+
var dbv TokenDBValue
59+
if err := json.NewDecoder(rd).Decode(&dbv); err != nil {
60+
glog.Errorf("bad DB value for %q: %v", user, err)
61+
return nil, fmt.Errorf("could not read token for user '%s' due: %v", user, err)
62+
}
63+
64+
return &dbv, nil
65+
}
66+
67+
// StoreToken stores token in the GCS file in a JSON format. Note that separate file is
68+
// used for each user
69+
func (db *gcsTokenDB) StoreToken(user string, v *TokenDBValue, updatePassword bool) (dp string, err error) {
70+
if updatePassword {
71+
dp = uniuri.New()
72+
dph, _ := bcrypt.GenerateFromPassword([]byte(dp), bcrypt.DefaultCost)
73+
v.DockerPassword = string(dph)
74+
}
75+
76+
wr := db.gcs.Bucket(db.bucket).Object(user).NewWriter(context.Background())
77+
78+
if err := json.NewEncoder(wr).Encode(v); err != nil {
79+
glog.Errorf("failed to set token data for %s: %s", user, err)
80+
return "", fmt.Errorf("failed to set token data for %s due: %v", user, err)
81+
}
82+
83+
err = wr.Close()
84+
return
85+
}
86+
87+
// ValidateToken verifies whether the provided token passed as password field
88+
// is still valid, e.g available and not expired
89+
func (db *gcsTokenDB) ValidateToken(user string, password PasswordString) error {
90+
dbv, err := db.GetValue(user)
91+
if err != nil {
92+
return err
93+
}
94+
if dbv == nil {
95+
return NoMatch
96+
}
97+
98+
if bcrypt.CompareHashAndPassword([]byte(dbv.DockerPassword), []byte(password)) != nil {
99+
return WrongPass
100+
}
101+
if time.Now().After(dbv.ValidUntil) {
102+
return ExpiredToken
103+
}
104+
105+
return nil
106+
}
107+
108+
// DeleteToken deletes the GCS file that is associated with the provided user.
109+
func (db *gcsTokenDB) DeleteToken(user string) error {
110+
ctx := context.Background()
111+
err := db.gcs.Bucket(db.bucket).Object(user).Delete(ctx)
112+
if err == storage.ErrObjectNotExist {
113+
return nil
114+
}
115+
return err
116+
}
117+
118+
// Close is a nop operation for this db
119+
func (db *gcsTokenDB) Close() error {
120+
return nil
121+
}

auth_server/server/config.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,12 @@ func validate(c *Config) error {
120120
}
121121
ghac.ClientSecret = strings.TrimSpace(string(contents))
122122
}
123-
if ghac.ClientId == "" || ghac.ClientSecret == "" || ghac.TokenDB == "" {
124-
return errors.New("github_auth.{client_id,client_secret,token_db} are required.")
123+
if ghac.ClientId == "" || ghac.ClientSecret == "" || (ghac.TokenDB == "" && ghac.GCSTokenDB == nil) {
124+
return errors.New("github_auth.{client_id,client_secret,token_db} are required")
125+
}
126+
127+
if ghac.ClientId == "" || ghac.ClientSecret == "" || (ghac.GCSTokenDB != nil && (ghac.GCSTokenDB.Bucket == "" || ghac.GCSTokenDB.ClientSecretFile == "")) {
128+
return errors.New("github_auth.{client_id,client_secret,gcs_token_db{bucket,client_secret_file}} are required")
125129
}
126130
if ghac.HTTPTimeout <= 0 {
127131
ghac.HTTPTimeout = time.Duration(10 * time.Second)

auth_server/vendor/vendor.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
"comment": "",
33
"ignore": "",
44
"package": [
5+
{
6+
"checksumSHA1": "0ot8Hk23WGrT0lE2BmQXeqe4bRo=",
7+
"path": "cloud.google.com/go/storage",
8+
"revision": "2b74e2e25316cfd9e46b74e444cdeceb78786dc5",
9+
"revisionTime": "2017-08-20T12:51:33Z"
10+
},
511
{
612
"checksumSHA1": "CujWu7+PWlZSX5+zAPJH91O5AVQ=",
713
"origin": "github.com/docker/distribution/vendor/github.com/Sirupsen/logrus",
@@ -397,11 +403,17 @@
397403
"revisionTime": "2017-03-21T17:14:25Z"
398404
},
399405
{
400-
"checksumSHA1": "AK65RmsGNBl0/e11OVrf2mW78gU=",
406+
"checksumSHA1": "1WoWjPiwUEFahi5xz29FRMtd8sA=",
401407
"path": "golang.org/x/sys/unix",
402408
"revision": "493114f68206f85e7e333beccfabc11e98cba8dd",
403409
"revisionTime": "2017-03-31T21:25:38Z"
404410
},
411+
{
412+
"checksumSHA1": "RpAaByicZuXzN7bReX8YXKf8gP0=",
413+
"path": "google.golang.org/api/option",
414+
"revision": "955a3ae66b420f3adc0d77da3d8ed767a74e2b4f",
415+
"revisionTime": "2017-09-01T00:04:07Z"
416+
},
405417
{
406418
"checksumSHA1": "fRERF7JFq7KYgM9I48onMlEgFm4=",
407419
"path": "gopkg.in/asn1-ber.v1",

examples/reference.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,12 @@ github_auth:
101101
# want to have sensitive information checked in.
102102
# client_secret: "verysecret"
103103
client_secret_file: "/path/to/client_secret.txt"
104-
# Where to store server tokens. Required.
104+
# Either token_db file for storing of server tokens.
105105
token_db: "/somewhere/to/put/github_tokens.ldb"
106+
# or google cloud storage for storing of the sensitive information.
107+
gcs_token_db:
108+
bucket: "tokenBucket"
109+
client_secret_file: "/path/to/client_secret.json"
106110
# How long to wait when talking to GitHub servers. Optional.
107111
http_timeout: "10s"
108112
# How long to wait before revalidating the GitHub token. Optional.

0 commit comments

Comments
 (0)