From 3c00d74c448f77dffb03c509a266305a349f47a4 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Wed, 10 Dec 2014 09:05:47 -0800 Subject: [PATCH 1/4] Update godo to 0.5.0 Signed-off-by: Ben Firshman --- Godeps/Godeps.json | 4 ++-- .../src/github.com/digitalocean/godo/droplets.go | 14 +++++++++----- .../src/github.com/digitalocean/godo/godo_test.go | 5 ++++- .../src/github.com/digitalocean/godo/links.go | 2 +- .../src/github.com/digitalocean/godo/regions.go | 1 + 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index fd756f3747..929ef9997d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -19,8 +19,8 @@ }, { "ImportPath": "github.com/digitalocean/godo", - "Comment": "v0.4.0-1-ga7066ec", - "Rev": "a7066ece1ff635d9fc12d123cd8a22aaf7b0062e" + "Comment": "v0.5.0", + "Rev": "5478aae80694de1d2d0e02c386bbedd201266234" }, { "ImportPath": "github.com/docker/docker/api", diff --git a/Godeps/_workspace/src/github.com/digitalocean/godo/droplets.go b/Godeps/_workspace/src/github.com/digitalocean/godo/droplets.go index 3e29078e4c..5b73cdc734 100644 --- a/Godeps/_workspace/src/github.com/digitalocean/godo/droplets.go +++ b/Godeps/_workspace/src/github.com/digitalocean/godo/droplets.go @@ -57,11 +57,15 @@ type dropletsRoot struct { // DropletCreateRequest represents a request to create a droplet. type DropletCreateRequest struct { - Name string `json:"name"` - Region string `json:"region"` - Size string `json:"size"` - Image string `json:"image"` - SSHKeys []interface{} `json:"ssh_keys"` + Name string `json:"name"` + Region string `json:"region"` + Size string `json:"size"` + Image string `json:"image"` + SSHKeys []interface{} `json:"ssh_keys"` + Backups bool `json:"backups"` + IPv6 bool `json:"ipv6"` + PrivateNetworking bool `json:"private_networking"` + UserData string `json:"user_data"` } func (d DropletCreateRequest) String() string { diff --git a/Godeps/_workspace/src/github.com/digitalocean/godo/godo_test.go b/Godeps/_workspace/src/github.com/digitalocean/godo/godo_test.go index 3cae791ea3..8b71451924 100644 --- a/Godeps/_workspace/src/github.com/digitalocean/godo/godo_test.go +++ b/Godeps/_workspace/src/github.com/digitalocean/godo/godo_test.go @@ -78,7 +78,10 @@ func TestNewRequest(t *testing.T) { c := NewClient(nil) inURL, outURL := "/foo", defaultBaseURL+"foo" - inBody, outBody := &DropletCreateRequest{Name: "l"}, `{"name":"l","region":"","size":"","image":"","ssh_keys":null}`+"\n" + inBody, outBody := &DropletCreateRequest{Name: "l"}, + `{"name":"l","region":"","size":"","image":"",`+ + `"ssh_keys":null,"backups":false,"ipv6":false,`+ + `"private_networking":false,"user_data":""}`+"\n" req, _ := c.NewRequest("GET", inURL, inBody) // test relative URL was expanded diff --git a/Godeps/_workspace/src/github.com/digitalocean/godo/links.go b/Godeps/_workspace/src/github.com/digitalocean/godo/links.go index fb220b8d67..64db34cb70 100644 --- a/Godeps/_workspace/src/github.com/digitalocean/godo/links.go +++ b/Godeps/_workspace/src/github.com/digitalocean/godo/links.go @@ -51,7 +51,7 @@ func (p *Pages) current() (int, error) { // IsLastPage returns true if the current page is the last func (l *Links) IsLastPage() bool { - if l == nil { + if l.Pages == nil { return true } return l.Pages.isLast() diff --git a/Godeps/_workspace/src/github.com/digitalocean/godo/regions.go b/Godeps/_workspace/src/github.com/digitalocean/godo/regions.go index 6be3ba16d0..63977fc6d0 100644 --- a/Godeps/_workspace/src/github.com/digitalocean/godo/regions.go +++ b/Godeps/_workspace/src/github.com/digitalocean/godo/regions.go @@ -19,6 +19,7 @@ type Region struct { Name string `json:"name,omitempty"` Sizes []string `json:"sizes,omitempty"` Available bool `json:"available,omitempty` + Features []string `json:"features,omitempty` } type regionsRoot struct { From b72822ff9d3964ea710397384fdf515106098da1 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Wed, 10 Dec 2014 09:06:10 -0800 Subject: [PATCH 2/4] Update goauth2 Signed-off-by: Ben Firshman --- Godeps/Godeps.json | 4 +- .../p/goauth2/oauth/example/oauthreq.go | 2 +- .../code.google.com/p/goauth2/oauth/oauth.go | 174 +++++++++++++----- .../p/goauth2/oauth/oauth_test.go | 61 +++++- 4 files changed, 189 insertions(+), 52 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 929ef9997d..12c1fe3d0b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -4,8 +4,8 @@ "Deps": [ { "ImportPath": "code.google.com/p/goauth2/oauth", - "Comment": "weekly-44", - "Rev": "696c088491246273e92442aa66b5281e5bb34faa" + "Comment": "weekly-56", + "Rev": "afe77d958c701557ec5dc56f6936fcc194d15520" }, { "ImportPath": "github.com/MSOpenTech/azure-sdk-for-go", diff --git a/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go b/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go index 0bedc754bc..f9651bd090 100644 --- a/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go +++ b/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go @@ -67,7 +67,7 @@ func main() { // Get an authorization code from the data provider. // ("Please ask the user if I can access this resource.") url := config.AuthCodeURL("") - fmt.Println("Visit this URL to get a code, then run again with -code=YOUR_CODE\n") + fmt.Print("Visit this URL to get a code, then run again with -code=YOUR_CODE\n\n") fmt.Println(url) return } diff --git a/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go b/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go index eb3ebe4026..6f1b6c1888 100644 --- a/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go +++ b/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// The oauth package provides support for making -// OAuth2-authenticated HTTP requests. +// Package oauth supports making OAuth2-authenticated HTTP requests. // // Example usage: // @@ -39,15 +38,23 @@ package oauth import ( "encoding/json" + "errors" + "fmt" + "io" "io/ioutil" "mime" "net/http" "net/url" "os" + "strconv" "strings" + "sync" "time" ) +// OAuthError is the error type returned by many operations. +// +// In retrospect it should not exist. Don't depend on it. type OAuthError struct { prefix string msg string @@ -123,7 +130,16 @@ type Config struct { // TokenCache allows tokens to be cached for subsequent requests. TokenCache Cache - AccessType string // Optional, "online" (default) or "offline", no refresh token if "online" + // AccessType is an OAuth extension that gets sent as the + // "access_type" field in the URL from AuthCodeURL. + // See https://developers.google.com/accounts/docs/OAuth2WebServer. + // It may be "online" (the default) or "offline". + // If your application needs to refresh access tokens when the + // user is not present at the browser, then use offline. This + // will result in your application obtaining a refresh token + // the first time your application exchanges an authorization + // code for a user. + AccessType string // ApprovalPrompt indicates whether the user should be // re-prompted for consent. If set to "auto" (default) the @@ -140,11 +156,20 @@ type Config struct { type Token struct { AccessToken string RefreshToken string - Expiry time.Time // If zero the token has no (known) expiry time. - Extra map[string]string // May be nil. + Expiry time.Time // If zero the token has no (known) expiry time. + + // Extra optionally contains extra metadata from the server + // when updating a token. The only current key that may be + // populated is "id_token". It may be nil and will be + // initialized as needed. + Extra map[string]string } +// Expired reports whether the token has expired or is invalid. func (t *Token) Expired() bool { + if t.AccessToken == "" { + return true + } if t.Expiry.IsZero() { return false } @@ -165,6 +190,9 @@ type Transport struct { *Config *Token + // mu guards modifying the token. + mu sync.Mutex + // Transport is the HTTP transport to use when making requests. // It will default to http.DefaultTransport if nil. // (It should never be an oauth.Transport.) @@ -193,11 +221,11 @@ func (c *Config) AuthCodeURL(state string) string { q := url.Values{ "response_type": {"code"}, "client_id": {c.ClientId}, - "redirect_uri": {c.RedirectURL}, - "scope": {c.Scope}, - "state": {state}, - "access_type": {c.AccessType}, - "approval_prompt": {c.ApprovalPrompt}, + "state": condVal(state), + "scope": condVal(c.Scope), + "redirect_uri": condVal(c.RedirectURL), + "access_type": condVal(c.AccessType), + "approval_prompt": condVal(c.ApprovalPrompt), }.Encode() if url_.RawQuery == "" { url_.RawQuery = q @@ -207,6 +235,13 @@ func (c *Config) AuthCodeURL(state string) string { return url_.String() } +func condVal(v string) []string { + if v == "" { + return nil + } + return []string{v} +} + // Exchange takes a code and gets access Token from the remote server. func (t *Transport) Exchange(code string) (*Token, error) { if t.Config == nil { @@ -247,35 +282,48 @@ func (t *Transport) Exchange(code string) (*Token, error) { // If the Token is invalid callers should expect HTTP-level errors, // as indicated by the Response's StatusCode. func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + accessToken, err := t.getAccessToken() + if err != nil { + return nil, err + } + // To set the Authorization header, we must make a copy of the Request + // so that we don't modify the Request we were given. + // This is required by the specification of http.RoundTripper. + req = cloneRequest(req) + req.Header.Set("Authorization", "Bearer "+accessToken) + + // Make the HTTP request. + return t.transport().RoundTrip(req) +} + +func (t *Transport) getAccessToken() (string, error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.Token == nil { if t.Config == nil { - return nil, OAuthError{"RoundTrip", "no Config supplied"} + return "", OAuthError{"RoundTrip", "no Config supplied"} } if t.TokenCache == nil { - return nil, OAuthError{"RoundTrip", "no Token supplied"} + return "", OAuthError{"RoundTrip", "no Token supplied"} } var err error t.Token, err = t.TokenCache.Token() if err != nil { - return nil, err + return "", err } } // Refresh the Token if it has expired. if t.Expired() { if err := t.Refresh(); err != nil { - return nil, err + return "", err } } - - // To set the Authorization header, we must make a copy of the Request - // so that we don't modify the Request we were given. - // This is required by the specification of http.RoundTripper. - req = cloneRequest(req) - req.Header.Set("Authorization", "Bearer "+t.AccessToken) - - // Make the HTTP request. - return t.transport().RoundTrip(req) + if t.AccessToken == "" { + return "", errors.New("no access token obtained from refresh") + } + return t.AccessToken, nil } // cloneRequest returns a clone of the provided *http.Request. @@ -317,38 +365,80 @@ func (t *Transport) Refresh() error { return nil } +// AuthenticateClient gets an access Token using the client_credentials grant +// type. +func (t *Transport) AuthenticateClient() error { + if t.Config == nil { + return OAuthError{"Exchange", "no Config supplied"} + } + if t.Token == nil { + t.Token = &Token{} + } + return t.updateToken(t.Token, url.Values{"grant_type": {"client_credentials"}}) +} + +// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL +// implements the OAuth2 spec correctly +// See https://code.google.com/p/goauth2/issues/detail?id=31 for background. +// In summary: +// - Reddit only accepts client secret in the Authorization header +// - Dropbox accepts either it in URL param or Auth header, but not both. +// - Google only accepts URL param (not spec compliant?), not Auth header +func providerAuthHeaderWorks(tokenURL string) bool { + if strings.HasPrefix(tokenURL, "https://accounts.google.com/") || + strings.HasPrefix(tokenURL, "https://github.com/") || + strings.HasPrefix(tokenURL, "https://api.instagram.com/") || + strings.HasPrefix(tokenURL, "https://www.douban.com/") { + // Some sites fail to implement the OAuth2 spec fully. + return false + } + + // Assume the provider implements the spec properly + // otherwise. We can add more exceptions as they're + // discovered. We will _not_ be adding configurable hooks + // to this package to let users select server bugs. + return true +} + +// updateToken mutates both tok and v. func (t *Transport) updateToken(tok *Token, v url.Values) error { v.Set("client_id", t.ClientId) - v.Set("client_secret", t.ClientSecret) + bustedAuth := !providerAuthHeaderWorks(t.TokenURL) + if bustedAuth { + v.Set("client_secret", t.ClientSecret) + } client := &http.Client{Transport: t.transport()} req, err := http.NewRequest("POST", t.TokenURL, strings.NewReader(v.Encode())) if err != nil { return err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.SetBasicAuth(t.ClientId, t.ClientSecret) + if !bustedAuth { + req.SetBasicAuth(t.ClientId, t.ClientSecret) + } r, err := client.Do(req) if err != nil { return err } defer r.Body.Close() if r.StatusCode != 200 { - return OAuthError{"updateToken", r.Status} + return OAuthError{"updateToken", "Unexpected HTTP status " + r.Status} } var b struct { - Access string `json:"access_token"` - Refresh string `json:"refresh_token"` - ExpiresIn time.Duration `json:"expires_in"` - Id string `json:"id_token"` + Access string `json:"access_token"` + Refresh string `json:"refresh_token"` + ExpiresIn int64 `json:"expires_in"` // seconds + Id string `json:"id_token"` + } + + body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20)) + if err != nil { + return err } content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type")) switch content { case "application/x-www-form-urlencoded", "text/plain": - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return err - } vals, err := url.ParseQuery(string(body)) if err != nil { return err @@ -356,25 +446,25 @@ func (t *Transport) updateToken(tok *Token, v url.Values) error { b.Access = vals.Get("access_token") b.Refresh = vals.Get("refresh_token") - b.ExpiresIn, _ = time.ParseDuration(vals.Get("expires_in") + "s") + b.ExpiresIn, _ = strconv.ParseInt(vals.Get("expires_in"), 10, 64) b.Id = vals.Get("id_token") default: - if err = json.NewDecoder(r.Body).Decode(&b); err != nil { - return err + if err = json.Unmarshal(body, &b); err != nil { + return fmt.Errorf("got bad response from server: %q", body) } - // The JSON parser treats the unitless ExpiresIn like 'ns' instead of 's' as above, - // so compensate here. - b.ExpiresIn *= time.Second + } + if b.Access == "" { + return errors.New("received empty access token from authorization server") } tok.AccessToken = b.Access // Don't overwrite `RefreshToken` with an empty value - if len(b.Refresh) > 0 { + if b.Refresh != "" { tok.RefreshToken = b.Refresh } if b.ExpiresIn == 0 { tok.Expiry = time.Time{} } else { - tok.Expiry = time.Now().Add(b.ExpiresIn) + tok.Expiry = time.Now().Add(time.Duration(b.ExpiresIn) * time.Second) } if b.Id != "" { if tok.Extra == nil { diff --git a/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go b/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go index 370d6a5f13..9fadc61990 100644 --- a/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go +++ b/Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go @@ -23,7 +23,7 @@ var requests = []struct { }{ { path: "/token", - query: "grant_type=authorization_code&code=c0d3&client_id=cl13nt1d&client_secret=s3cr3t", + query: "grant_type=authorization_code&code=c0d3&client_id=cl13nt1d", contenttype: "application/json", auth: "Basic Y2wxM250MWQ6czNjcjN0", body: ` @@ -38,7 +38,7 @@ var requests = []struct { {path: "/secure", auth: "Bearer token1", body: "first payload"}, { path: "/token", - query: "grant_type=refresh_token&refresh_token=refreshtoken1&client_id=cl13nt1d&client_secret=s3cr3t", + query: "grant_type=refresh_token&refresh_token=refreshtoken1&client_id=cl13nt1d", contenttype: "application/json", auth: "Basic Y2wxM250MWQ6czNjcjN0", body: ` @@ -53,12 +53,25 @@ var requests = []struct { {path: "/secure", auth: "Bearer token2", body: "second payload"}, { path: "/token", - query: "grant_type=refresh_token&refresh_token=refreshtoken2&client_id=cl13nt1d&client_secret=s3cr3t", + query: "grant_type=refresh_token&refresh_token=refreshtoken2&client_id=cl13nt1d", contenttype: "application/x-www-form-urlencoded", body: "access_token=token3&refresh_token=refreshtoken3&id_token=idtoken3&expires_in=3600", auth: "Basic Y2wxM250MWQ6czNjcjN0", }, {path: "/secure", auth: "Bearer token3", body: "third payload"}, + { + path: "/token", + query: "grant_type=client_credentials&client_id=cl13nt1d", + contenttype: "application/json", + auth: "Basic Y2wxM250MWQ6czNjcjN0", + body: ` + { + "access_token":"token4", + "expires_in":3600 + } + `, + }, + {path: "/secure", auth: "Bearer token4", body: "fourth payload"}, } func TestOAuth(t *testing.T) { @@ -134,6 +147,18 @@ func TestOAuth(t *testing.T) { } checkBody(t, resp, "third payload") checkToken(t, transport.Token, "token3", "refreshtoken3", "idtoken3") + + transport.Token = &Token{} + err = transport.AuthenticateClient() + if err != nil { + t.Fatalf("AuthenticateClient: %v", err) + } + checkToken(t, transport.Token, "token4", "", "") + resp, err = c.Get(server.URL + "/secure") + if err != nil { + t.Fatalf("Get: %v", err) + } + checkBody(t, resp, "fourth payload") } func checkToken(t *testing.T, tok *Token, access, refresh, id string) { @@ -146,16 +171,21 @@ func checkToken(t *testing.T, tok *Token, access, refresh, id string) { if g, w := tok.Extra["id_token"], id; g != w { t.Errorf("Extra['id_token'] = %q, want %q", g, w) } - exp := tok.Expiry.Sub(time.Now()) - if (time.Hour-time.Second) > exp || exp > time.Hour { - t.Errorf("Expiry = %v, want ~1 hour", exp) + if tok.Expiry.IsZero() { + t.Errorf("Expiry is zero; want ~1 hour") + } else { + exp := tok.Expiry.Sub(time.Now()) + const slop = 3 * time.Second // time moving during test + if (time.Hour-slop) > exp || exp > time.Hour { + t.Errorf("Expiry = %v, want ~1 hour", exp) + } } } func checkBody(t *testing.T, r *http.Response, body string) { b, err := ioutil.ReadAll(r.Body) if err != nil { - t.Error("reading reponse body: %v, want %q", err, body) + t.Errorf("reading reponse body: %v, want %q", err, body) } if g, w := string(b), body; g != w { t.Errorf("request body mismatch: got %q, want %q", g, w) @@ -187,3 +217,20 @@ func TestCachePermissions(t *testing.T) { t.Errorf("Created cache file has mode %#o, want non-accessible to group+other", fi.Mode()) } } + +func TestTokenExpired(t *testing.T) { + tests := []struct { + token Token + expired bool + }{ + {Token{AccessToken: "foo"}, false}, + {Token{AccessToken: ""}, true}, + {Token{AccessToken: "foo", Expiry: time.Now().Add(-1 * time.Hour)}, true}, + {Token{AccessToken: "foo", Expiry: time.Now().Add(1 * time.Hour)}, false}, + } + for _, tt := range tests { + if got := tt.token.Expired(); got != tt.expired { + t.Errorf("token %+v Expired = %v; want %v", tt.token, got, !got) + } + } +} From 24574d513ae5ad1a5f826f51d0e0f1e566916fa4 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Wed, 10 Dec 2014 09:08:36 -0800 Subject: [PATCH 3/4] Update libtrust Signed-off-by: Ben Firshman --- Godeps/Godeps.json | 2 +- .../src/github.com/docker/libtrust/ec_key.go | 13 +-- .../src/github.com/docker/libtrust/filter.go | 24 +++--- .../github.com/docker/libtrust/filter_test.go | 6 +- .../docker/libtrust/key_files_test.go | 4 +- .../github.com/docker/libtrust/key_test.go | 80 +++++++++++++++++++ .../src/github.com/docker/libtrust/rsa_key.go | 13 +-- .../libtrust/trustgraph/statement_test.go | 4 +- .../src/github.com/docker/libtrust/util.go | 16 ++++ .../github.com/docker/libtrust/util_test.go | 23 ++++++ 10 files changed, 147 insertions(+), 38 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/docker/libtrust/key_test.go create mode 100644 Godeps/_workspace/src/github.com/docker/libtrust/util_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 12c1fe3d0b..ad41cdea86 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -109,7 +109,7 @@ }, { "ImportPath": "github.com/docker/libtrust", - "Rev": "d273ef2565cab2fd9832b8a3d6320e5ab39ef9db" + "Rev": "6b7834910dcbb3021adc193411d01f65595445fb" }, { "ImportPath": "github.com/google/go-querystring/query", diff --git a/Godeps/_workspace/src/github.com/docker/libtrust/ec_key.go b/Godeps/_workspace/src/github.com/docker/libtrust/ec_key.go index c7ac6844cf..00bbe4b3ca 100644 --- a/Godeps/_workspace/src/github.com/docker/libtrust/ec_key.go +++ b/Godeps/_workspace/src/github.com/docker/libtrust/ec_key.go @@ -55,16 +55,7 @@ func (k *ecPublicKey) CurveName() string { // KeyID returns a distinct identifier which is unique to this Public Key. func (k *ecPublicKey) KeyID() string { - // Generate and return a libtrust fingerprint of the EC public key. - // For an EC key this should be: - // SHA256("EC"+curveName+bytes(X)+bytes(Y)) - // Then truncated to 240 bits and encoded into 12 base32 groups like so: - // ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP - hasher := crypto.SHA256.New() - hasher.Write([]byte(k.KeyType() + k.CurveName())) - hasher.Write(k.X.Bytes()) - hasher.Write(k.Y.Bytes()) - return keyIDEncode(hasher.Sum(nil)[:30]) + return keyIDFromCryptoKey(k) } func (k *ecPublicKey) String() string { @@ -151,7 +142,7 @@ func (k *ecPublicKey) PEMBlock() (*pem.Block, error) { if err != nil { return nil, fmt.Errorf("unable to serialize EC PublicKey to DER-encoded PKIX format: %s", err) } - k.extended["keyID"] = k.KeyID() // For display purposes. + k.extended["kid"] = k.KeyID() // For display purposes. return createPemBlock("PUBLIC KEY", derBytes, k.extended) } diff --git a/Godeps/_workspace/src/github.com/docker/libtrust/filter.go b/Godeps/_workspace/src/github.com/docker/libtrust/filter.go index 945852afc8..5b2b4fca6f 100644 --- a/Godeps/_workspace/src/github.com/docker/libtrust/filter.go +++ b/Godeps/_workspace/src/github.com/docker/libtrust/filter.go @@ -11,9 +11,21 @@ func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKe filtered := make([]PublicKey, 0, len(keys)) for _, pubKey := range keys { - hosts, ok := pubKey.GetExtendedField("hosts").([]interface{}) + var hosts []string + switch v := pubKey.GetExtendedField("hosts").(type) { + case []string: + hosts = v + case []interface{}: + for _, value := range v { + h, ok := value.(string) + if !ok { + continue + } + hosts = append(hosts, h) + } + } - if !ok || (ok && len(hosts) == 0) { + if len(hosts) == 0 { if includeEmpty { filtered = append(filtered, pubKey) } @@ -21,12 +33,7 @@ func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKe } // Check if any hosts match pattern - for _, hostVal := range hosts { - hostPattern, ok := hostVal.(string) - if !ok { - continue - } - + for _, hostPattern := range hosts { match, err := filepath.Match(hostPattern, host) if err != nil { return nil, err @@ -37,7 +44,6 @@ func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKe continue } } - } return filtered, nil diff --git a/Godeps/_workspace/src/github.com/docker/libtrust/filter_test.go b/Godeps/_workspace/src/github.com/docker/libtrust/filter_test.go index b24e3322e6..997e554c04 100644 --- a/Godeps/_workspace/src/github.com/docker/libtrust/filter_test.go +++ b/Godeps/_workspace/src/github.com/docker/libtrust/filter_test.go @@ -27,6 +27,8 @@ func TestFilter(t *testing.T) { t.Fatal(err) } + // we use both []interface{} and []string here because jwt uses + // []interface{} format, while PEM uses []string switch { case i == 0: // Don't add entries for this key, key 0. @@ -36,10 +38,10 @@ func TestFilter(t *testing.T) { key.AddExtendedField("hosts", []interface{}{"*.even.example.com"}) case i == 7: // Should catch only the last key, and make it match any hostname. - key.AddExtendedField("hosts", []interface{}{"*"}) + key.AddExtendedField("hosts", []string{"*"}) default: // should catch keys 1, 3, 5. - key.AddExtendedField("hosts", []interface{}{"*.example.com"}) + key.AddExtendedField("hosts", []string{"*.example.com"}) } keys = append(keys, key) diff --git a/Godeps/_workspace/src/github.com/docker/libtrust/key_files_test.go b/Godeps/_workspace/src/github.com/docker/libtrust/key_files_test.go index 66c71dd43f..57e691f2ed 100644 --- a/Godeps/_workspace/src/github.com/docker/libtrust/key_files_test.go +++ b/Godeps/_workspace/src/github.com/docker/libtrust/key_files_test.go @@ -138,7 +138,7 @@ func testTrustedHostKeysFile(t *testing.T, trustedHostKeysFilename string) { } for addr, hostKey := range trustedHostKeysMapping { - t.Logf("Host Address: %s\n", addr) + t.Logf("Host Address: %d\n", addr) t.Logf("Host Key: %s\n\n", hostKey) } @@ -160,7 +160,7 @@ func testTrustedHostKeysFile(t *testing.T, trustedHostKeysFilename string) { } for addr, hostKey := range trustedHostKeysMapping { - t.Logf("Host Address: %s\n", addr) + t.Logf("Host Address: %d\n", addr) t.Logf("Host Key: %s\n\n", hostKey) } diff --git a/Godeps/_workspace/src/github.com/docker/libtrust/key_test.go b/Godeps/_workspace/src/github.com/docker/libtrust/key_test.go new file mode 100644 index 0000000000..f6c59cc42b --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libtrust/key_test.go @@ -0,0 +1,80 @@ +package libtrust + +import ( + "testing" +) + +type generateFunc func() (PrivateKey, error) + +func runGenerateBench(b *testing.B, f generateFunc, name string) { + for i := 0; i < b.N; i++ { + _, err := f() + if err != nil { + b.Fatalf("Error generating %s: %s", name, err) + } + } +} + +func runFingerprintBench(b *testing.B, f generateFunc, name string) { + b.StopTimer() + // Don't count this relatively slow generation call. + key, err := f() + if err != nil { + b.Fatalf("Error generating %s: %s", name, err) + } + b.StartTimer() + + for i := 0; i < b.N; i++ { + if key.KeyID() == "" { + b.Fatalf("Error generating key ID for %s", name) + } + } +} + +func BenchmarkECP256Generate(b *testing.B) { + runGenerateBench(b, GenerateECP256PrivateKey, "P256") +} + +func BenchmarkECP384Generate(b *testing.B) { + runGenerateBench(b, GenerateECP384PrivateKey, "P384") +} + +func BenchmarkECP521Generate(b *testing.B) { + runGenerateBench(b, GenerateECP521PrivateKey, "P521") +} + +func BenchmarkRSA2048Generate(b *testing.B) { + runGenerateBench(b, GenerateRSA2048PrivateKey, "RSA2048") +} + +func BenchmarkRSA3072Generate(b *testing.B) { + runGenerateBench(b, GenerateRSA3072PrivateKey, "RSA3072") +} + +func BenchmarkRSA4096Generate(b *testing.B) { + runGenerateBench(b, GenerateRSA4096PrivateKey, "RSA4096") +} + +func BenchmarkECP256Fingerprint(b *testing.B) { + runFingerprintBench(b, GenerateECP256PrivateKey, "P256") +} + +func BenchmarkECP384Fingerprint(b *testing.B) { + runFingerprintBench(b, GenerateECP384PrivateKey, "P384") +} + +func BenchmarkECP521Fingerprint(b *testing.B) { + runFingerprintBench(b, GenerateECP521PrivateKey, "P521") +} + +func BenchmarkRSA2048Fingerprint(b *testing.B) { + runFingerprintBench(b, GenerateRSA2048PrivateKey, "RSA2048") +} + +func BenchmarkRSA3072Fingerprint(b *testing.B) { + runFingerprintBench(b, GenerateRSA3072PrivateKey, "RSA3072") +} + +func BenchmarkRSA4096Fingerprint(b *testing.B) { + runFingerprintBench(b, GenerateRSA4096PrivateKey, "RSA4096") +} diff --git a/Godeps/_workspace/src/github.com/docker/libtrust/rsa_key.go b/Godeps/_workspace/src/github.com/docker/libtrust/rsa_key.go index 45463039d2..dac4cacf20 100644 --- a/Godeps/_workspace/src/github.com/docker/libtrust/rsa_key.go +++ b/Godeps/_workspace/src/github.com/docker/libtrust/rsa_key.go @@ -34,16 +34,7 @@ func (k *rsaPublicKey) KeyType() string { // KeyID returns a distinct identifier which is unique to this Public Key. func (k *rsaPublicKey) KeyID() string { - // Generate and return a 'libtrust' fingerprint of the RSA public key. - // For an RSA key this should be: - // SHA256("RSA"+bytes(N)+bytes(E)) - // Then truncated to 240 bits and encoded into 12 base32 groups like so: - // ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP - hasher := crypto.SHA256.New() - hasher.Write([]byte(k.KeyType())) - hasher.Write(k.N.Bytes()) - hasher.Write(serializeRSAPublicExponentParam(k.E)) - return keyIDEncode(hasher.Sum(nil)[:30]) + return keyIDFromCryptoKey(k) } func (k *rsaPublicKey) String() string { @@ -108,7 +99,7 @@ func (k *rsaPublicKey) PEMBlock() (*pem.Block, error) { if err != nil { return nil, fmt.Errorf("unable to serialize RSA PublicKey to DER-encoded PKIX format: %s", err) } - k.extended["keyID"] = k.KeyID() // For display purposes. + k.extended["kid"] = k.KeyID() // For display purposes. return createPemBlock("PUBLIC KEY", derBytes, k.extended) } diff --git a/Godeps/_workspace/src/github.com/docker/libtrust/trustgraph/statement_test.go b/Godeps/_workspace/src/github.com/docker/libtrust/trustgraph/statement_test.go index d9c3c1a1ea..e509468659 100644 --- a/Godeps/_workspace/src/github.com/docker/libtrust/trustgraph/statement_test.go +++ b/Godeps/_workspace/src/github.com/docker/libtrust/trustgraph/statement_test.go @@ -201,7 +201,7 @@ func TestCollapseGrants(t *testing.T) { collapsedGrants, expiration, err := CollapseStatements(statements, false) if len(collapsedGrants) != 12 { - t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %s", 12, len(collapsedGrants)) + t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %d", 12, len(collapsedGrants)) } if expiration.After(time.Now().Add(time.Hour*5)) || expiration.Before(time.Now()) { t.Fatalf("Unexpected expiration time: %s", expiration.String()) @@ -261,7 +261,7 @@ func TestCollapseGrants(t *testing.T) { collapsedGrants, expiration, err = CollapseStatements(statements, false) if len(collapsedGrants) != 12 { - t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %s", 12, len(collapsedGrants)) + t.Fatalf("Unexpected number of grants\n\tExpected: %d\n\tActual: %d", 12, len(collapsedGrants)) } if expiration.After(time.Now().Add(time.Hour*5)) || expiration.Before(time.Now()) { t.Fatalf("Unexpected expiration time: %s", expiration.String()) diff --git a/Godeps/_workspace/src/github.com/docker/libtrust/util.go b/Godeps/_workspace/src/github.com/docker/libtrust/util.go index 3b2fac95b1..4d5a6200a8 100644 --- a/Godeps/_workspace/src/github.com/docker/libtrust/util.go +++ b/Godeps/_workspace/src/github.com/docker/libtrust/util.go @@ -2,6 +2,7 @@ package libtrust import ( "bytes" + "crypto" "crypto/elliptic" "crypto/x509" "encoding/base32" @@ -52,6 +53,21 @@ func keyIDEncode(b []byte) string { return buf.String() } +func keyIDFromCryptoKey(pubKey PublicKey) string { + // Generate and return a 'libtrust' fingerprint of the public key. + // For an RSA key this should be: + // SHA256(DER encoded ASN1) + // Then truncated to 240 bits and encoded into 12 base32 groups like so: + // ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP + derBytes, err := x509.MarshalPKIXPublicKey(pubKey.CryptoPublicKey()) + if err != nil { + return "" + } + hasher := crypto.SHA256.New() + hasher.Write(derBytes) + return keyIDEncode(hasher.Sum(nil)[:30]) +} + func stringFromMap(m map[string]interface{}, key string) (string, error) { val, ok := m[key] if !ok { diff --git a/Godeps/_workspace/src/github.com/docker/libtrust/util_test.go b/Godeps/_workspace/src/github.com/docker/libtrust/util_test.go new file mode 100644 index 0000000000..ee54f5b8cc --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libtrust/util_test.go @@ -0,0 +1,23 @@ +package libtrust + +import ( + "encoding/pem" + "reflect" + "testing" +) + +func TestAddPEMHeadersToKey(t *testing.T) { + pk := &rsaPublicKey{nil, map[string]interface{}{}} + blk := &pem.Block{Headers: map[string]string{"hosts": "localhost,127.0.0.1"}} + addPEMHeadersToKey(blk, pk) + + val := pk.GetExtendedField("hosts") + hosts, ok := val.([]string) + if !ok { + t.Fatalf("hosts type(%v), expected []string", reflect.TypeOf(val)) + } + expected := []string{"localhost", "127.0.0.1"} + if !reflect.DeepEqual(hosts, expected) { + t.Errorf("hosts(%v), expected %v", hosts, expected) + } +} From e0b18fee7d11799ade76ea70b177fa36db4b658b Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Wed, 10 Dec 2014 09:09:54 -0800 Subject: [PATCH 4/4] Update logrus Signed-off-by: Ben Firshman --- Godeps/Godeps.json | 4 ++-- .../src/github.com/Sirupsen/logrus/README.md | 9 ++++++++- .../src/github.com/Sirupsen/logrus/exported.go | 5 +++++ .../Sirupsen/logrus/hooks/syslog/syslog.go | 14 +++++++------- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index ad41cdea86..4e2a32fa9e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -14,8 +14,8 @@ }, { "ImportPath": "github.com/Sirupsen/logrus", - "Comment": "v0.6.0-5-gf92b795", - "Rev": "f92b7950b372b1db80bd3527e4d40e42555fe6c2" + "Comment": "v0.6.1", + "Rev": "1f2ba2c6317323dd667bd266c1e8ebffc4a4c62f" }, { "ImportPath": "github.com/digitalocean/godo", diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md b/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md index 01769c723f..cabd027ae9 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md @@ -206,11 +206,18 @@ import ( log "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus/hooks/airbrake" "github.com/Sirupsen/logrus/hooks/syslog" + "log/syslog" ) func init() { log.AddHook(new(logrus_airbrake.AirbrakeHook)) - log.AddHook(logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")) + + hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + if err != nil { + log.Error("Unable to connect to local syslog daemon") + } else { + log.AddHook(hook) + } } ``` diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go index 0e2d59f19a..d087124481 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go @@ -30,6 +30,11 @@ func SetLevel(level Level) { std.Level = level } +// GetLevel returns the standard logger level. +func GetLevel() Level { + return std.Level +} + // AddHook adds a hook to the standard logger hooks. func AddHook(hook Hook) { std.mu.Lock() diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go index 2a18ce6130..b6fa374628 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go @@ -29,18 +29,18 @@ func (hook *SyslogHook) Fire(entry *logrus.Entry) error { return err } - switch entry.Data["level"] { - case "panic": + switch entry.Level { + case logrus.PanicLevel: return hook.Writer.Crit(line) - case "fatal": + case logrus.FatalLevel: return hook.Writer.Crit(line) - case "error": + case logrus.ErrorLevel: return hook.Writer.Err(line) - case "warn": + case logrus.WarnLevel: return hook.Writer.Warning(line) - case "info": + case logrus.InfoLevel: return hook.Writer.Info(line) - case "debug": + case logrus.DebugLevel: return hook.Writer.Debug(line) default: return nil