Skip to content

Commit 694cf19

Browse files
authored
Merge pull request #193 from mgenov/gcs_tokens
docker_auth/github: store tokens in google cloud storage
2 parents d314c82 + 4e49efe commit 694cf19

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)