aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/auth/jwt.go
diff options
context:
space:
mode:
authorGreg Steuck <gnezdo@google.com>2021-07-28 14:28:49 -0700
committerDmitry Vyukov <dvyukov@google.com>2021-07-30 18:21:17 +0200
commit75047cf943db20755af0bf9ce3af5502b4040b70 (patch)
tree534bc810655205298d73ed2b749361363c6c74bd /pkg/auth/jwt.go
parent7fa384c47c7a97db7f667797bfc8e1ea78167f39 (diff)
pkg/auth: create explicit JWT token cache
This will be easier to reuse as it is no longer http-specific.
Diffstat (limited to 'pkg/auth/jwt.go')
-rw-r--r--pkg/auth/jwt.go62
1 files changed, 54 insertions, 8 deletions
diff --git a/pkg/auth/jwt.go b/pkg/auth/jwt.go
index 680c16281..3e2313d11 100644
--- a/pkg/auth/jwt.go
+++ b/pkg/auth/jwt.go
@@ -11,6 +11,7 @@ import (
"io/ioutil"
"net/http"
"strings"
+ "sync"
"time"
)
@@ -18,9 +19,9 @@ const (
DashboardAudience = "https://syzkaller.appspot.com/api"
)
-type ExpiringToken struct {
- Token string
- Expiration time.Time
+type expiringToken struct {
+ value string
+ expiration time.Time
}
// Returns the unverified expiration value from the given JWT token.
@@ -43,12 +44,16 @@ func extractJwtExpiration(token string) (time.Time, error) {
return time.Unix(claims.Expiration, 0), nil
}
+type (
+ // The types of ctor and doer are the same as in http.NewRequest and
+ // http.DefaultClient.Do.
+ requestCtor func(method, url string, body io.Reader) (*http.Request, error)
+ requestDoer func(req *http.Request) (*http.Response, error)
+)
+
// Queries the metadata server and returns the bearer token of the
// service account. The token is scoped for the official dashboard.
-// The types of ctor and doer are the same as in http.NewRequest and
-// http.DefaultClient.Do.
-func RetrieveJwtToken(ctor func(method, url string, body io.Reader) (*http.Request, error),
- doer func(req *http.Request) (*http.Response, error)) (*ExpiringToken, error) {
+func retrieveJwtToken(ctor requestCtor, doer requestDoer) (*expiringToken, error) {
const v1meta = "http://metadata.google.internal/computeMetadata/v1"
req, err := ctor("GET", v1meta+"/instance/service-accounts/default/identity?audience="+DashboardAudience, nil)
if err != nil {
@@ -72,5 +77,46 @@ func RetrieveJwtToken(ctor func(method, url string, body io.Reader) (*http.Reque
if err != nil {
return nil, err
}
- return &ExpiringToken{token, expiration}, nil
+ return &expiringToken{token, expiration}, nil
+}
+
+// TokenCache keeps the tokens for reuse by Get.
+type TokenCache struct {
+ lock sync.Mutex
+ token *expiringToken
+ ctor requestCtor
+ doer requestDoer
+}
+
+// MakeCache creates a new cache or returns an error if tokens aren't
+// available.
+func MakeCache(ctor func(method, url string, body io.Reader) (*http.Request, error),
+ doer func(req *http.Request) (*http.Response, error)) (*TokenCache, error) {
+ token, err := retrieveJwtToken(ctor, doer)
+ if err != nil {
+ return nil, err
+ }
+ return &TokenCache{sync.Mutex{}, token, ctor, doer}, nil
+}
+
+// Get returns a potentially cached value of the token or renews as
+// necessary. The now parameter provides the current time for cache
+// expiration.
+func (cache *TokenCache) Get(now time.Time) (string, error) {
+ cache.lock.Lock()
+ defer cache.lock.Unlock()
+ // A typical token returned by metadata server is valid for an hour.
+ // Refreshing a minute early should give the recipient plenty of time
+ // to verify the token.
+ if cache.token.expiration.Sub(now) < time.Minute {
+ // Keeping the lock while making http request is dubious, but
+ // making multiple concurrent requests is not any better.
+ t, err := retrieveJwtToken(cache.ctor, cache.doer)
+ if err != nil {
+ // Can't get a new token, so returning the error preemptively.
+ return "", err
+ }
+ cache.token = t
+ }
+ return cache.token.value, nil
}