From 08f66eff458e07faa586b4a1818b95bf88f4d70b Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Thu, 27 Aug 2020 12:18:27 +0800 Subject: [PATCH 01/14] Add OAuth, Google and Static Authentication Providers to Go SDK. --- sdk/go/auth.go | 129 ++++++++++++++++++++++++++++++++++++++++ sdk/go/auth_test.go | 140 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 sdk/go/auth.go create mode 100644 sdk/go/auth_test.go diff --git a/sdk/go/auth.go b/sdk/go/auth.go new file mode 100644 index 00000000000..b474291264f --- /dev/null +++ b/sdk/go/auth.go @@ -0,0 +1,129 @@ +package feast + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "io/ioutil" + "net/http" + "net/url" +) + +// AuthProvider defines an Authentication Provider that provides OIDC ID tokens +// to be use when authenticating with Feast. +type AuthProvider interface { + // Get a OIDC ID token that can be used for authenticating with Feast. + Token() (string, error) +} + +// Defines a AuthProvider that uses a static token +type StaticProvider struct { + // Static token that the AuthProvider uses authenticate. + StaticToken string +} + +func (provider *StaticProvider) Token() (string, error) { + return provider.StaticToken, nil +} + +// Google Authentication Provider obtains credentials from Application Default Credentials +type GoogleProvider struct { + token *oauth2.Token + findDefaultCredentials func(ctx context.Context, scopes ...string) (*google.Credentials, error) +} + +func NewGoogleProvider() *GoogleProvider { + return &GoogleProvider{ + token: nil, + findDefaultCredentials: google.FindDefaultCredentials, + } +} + +/// Backend Google OAuth used by GoogleProvider to source credentials. +func (provider *GoogleProvider) Token() (string, error) { + if provider.token == nil || !provider.token.Valid() { + // Refresh a Google Id token + // Attempt to refresh Token from Google Application Default Credentials + ctx := context.Background() + creds, err := provider.findDefaultCredentials(ctx, "openid", "email") + if err != nil { + return "", err + } + token, err := creds.TokenSource.Token() + if err != nil { + return "", err + } + provider.token = token + } + + return provider.token.AccessToken, nil +} + +// OAuth Provider obtains credentials by making a client credentials request to +// an OAuth endpoint. +type OAuthProvider struct { + // Client credentials used to authenticate the client when obtaining credentials. + ClientId string + ClientSecret string + // Target audience of the obtained credentials. + Audience string + // Target endpoint to make request to refresh credentials. + EndpointURL *url.URL + token *oauth2.Token +} + +func (provider *OAuthProvider) Token() (string, error) { + if provider.token == nil || !provider.token.Valid() { + // Refresh Oauth Id token by making Oauth client credentials request + reqMap := map[string]string{ + "grant_type": "client_credentials", + "client_id": provider.ClientId, + "client_secret": provider.ClientSecret, + "audience": provider.Audience, + } + reqBytes, err := json.Marshal(reqMap) + if err != nil { + return "", err + } + resp, err := http.Post(provider.EndpointURL.String(), + "application/json", bytes.NewBuffer(reqBytes)) + if err != nil { + return "", err + } + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("OAuth Endpoint returned unexpected status: %s", resp.Status) + } + respBytes, err := ioutil.ReadAll(resp.Body) + fmt.Println(string(respBytes)) + if err != nil { + return "", err + } + provider.token = &oauth2.Token{} + err = json.Unmarshal(respBytes, provider.token) + if err != nil { + return "", err + } + } + + return provider.token.AccessToken, nil +} + +// Create an GRPC Authentication interceptor with the given AuthProvider. +// Interceptor will append token from provider to grpc metadata to authenticate with Feast. +// provider - AuthProvider used to provide credentials +func NewAuthInterceptor(provider AuthProvider) grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, + invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + token, err := provider.Token() + if err != nil { + return fmt.Errorf("Failed obtain token from Authentication provider: %v", err) + } + ctx = metadata.AppendToOutgoingContext(ctx, "Authorization", "Bearer "+token) + return invoker(ctx, method, req, reply, cc, opts...) + } +} diff --git a/sdk/go/auth_test.go b/sdk/go/auth_test.go new file mode 100644 index 00000000000..191c9c9c528 --- /dev/null +++ b/sdk/go/auth_test.go @@ -0,0 +1,140 @@ +package feast + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" +) + +// Returns a mocked google authentication provider +func mockGoogleProvider(token string) *GoogleProvider { + return &GoogleProvider{ + // mock find default credentials implementation. + findDefaultCredentials: func(ctx context.Context, scopes ...string) (*google.Credentials, error) { + if scopes[0] != "openid" && scopes[1] != "email" { + return nil, fmt.Errorf("Got bad scopes. Expected 'openid', 'email'") + } + + return &google.Credentials{ + ProjectID: "", + TokenSource: oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: token, + }), + }, nil + }, + } +} + +// Create a mock OAuth HTTP server & Oauth Provider +type OAuthCredientialsRequest struct { + GrantType string `json:"grant_type"` + ClientId string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Audience string `json:"audience"` +} + +func mockOAuthProvider(token string) (*httptest.Server, *OAuthProvider) { + clientId := "id" + clientSecret := "secret" + audience := "http://localhost" + path := "/oauth" + + // Create a mock OAuth server to test Oauth provider. + handlers := http.NewServeMux() + handlers.HandleFunc(path, func(resp http.ResponseWriter, req *http.Request) { + reqBytes, err := ioutil.ReadAll(req.Body) + if err != nil { + resp.WriteHeader(http.StatusBadRequest) + fmt.Printf("Oauth server failed to read request: %v", err) + } + + oauthReq := OAuthCredientialsRequest{} + err = json.Unmarshal(reqBytes, &oauthReq) + if err != nil { + resp.WriteHeader(http.StatusBadRequest) + fmt.Printf("Oauth server failed to read request: %v", err) + } + + if oauthReq.GrantType != "client_credentials" || + oauthReq.ClientId != clientId || + oauthReq.ClientSecret != clientSecret || + oauthReq.Audience != audience { + + resp.WriteHeader(http.StatusUnauthorized) + fmt.Printf("Oauth server failed authenticate request") + } + + _, err = resp.Write([]byte(fmt.Sprintf("{\"access_token\": \"%s\"}", token))) + if err != nil { + resp.WriteHeader(http.StatusInternalServerError) + fmt.Printf("Oauth server failed to write response: %v", err) + } + }) + + srv := httptest.NewServer(handlers) + url, _ := url.Parse(srv.URL + path) + return srv, &OAuthProvider{ + ClientId: "id", + ClientSecret: "secret", + Audience: "http://localhost", + EndpointURL: url, + } +} + +func TestAuthProvider(t *testing.T) { + srv, oauthProvider := mockOAuthProvider("oauth token") + defer srv.Close() + + tt := []struct { + name string + provider AuthProvider + want string + wantErr bool + err error + }{ + { + name: "Valid Static Authentication Provider get token.", + provider: &StaticProvider{ + StaticToken: "static token", + }, + want: "static token", + wantErr: false, + err: nil, + }, + { + name: "Valid Google Authentication Provider get token.", + provider: mockGoogleProvider("google token"), + want: "google token", + wantErr: false, + err: nil, + }, + { + name: "Valid OAuth Authentication Provider get token.", + provider: oauthProvider, + want: "oauth token", + wantErr: false, + err: nil, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + token, err := tc.provider.Token() + if err != nil { + t.Error(err) + } + + if token != tc.want { + t.Fatalf("Authentication provider returned '%s', expected '%s'", token, tc.want) + } + }) + } +} From 55ea17e54b550df827ce6180df74ec08e4affdb7 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Thu, 27 Aug 2020 17:08:14 +0800 Subject: [PATCH 02/14] Added NewAuthGrpcClient to Go SDK to create authenticated GrpcClient --- sdk/go/auth.go | 17 ------------ sdk/go/client.go | 69 +++++++++++++++++++++++++++++++++++++++++++----- sdk/go/go.mod | 1 + sdk/go/go.sum | 2 ++ 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/sdk/go/auth.go b/sdk/go/auth.go index b474291264f..218ad903fe0 100644 --- a/sdk/go/auth.go +++ b/sdk/go/auth.go @@ -7,8 +7,6 @@ import ( "fmt" "golang.org/x/oauth2" "golang.org/x/oauth2/google" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" "io/ioutil" "net/http" "net/url" @@ -112,18 +110,3 @@ func (provider *OAuthProvider) Token() (string, error) { return provider.token.AccessToken, nil } - -// Create an GRPC Authentication interceptor with the given AuthProvider. -// Interceptor will append token from provider to grpc metadata to authenticate with Feast. -// provider - AuthProvider used to provide credentials -func NewAuthInterceptor(provider AuthProvider) grpc.UnaryClientInterceptor { - return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, - invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { - token, err := provider.Token() - if err != nil { - return fmt.Errorf("Failed obtain token from Authentication provider: %v", err) - } - ctx = metadata.AppendToOutgoingContext(ctx, "Authorization", "Bearer "+token) - return invoker(ctx, method, req, reply, cc, opts...) - } -} diff --git a/sdk/go/client.go b/sdk/go/client.go index eb9fb5bc81b..8662562a722 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -3,12 +3,12 @@ package feast import ( "context" "fmt" - "github.com/opentracing/opentracing-go" - "github.com/feast-dev/feast/sdk/go/protos/feast/serving" - "google.golang.org/grpc" - + "github.com/opentracing/opentracing-go" "go.opencensus.io/plugin/ocgrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" ) // Client is a feast serving client. @@ -24,12 +24,53 @@ type GrpcClient struct { conn *grpc.ClientConn } +// SecurityConfig wraps security config for GrpcClient +type SecurityConfig struct { + // Whether to enable TLS SSL trasnport security if true. + EnableTLS bool + // Optional: Provides path to TLS certificate use the verify Service identity. + TLSCertPath string + // Optional: Authentication provider used for authentication. + // Disables authentication if unspecified. + AuthProvider AuthProvider +} + // NewGrpcClient constructs a client that can interact via grpc with the feast serving instance at the given host:port. func NewGrpcClient(host string, port int) (*GrpcClient, error) { - feastCli := &GrpcClient{} + return NewAuthGrpcClient(host, port, SecurityConfig{ + EnableTLS: false, + AuthProvider: nil, + }) +} +// NewAuthGrpcClient constructs a client that can connect with feast serving instances with authentication enabled. +// host - hostname of the serving host/instance to connect to. +// port - post of the host to service host/instancf to connect to. +// securityConfig - security config configures client security. +func NewAuthGrpcClient(host string, port int, security SecurityConfig) (*GrpcClient, error) { + feastCli := &GrpcClient{} adr := fmt.Sprintf("%s:%d", host, port) - conn, err := grpc.Dial(adr, grpc.WithStatsHandler(&ocgrpc.ClientHandler{}), grpc.WithInsecure()) + + // Compile grpc dial options from security config. + options := []grpc.DialOption{grpc.WithStatsHandler(&ocgrpc.ClientHandler{})} + if !security.EnableTLS { + options = append(options, grpc.WithInsecure()) + } + // Read TLS certificate from given path instead of using system certs if specified. + if security.EnableTLS && security.TLSCertPath != "" { + tlsCreds, err := credentials.NewClientTLSFromFile(security.TLSCertPath, "") + if err != nil { + return nil, err + } + options = append(options, grpc.WithTransportCredentials(tlsCreds)) + } + // Enable authentication by attaching auth interceptor if auth provider is given. + if security.AuthProvider != nil { + authInterceptor := newAuthInterceptor(security.AuthProvider) + options = append(options, grpc.WithUnaryInterceptor(authInterceptor)) + } + + conn, err := grpc.Dial(adr, options...) if err != nil { return nil, err } @@ -73,3 +114,19 @@ func (fc *GrpcClient) GetFeastServingInfo(ctx context.Context, in *serving.GetFe func (fc *GrpcClient) Close() error { return fc.conn.Close() } + +// Utilities +// Create an GRPC Authentication interceptor with the given AuthProvider. +// Interceptor will append token from provider to grpc metadata to authenticate with Feast. +// provider - AuthProvider used to provide credentials +func newAuthInterceptor(provider AuthProvider) grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, + invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + token, err := provider.Token() + if err != nil { + return fmt.Errorf("Failed obtain token from Authentication provider: %v", err) + } + ctx = metadata.AppendToOutgoingContext(ctx, "Authorization", "Bearer "+token) + return invoker(ctx, method, req, reply, cc, opts...) + } +} diff --git a/sdk/go/go.mod b/sdk/go/go.mod index 656810354ba..44dfdf14e3c 100644 --- a/sdk/go/go.mod +++ b/sdk/go/go.mod @@ -9,6 +9,7 @@ require ( github.com/opentracing/opentracing-go v1.1.0 github.com/stretchr/testify v1.4.0 // indirect go.opencensus.io v0.22.1 + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be google.golang.org/grpc v1.28.0 google.golang.org/protobuf v1.21.0 ) diff --git a/sdk/go/go.sum b/sdk/go/go.sum index 39e6ef923b4..13e6d597471 100644 --- a/sdk/go/go.sum +++ b/sdk/go/go.sum @@ -1,3 +1,4 @@ +cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -57,6 +58,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJV golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 1ef2d0cb9dd2792d0ffd095bff4374856cc62443 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Fri, 28 Aug 2020 10:14:35 +0800 Subject: [PATCH 03/14] Fix GoogleProvider providing access token instead of id token required for feast authentication --- sdk/go/auth.go | 18 ++++++++++++------ sdk/go/auth_test.go | 20 +++++++++++++++----- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/sdk/go/auth.go b/sdk/go/auth.go index 218ad903fe0..d9374476f8c 100644 --- a/sdk/go/auth.go +++ b/sdk/go/auth.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "google.golang.org/api/idtoken" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "io/ioutil" @@ -33,12 +34,14 @@ func (provider *StaticProvider) Token() (string, error) { type GoogleProvider struct { token *oauth2.Token findDefaultCredentials func(ctx context.Context, scopes ...string) (*google.Credentials, error) + makeTokenSource func(ctx context.Context, audience string, opts ...idtoken.ClientOption) (oauth2.TokenSource, error) } func NewGoogleProvider() *GoogleProvider { return &GoogleProvider{ token: nil, findDefaultCredentials: google.FindDefaultCredentials, + makeTokenSource: idtoken.NewTokenSource, } } @@ -46,19 +49,23 @@ func NewGoogleProvider() *GoogleProvider { func (provider *GoogleProvider) Token() (string, error) { if provider.token == nil || !provider.token.Valid() { // Refresh a Google Id token - // Attempt to refresh Token from Google Application Default Credentials + // Attempt to id token from Google Application Default Credentials ctx := context.Background() creds, err := provider.findDefaultCredentials(ctx, "openid", "email") if err != nil { return "", err } - token, err := creds.TokenSource.Token() + // TODO: remove hardcode. + aud := "localhost" + src, err := provider.makeTokenSource(ctx, aud, idtoken.WithCredentialsJSON(creds.JSON)) + if err != nil { + return "", err + } + provider.token, err = src.Token() if err != nil { return "", err } - provider.token = token } - return provider.token.AccessToken, nil } @@ -97,7 +104,6 @@ func (provider *OAuthProvider) Token() (string, error) { return "", fmt.Errorf("OAuth Endpoint returned unexpected status: %s", resp.Status) } respBytes, err := ioutil.ReadAll(resp.Body) - fmt.Println(string(respBytes)) if err != nil { return "", err } @@ -107,6 +113,6 @@ func (provider *OAuthProvider) Token() (string, error) { return "", err } } - + return provider.token.AccessToken, nil } diff --git a/sdk/go/auth_test.go b/sdk/go/auth_test.go index 191c9c9c528..a9e920ba006 100644 --- a/sdk/go/auth_test.go +++ b/sdk/go/auth_test.go @@ -12,6 +12,7 @@ import ( "golang.org/x/oauth2" "golang.org/x/oauth2/google" + "google.golang.org/api/idtoken" ) // Returns a mocked google authentication provider @@ -19,17 +20,26 @@ func mockGoogleProvider(token string) *GoogleProvider { return &GoogleProvider{ // mock find default credentials implementation. findDefaultCredentials: func(ctx context.Context, scopes ...string) (*google.Credentials, error) { - if scopes[0] != "openid" && scopes[1] != "email" { + if len(scopes) != 2 && scopes[0] != "openid" && scopes[1] != "email" { return nil, fmt.Errorf("Got bad scopes. Expected 'openid', 'email'") } return &google.Credentials{ - ProjectID: "", - TokenSource: oauth2.StaticTokenSource(&oauth2.Token{ - AccessToken: token, - }), + ProjectID: "project_id", + JSON: []byte("mock key json"), }, nil }, + // mock id token source implementation. + makeTokenSource: func(ctx context.Context, audience string, opts ...idtoken.ClientOption) (oauth2.TokenSource, error) { + // unable to check opts as ClientOption refrences internal type. + if len(audience) == 0 { + return nil, fmt.Errorf("Audience cannot be an empty string.") + } + + return oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: "google token", + }), nil + }, } } From ea40082edbc15fe60105ff7e45aa0b66c66bce7a Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Sun, 30 Aug 2020 11:30:40 +0800 Subject: [PATCH 04/14] Fix typo in authorization grpc metadata should not caps first letter --- sdk/go/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/go/client.go b/sdk/go/client.go index 8662562a722..cf519ddf1e3 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -126,7 +126,7 @@ func newAuthInterceptor(provider AuthProvider) grpc.UnaryClientInterceptor { if err != nil { return fmt.Errorf("Failed obtain token from Authentication provider: %v", err) } - ctx = metadata.AppendToOutgoingContext(ctx, "Authorization", "Bearer "+token) + ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token) return invoker(ctx, method, req, reply, cc, opts...) } } From 4daf18844ba548858cdae7ce83d67ed03bbff01c Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Sun, 30 Aug 2020 12:25:19 +0800 Subject: [PATCH 05/14] Infer token audience from serving service address in Go SDK. --- sdk/go/auth.go | 41 ++++--- sdk/go/auth_test.go | 25 ++-- sdk/go/client.go | 3 +- sdk/go/go.mod | 13 +- sdk/go/go.sum | 285 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 326 insertions(+), 41 deletions(-) diff --git a/sdk/go/auth.go b/sdk/go/auth.go index d9374476f8c..f4a2d2b78b2 100644 --- a/sdk/go/auth.go +++ b/sdk/go/auth.go @@ -5,9 +5,9 @@ import ( "context" "encoding/json" "fmt" - "google.golang.org/api/idtoken" "golang.org/x/oauth2" "golang.org/x/oauth2/google" + "google.golang.org/api/idtoken" "io/ioutil" "net/http" "net/url" @@ -17,7 +17,8 @@ import ( // to be use when authenticating with Feast. type AuthProvider interface { // Get a OIDC ID token that can be used for authenticating with Feast. - Token() (string, error) + // audience - Target audience of the obtained credentials. + Token(audience string) (string, error) } // Defines a AuthProvider that uses a static token @@ -26,28 +27,28 @@ type StaticProvider struct { StaticToken string } -func (provider *StaticProvider) Token() (string, error) { +func (provider *StaticProvider) Token(_ string) (string, error) { return provider.StaticToken, nil } // Google Authentication Provider obtains credentials from Application Default Credentials type GoogleProvider struct { - token *oauth2.Token findDefaultCredentials func(ctx context.Context, scopes ...string) (*google.Credentials, error) - makeTokenSource func(ctx context.Context, audience string, opts ...idtoken.ClientOption) (oauth2.TokenSource, error) + makeTokenSource func(ctx context.Context, audience string, opts ...idtoken.ClientOption) (oauth2.TokenSource, error) + token *oauth2.Token + audience string } func NewGoogleProvider() *GoogleProvider { return &GoogleProvider{ - token: nil, findDefaultCredentials: google.FindDefaultCredentials, - makeTokenSource: idtoken.NewTokenSource, + makeTokenSource: idtoken.NewTokenSource, } } /// Backend Google OAuth used by GoogleProvider to source credentials. -func (provider *GoogleProvider) Token() (string, error) { - if provider.token == nil || !provider.token.Valid() { +func (provider *GoogleProvider) Token(audience string) (string, error) { + if provider.token == nil || !provider.token.Valid() || provider.audience != audience { // Refresh a Google Id token // Attempt to id token from Google Application Default Credentials ctx := context.Background() @@ -55,13 +56,12 @@ func (provider *GoogleProvider) Token() (string, error) { if err != nil { return "", err } - // TODO: remove hardcode. - aud := "localhost" - src, err := provider.makeTokenSource(ctx, aud, idtoken.WithCredentialsJSON(creds.JSON)) + src, err := provider.makeTokenSource(ctx, audience, idtoken.WithCredentialsJSON(creds.JSON)) if err != nil { return "", err } provider.token, err = src.Token() + provider.audience = audience if err != nil { return "", err } @@ -75,21 +75,19 @@ type OAuthProvider struct { // Client credentials used to authenticate the client when obtaining credentials. ClientId string ClientSecret string - // Target audience of the obtained credentials. - Audience string - // Target endpoint to make request to refresh credentials. - EndpointURL *url.URL - token *oauth2.Token + EndpointURL *url.URL + token *oauth2.Token + audience string } -func (provider *OAuthProvider) Token() (string, error) { - if provider.token == nil || !provider.token.Valid() { +func (provider *OAuthProvider) Token(audience string) (string, error) { + if provider.token == nil || !provider.token.Valid() || provider.audience != audience { // Refresh Oauth Id token by making Oauth client credentials request reqMap := map[string]string{ "grant_type": "client_credentials", "client_id": provider.ClientId, "client_secret": provider.ClientSecret, - "audience": provider.Audience, + "audience": audience, } reqBytes, err := json.Marshal(reqMap) if err != nil { @@ -108,11 +106,12 @@ func (provider *OAuthProvider) Token() (string, error) { return "", err } provider.token = &oauth2.Token{} + provider.audience = audience err = json.Unmarshal(respBytes, provider.token) if err != nil { return "", err } } - + return provider.token.AccessToken, nil } diff --git a/sdk/go/auth_test.go b/sdk/go/auth_test.go index a9e920ba006..7720144fef8 100644 --- a/sdk/go/auth_test.go +++ b/sdk/go/auth_test.go @@ -16,7 +16,7 @@ import ( ) // Returns a mocked google authentication provider -func mockGoogleProvider(token string) *GoogleProvider { +func mockGoogleProvider(token string, targetAudience string) *GoogleProvider { return &GoogleProvider{ // mock find default credentials implementation. findDefaultCredentials: func(ctx context.Context, scopes ...string) (*google.Credentials, error) { @@ -26,16 +26,16 @@ func mockGoogleProvider(token string) *GoogleProvider { return &google.Credentials{ ProjectID: "project_id", - JSON: []byte("mock key json"), + JSON: []byte("mock key json"), }, nil }, // mock id token source implementation. makeTokenSource: func(ctx context.Context, audience string, opts ...idtoken.ClientOption) (oauth2.TokenSource, error) { // unable to check opts as ClientOption refrences internal type. - if len(audience) == 0 { - return nil, fmt.Errorf("Audience cannot be an empty string.") + if targetAudience != audience { + return nil, fmt.Errorf("Audience does not match up with target audience") } - + return oauth2.StaticTokenSource(&oauth2.Token{ AccessToken: "google token", }), nil @@ -51,10 +51,9 @@ type OAuthCredientialsRequest struct { Audience string `json:"audience"` } -func mockOAuthProvider(token string) (*httptest.Server, *OAuthProvider) { +func mockOAuthProvider(token string, audience string) (*httptest.Server, *OAuthProvider) { clientId := "id" clientSecret := "secret" - audience := "http://localhost" path := "/oauth" // Create a mock OAuth server to test Oauth provider. @@ -90,17 +89,17 @@ func mockOAuthProvider(token string) (*httptest.Server, *OAuthProvider) { }) srv := httptest.NewServer(handlers) - url, _ := url.Parse(srv.URL + path) + endpointURL, _ := url.Parse(srv.URL + path) return srv, &OAuthProvider{ ClientId: "id", ClientSecret: "secret", - Audience: "http://localhost", - EndpointURL: url, + EndpointURL: endpointURL, } } func TestAuthProvider(t *testing.T) { - srv, oauthProvider := mockOAuthProvider("oauth token") + audience := "localhost" + srv, oauthProvider := mockOAuthProvider("oauth token", audience) defer srv.Close() tt := []struct { @@ -121,7 +120,7 @@ func TestAuthProvider(t *testing.T) { }, { name: "Valid Google Authentication Provider get token.", - provider: mockGoogleProvider("google token"), + provider: mockGoogleProvider("google token", audience), want: "google token", wantErr: false, err: nil, @@ -137,7 +136,7 @@ func TestAuthProvider(t *testing.T) { for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - token, err := tc.provider.Token() + token, err := tc.provider.Token(audience) if err != nil { t.Error(err) } diff --git a/sdk/go/client.go b/sdk/go/client.go index cf519ddf1e3..8254184ad14 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -122,7 +122,8 @@ func (fc *GrpcClient) Close() error { func newAuthInterceptor(provider AuthProvider) grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { - token, err := provider.Token() + // configure service address as token audience. + token, err := provider.Token(cc.Target()) if err != nil { return fmt.Errorf("Failed obtain token from Authentication provider: %v", err) } diff --git a/sdk/go/go.mod b/sdk/go/go.mod index 44dfdf14e3c..6a3b3dd477b 100644 --- a/sdk/go/go.mod +++ b/sdk/go/go.mod @@ -4,12 +4,13 @@ go 1.13 require ( github.com/golang/mock v1.4.3 - github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0 - github.com/google/go-cmp v0.4.0 + github.com/golang/protobuf v1.4.2 + github.com/google/go-cmp v0.5.1 github.com/opentracing/opentracing-go v1.1.0 github.com/stretchr/testify v1.4.0 // indirect - go.opencensus.io v0.22.1 - golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be - google.golang.org/grpc v1.28.0 - google.golang.org/protobuf v1.21.0 + go.opencensus.io v0.22.4 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + google.golang.org/api v0.30.0 + google.golang.org/grpc v1.31.0 + google.golang.org/protobuf v1.25.0 ) diff --git a/sdk/go/go.sum b/sdk/go/go.sum index 13e6d597471..dba3041f042 100644 --- a/sdk/go/go.sum +++ b/sdk/go/go.sum @@ -1,20 +1,67 @@ cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0 h1:RmDygqvj27Zf3fCQjQRtLyC7KwFcHkeJitcO0OoGOcA= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -23,11 +70,19 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0 h1:aRz0NBceriICVtjhCgKkDvl+RudKu1CT6h0ZvUTrNfE= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= @@ -36,77 +91,307 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1 h1:8dP3SGL7MPB94crU3bEPplMPe83FI4EouesJUeFHv50= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd h1:r7DufRZuZbWB7j439YfAzP8RPDa9unLkpwQKUYbIMPI= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c h1:Lq4llNryJoaVFRmvrIwC/ZHH7tNt4tUYIu8+se2aayY= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 253b320eb0faf3c29939fdf23b704359c7bc2f5f Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Mon, 31 Aug 2020 15:10:46 +0800 Subject: [PATCH 06/14] Add javadocs to OAuthCredentials, GoogleAuthCredentials & JwtCallCredentials as they are to become user facing apis. --- .../auth/credentials/GoogleAuthCredentials.java | 15 +++++++++++---- .../auth/credentials/OAuthCredentials.java | 16 +++++++++++++--- .../core/auth/infra/JwtCallCredentials.java | 4 ++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/feast/common/auth/credentials/GoogleAuthCredentials.java b/common/src/main/java/feast/common/auth/credentials/GoogleAuthCredentials.java index c8c2d846001..4c096877dc9 100644 --- a/common/src/main/java/feast/common/auth/credentials/GoogleAuthCredentials.java +++ b/common/src/main/java/feast/common/auth/credentials/GoogleAuthCredentials.java @@ -28,9 +28,11 @@ import java.util.Map; import java.util.concurrent.Executor; -/* - * Google auth provider's callCredentials Implementation for serving. - * Used by CoreSpecService to connect to core. +/** + * GoogleAuthCredentials provides a Google OIDC ID token for making authenticated gRPC calls. Uses + * Google Application + * Default credentials to obtain the OIDC token used for authentication. The given token will be + * passed as authorization bearer token when making calls. */ public class GoogleAuthCredentials extends CallCredentials { private final IdTokenCredentials credentials; @@ -38,8 +40,13 @@ public class GoogleAuthCredentials extends CallCredentials { private static final Metadata.Key AUTHORIZATION_METADATA_KEY = Metadata.Key.of("Authorization", ASCII_STRING_MARSHALLER); + /** + * Constructa new GoogleAuthCredentials with given options. + * + * @param options a map of options, Required unless specified: audience - Optional, Sets the + * target audience of the token obtained. + */ public GoogleAuthCredentials(Map options) throws IOException { - String targetAudience = options.getOrDefault("audience", "https://localhost"); ServiceAccountCredentials serviceCreds = (ServiceAccountCredentials) diff --git a/common/src/main/java/feast/common/auth/credentials/OAuthCredentials.java b/common/src/main/java/feast/common/auth/credentials/OAuthCredentials.java index 58ab3cf868d..429fd7fdadd 100644 --- a/common/src/main/java/feast/common/auth/credentials/OAuthCredentials.java +++ b/common/src/main/java/feast/common/auth/credentials/OAuthCredentials.java @@ -34,9 +34,10 @@ import okhttp3.Response; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -/* - * Oauth Credentials Implementation for serving. - * Used by CoreSpecService to connect to core. +/** + * OAuthCredentials uses a OAuth OIDC ID token making authenticated gRPC calls. Makes an OAuth + * request to obtain the OIDC token used for authentication. The given token will be passed as + * authorization bearer token when making calls. */ public class OAuthCredentials extends CallCredentials { @@ -58,6 +59,15 @@ public class OAuthCredentials extends CallCredentials { private Instant tokenExpiryTime; private NimbusJwtDecoder jwtDecoder; + /** + * Constructs a new OAuthCredentials with given options. + * + * @param options a map of options, Required unless specified: grant_type - OAuth grant type. + * Should be set as client_credentials audience - Sets the target audience of the token + * obtained. client_id - Client id to use in the OAuth request. client_secret - Client securet + * to use in the OAuth request. jwtEndpointURI - HTTPS URL used to retrieve a JWK that can be + * used to decode the credential. + */ public OAuthCredentials(Map options) { this.httpClient = new OkHttpClient(); if (!(options.containsKey(GRANT_TYPE) diff --git a/core/src/test/java/feast/core/auth/infra/JwtCallCredentials.java b/core/src/test/java/feast/core/auth/infra/JwtCallCredentials.java index 6029cee1219..2e5ed97eb5c 100644 --- a/core/src/test/java/feast/core/auth/infra/JwtCallCredentials.java +++ b/core/src/test/java/feast/core/auth/infra/JwtCallCredentials.java @@ -20,6 +20,10 @@ import io.grpc.Metadata; import java.util.concurrent.Executor; +/** + * JWTCallCredentials provides/attaches a static JWT token for making authenticated gRPC calls. The + * given token will be passed as authorization bearer token when making calls. + */ public final class JwtCallCredentials extends CallCredentials { private String jwt; From 23bd5ad48ba0fa85ff6c4ee5d6d8826f1e400b55 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Mon, 31 Aug 2020 17:47:28 +0800 Subject: [PATCH 07/14] Move JwtCallCredentials to common module as static JWT CallCredentials implementation. --- .../java/feast/common/auth/credentials}/JwtCallCredentials.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename {core/src/test/java/feast/core/auth/infra => common/src/main/java/feast/common/auth/credentials}/JwtCallCredentials.java (97%) diff --git a/core/src/test/java/feast/core/auth/infra/JwtCallCredentials.java b/common/src/main/java/feast/common/auth/credentials/JwtCallCredentials.java similarity index 97% rename from core/src/test/java/feast/core/auth/infra/JwtCallCredentials.java rename to common/src/main/java/feast/common/auth/credentials/JwtCallCredentials.java index 2e5ed97eb5c..f09f4ec9404 100644 --- a/core/src/test/java/feast/core/auth/infra/JwtCallCredentials.java +++ b/common/src/main/java/feast/common/auth/credentials/JwtCallCredentials.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package feast.core.auth.infra; +package feast.common.auth.credentials; import io.grpc.CallCredentials; import io.grpc.Metadata; From f1e28d9e0789d8e54ae922f566b0791b4ca03ada Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Tue, 1 Sep 2020 11:41:39 +0800 Subject: [PATCH 08/14] Go SDK: Replace AuthProvider with Credential implementation. --- sdk/go/auth.go | 155 +++++++++++++++++++++++++------------------- sdk/go/auth_test.go | 131 ++++++++++++++++++------------------- sdk/go/client.go | 33 ++-------- 3 files changed, 156 insertions(+), 163 deletions(-) diff --git a/sdk/go/auth.go b/sdk/go/auth.go index f4a2d2b78b2..143a74dba27 100644 --- a/sdk/go/auth.go +++ b/sdk/go/auth.go @@ -13,105 +13,124 @@ import ( "net/url" ) -// AuthProvider defines an Authentication Provider that provides OIDC ID tokens -// to be use when authenticating with Feast. -type AuthProvider interface { - // Get a OIDC ID token that can be used for authenticating with Feast. - // audience - Target audience of the obtained credentials. - Token(audience string) (string, error) +// Credential provides OIDC ID tokens used when authenticating with Feast. +// Implements credentials.PerRPCCredentials +type Credential struct { + tokenSrc oauth2.TokenSource } -// Defines a AuthProvider that uses a static token -type StaticProvider struct { - // Static token that the AuthProvider uses authenticate. - StaticToken string +// GetRequestMetadata attaches OIDC token as metadata, refreshing tokens if required. +// This should be called by the GRPC to authenticate each request. +func (provider *Credential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + token, err := provider.tokenSrc.Token() + if err != nil { + return map[string]string{}, nil + } + return map[string]string{ + "Authorization": "Bearer: " + token.AccessToken, + }, nil } -func (provider *StaticProvider) Token(_ string) (string, error) { - return provider.StaticToken, nil +// Disable requirement of transport security to allow user to configure it explictly instead. +func (provider *Credential) RequireTransportSecurity() bool { + return false } -// Google Authentication Provider obtains credentials from Application Default Credentials -type GoogleProvider struct { - findDefaultCredentials func(ctx context.Context, scopes ...string) (*google.Credentials, error) - makeTokenSource func(ctx context.Context, audience string, opts ...idtoken.ClientOption) (oauth2.TokenSource, error) - token *oauth2.Token - audience string +// Create a Static Authentication Provider that provides a static token +func NewStaticCredential(token string) *Credential { + return &Credential{tokenSrc: oauth2.StaticTokenSource( + &oauth2.Token{ + AccessToken: token, + }), + } } -func NewGoogleProvider() *GoogleProvider { - return &GoogleProvider{ - findDefaultCredentials: google.FindDefaultCredentials, - makeTokenSource: idtoken.NewTokenSource, +func newGoogleCredential( + audience string, + findDefaultCredentials func(ctx context.Context, scopes ...string) (*google.Credentials, error), + makeTokenSource func(ctx context.Context, audience string, opts ...idtoken.ClientOption) (oauth2.TokenSource, error)) (*Credential, error) { + // Refresh a Google Id token + // Attempt to id token from Google Application Default Credentials + ctx := context.Background() + creds, err := findDefaultCredentials(ctx, "openid", "email") + if err != nil { + return nil, err } + tokenSrc, err := makeTokenSource(ctx, audience, idtoken.WithCredentialsJSON(creds.JSON)) + if err != nil { + return nil, err + } + return &Credential{tokenSrc: tokenSrc}, nil } -/// Backend Google OAuth used by GoogleProvider to source credentials. -func (provider *GoogleProvider) Token(audience string) (string, error) { - if provider.token == nil || !provider.token.Valid() || provider.audience != audience { - // Refresh a Google Id token - // Attempt to id token from Google Application Default Credentials - ctx := context.Background() - creds, err := provider.findDefaultCredentials(ctx, "openid", "email") - if err != nil { - return "", err - } - src, err := provider.makeTokenSource(ctx, audience, idtoken.WithCredentialsJSON(creds.JSON)) - if err != nil { - return "", err - } - provider.token, err = src.Token() - provider.audience = audience - if err != nil { - return "", err - } +// Creates a new Google Credential which obtains credentials from Application Default Credentials +func NewGoogleCredential(audience string) (*Credential, error) { + return newGoogleCredential(audience, google.FindDefaultCredentials, idtoken.NewTokenSource) +} + +// Creates a new OAuth credential witch obtains credentials by making a client credentials request to an OAuth endpoint. +// clientId, clientSecret - Client credentials used to authenticate the client when obtaining credentials. +// endpointURL - target URL of the OAuth endpoint to make the OAuth request to. +func NewOAuthCredential(audience string, clientId string, clientSecret string, endpointURL *url.URL) *Credential { + tokenSrc := &oAuthTokenSource{ + clientId: clientId, + clientSecret: clientSecret, + endpointURL: endpointURL, + audience: audience, } - return provider.token.AccessToken, nil + return &Credential{tokenSrc: tokenSrc} } -// OAuth Provider obtains credentials by making a client credentials request to -// an OAuth endpoint. -type OAuthProvider struct { - // Client credentials used to authenticate the client when obtaining credentials. - ClientId string - ClientSecret string - EndpointURL *url.URL - token *oauth2.Token +// Defines a Token Source that obtains tokens via making a OAuth client credentials request. +type oAuthTokenSource struct { + clientId string + clientSecret string + endpointURL *url.URL audience string + token *oauth2.Token +} + +// Defines a Oauth cleint credentials request. +type oAuthClientCredientialsRequest struct { + GrantType string `json:"grant_type"` + ClientId string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Audience string `json:"audience"` } -func (provider *OAuthProvider) Token(audience string) (string, error) { - if provider.token == nil || !provider.token.Valid() || provider.audience != audience { +// Obtain or Refresh token from OAuth Token Source. +func (tokenSrc *oAuthTokenSource) Token() (*oauth2.Token, error) { + if tokenSrc.token == nil || !tokenSrc.token.Valid() { // Refresh Oauth Id token by making Oauth client credentials request - reqMap := map[string]string{ - "grant_type": "client_credentials", - "client_id": provider.ClientId, - "client_secret": provider.ClientSecret, - "audience": audience, + req := &oAuthClientCredientialsRequest{ + GrantType: "client_credentials", + ClientId: tokenSrc.clientId, + ClientSecret: tokenSrc.clientSecret, + Audience: tokenSrc.audience, } - reqBytes, err := json.Marshal(reqMap) + + reqBytes, err := json.Marshal(req) if err != nil { - return "", err + return nil, err } - resp, err := http.Post(provider.EndpointURL.String(), + resp, err := http.Post(tokenSrc.endpointURL.String(), "application/json", bytes.NewBuffer(reqBytes)) if err != nil { - return "", err + return nil, err } if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("OAuth Endpoint returned unexpected status: %s", resp.Status) + return nil, fmt.Errorf("OAuth Endpoint returned unexpected status: %s", resp.Status) } respBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - return "", err + return nil, err } - provider.token = &oauth2.Token{} - provider.audience = audience - err = json.Unmarshal(respBytes, provider.token) + tokenSrc.token = &oauth2.Token{} + err = json.Unmarshal(respBytes, tokenSrc.token) if err != nil { - return "", err + return nil, err } } - return provider.token.AccessToken, nil + return tokenSrc.token, nil } diff --git a/sdk/go/auth_test.go b/sdk/go/auth_test.go index 7720144fef8..32226586229 100644 --- a/sdk/go/auth_test.go +++ b/sdk/go/auth_test.go @@ -15,43 +15,37 @@ import ( "google.golang.org/api/idtoken" ) -// Returns a mocked google authentication provider -func mockGoogleProvider(token string, targetAudience string) *GoogleProvider { - return &GoogleProvider{ - // mock find default credentials implementation. - findDefaultCredentials: func(ctx context.Context, scopes ...string) (*google.Credentials, error) { - if len(scopes) != 2 && scopes[0] != "openid" && scopes[1] != "email" { - return nil, fmt.Errorf("Got bad scopes. Expected 'openid', 'email'") - } +// Returns a mocked google credential. +func mockGoogleCredential(token string, targetAudience string) (*Credential, error) { + // mock find default credentials implementation. + findDefaultCredentials := func(ctx context.Context, scopes ...string) (*google.Credentials, error) { + if len(scopes) != 2 && scopes[0] != "openid" && scopes[1] != "email" { + return nil, fmt.Errorf("Got bad scopes. Expected 'openid', 'email'") + } - return &google.Credentials{ - ProjectID: "project_id", - JSON: []byte("mock key json"), - }, nil - }, - // mock id token source implementation. - makeTokenSource: func(ctx context.Context, audience string, opts ...idtoken.ClientOption) (oauth2.TokenSource, error) { - // unable to check opts as ClientOption refrences internal type. - if targetAudience != audience { - return nil, fmt.Errorf("Audience does not match up with target audience") - } + return &google.Credentials{ + ProjectID: "project_id", + JSON: []byte("mock key json"), + }, nil + } - return oauth2.StaticTokenSource(&oauth2.Token{ - AccessToken: "google token", - }), nil - }, + // mock id token source implementation. + makeTokenSource := func(ctx context.Context, audience string, opts ...idtoken.ClientOption) (oauth2.TokenSource, error) { + // unable to check opts as ClientOption refrences internal type. + if targetAudience != audience { + return nil, fmt.Errorf("Audience does not match up with target audience") + } + + return oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: "google token", + }), nil } -} -// Create a mock OAuth HTTP server & Oauth Provider -type OAuthCredientialsRequest struct { - GrantType string `json:"grant_type"` - ClientId string `json:"client_id"` - ClientSecret string `json:"client_secret"` - Audience string `json:"audience"` + return newGoogleCredential(targetAudience, findDefaultCredentials, makeTokenSource) } -func mockOAuthProvider(token string, audience string) (*httptest.Server, *OAuthProvider) { +// Create a mocked OAuth credential with a backing mocked OAuth server. +func mockOAuthCredential(token string, audience string) (*httptest.Server, *Credential) { clientId := "id" clientSecret := "secret" path := "/oauth" @@ -62,87 +56,86 @@ func mockOAuthProvider(token string, audience string) (*httptest.Server, *OAuthP reqBytes, err := ioutil.ReadAll(req.Body) if err != nil { resp.WriteHeader(http.StatusBadRequest) - fmt.Printf("Oauth server failed to read request: %v", err) } - oauthReq := OAuthCredientialsRequest{} + oauthReq := oAuthClientCredientialsRequest{} err = json.Unmarshal(reqBytes, &oauthReq) if err != nil { resp.WriteHeader(http.StatusBadRequest) - fmt.Printf("Oauth server failed to read request: %v", err) } if oauthReq.GrantType != "client_credentials" || oauthReq.ClientId != clientId || oauthReq.ClientSecret != clientSecret || oauthReq.Audience != audience { - resp.WriteHeader(http.StatusUnauthorized) - fmt.Printf("Oauth server failed authenticate request") } _, err = resp.Write([]byte(fmt.Sprintf("{\"access_token\": \"%s\"}", token))) if err != nil { resp.WriteHeader(http.StatusInternalServerError) - fmt.Printf("Oauth server failed to write response: %v", err) } }) srv := httptest.NewServer(handlers) endpointURL, _ := url.Parse(srv.URL + path) - return srv, &OAuthProvider{ - ClientId: "id", - ClientSecret: "secret", - EndpointURL: endpointURL, - } + return srv, NewOAuthCredential(audience, clientId, clientSecret, endpointURL) } -func TestAuthProvider(t *testing.T) { +func TestCredentials(t *testing.T) { audience := "localhost" - srv, oauthProvider := mockOAuthProvider("oauth token", audience) + srv, oauthCred := mockOAuthCredential("oauth token", audience) defer srv.Close() + googleCred, err := mockGoogleCredential("google token", audience) + if err != nil { + t.Errorf("Unexpected error creating mock google credential: %v", err) + } tt := []struct { - name string - provider AuthProvider - want string - wantErr bool - err error + name string + credential *Credential + want string + wantErr bool + err error }{ { - name: "Valid Static Authentication Provider get token.", - provider: &StaticProvider{ - StaticToken: "static token", - }, - want: "static token", - wantErr: false, - err: nil, + name: "Valid Static Credential get authentication metadata.", + credential: NewStaticCredential("static token"), + want: "static token", + wantErr: false, + err: nil, }, { - name: "Valid Google Authentication Provider get token.", - provider: mockGoogleProvider("google token", audience), - want: "google token", - wantErr: false, - err: nil, + name: "Valid Google Credential get authentication metadata.", + credential: googleCred, + want: "google token", + wantErr: false, + err: nil, }, { - name: "Valid OAuth Authentication Provider get token.", - provider: oauthProvider, - want: "oauth token", - wantErr: false, - err: nil, + name: "Valid OAuth Credential get authentication metadata.", + credential: oauthCred, + want: "oauth token", + wantErr: false, + err: nil, }, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - token, err := tc.provider.Token(audience) + ctx := context.Background() + meta, err := tc.credential.GetRequestMetadata(ctx, "feast.serving") if err != nil { t.Error(err) } + authKey := "Authorization" + if _, ok := meta[authKey]; !ok { + t.Errorf("Expected authentication metadata with key: '%s'", authKey) + } - if token != tc.want { - t.Fatalf("Authentication provider returned '%s', expected '%s'", token, tc.want) + expectedVal := "Bearer: " + tc.want + if meta[authKey] != expectedVal { + t.Errorf("Expected authentication metadata with value: '%s' Got instead: '%s'", expectedVal, meta[authKey]) } }) } diff --git a/sdk/go/client.go b/sdk/go/client.go index 8254184ad14..997c0bd7c91 100644 --- a/sdk/go/client.go +++ b/sdk/go/client.go @@ -8,7 +8,6 @@ import ( "go.opencensus.io/plugin/ocgrpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials" - "google.golang.org/grpc/metadata" ) // Client is a feast serving client. @@ -30,16 +29,16 @@ type SecurityConfig struct { EnableTLS bool // Optional: Provides path to TLS certificate use the verify Service identity. TLSCertPath string - // Optional: Authentication provider used for authentication. + // Optional: Credential used for authentication. // Disables authentication if unspecified. - AuthProvider AuthProvider + Credential *Credential } // NewGrpcClient constructs a client that can interact via grpc with the feast serving instance at the given host:port. func NewGrpcClient(host string, port int) (*GrpcClient, error) { return NewAuthGrpcClient(host, port, SecurityConfig{ - EnableTLS: false, - AuthProvider: nil, + EnableTLS: false, + Credential: nil, }) } @@ -64,10 +63,9 @@ func NewAuthGrpcClient(host string, port int, security SecurityConfig) (*GrpcCli } options = append(options, grpc.WithTransportCredentials(tlsCreds)) } - // Enable authentication by attaching auth interceptor if auth provider is given. - if security.AuthProvider != nil { - authInterceptor := newAuthInterceptor(security.AuthProvider) - options = append(options, grpc.WithUnaryInterceptor(authInterceptor)) + // Enable authentication by attaching credentials if given + if security.Credential != nil { + options = append(options, grpc.WithPerRPCCredentials(security.Credential)) } conn, err := grpc.Dial(adr, options...) @@ -114,20 +112,3 @@ func (fc *GrpcClient) GetFeastServingInfo(ctx context.Context, in *serving.GetFe func (fc *GrpcClient) Close() error { return fc.conn.Close() } - -// Utilities -// Create an GRPC Authentication interceptor with the given AuthProvider. -// Interceptor will append token from provider to grpc metadata to authenticate with Feast. -// provider - AuthProvider used to provide credentials -func newAuthInterceptor(provider AuthProvider) grpc.UnaryClientInterceptor { - return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, - invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { - // configure service address as token audience. - token, err := provider.Token(cc.Target()) - if err != nil { - return fmt.Errorf("Failed obtain token from Authentication provider: %v", err) - } - ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token) - return invoker(ctx, method, req, reply, cc, opts...) - } -} From c2c87f4221ee959e1aa9857099410f84b7bed363 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Tue, 1 Sep 2020 12:07:30 +0800 Subject: [PATCH 09/14] Update Java SDK to support authentication with CallCredentials implementations. --- .../java/feast/core/auth/infra/JwtHelper.java | 1 + .../java/com/gojek/feast/FeastClient.java | 29 +++++++++-- .../java/com/gojek/feast/FeastClientTest.java | 50 +++++++++++++++++-- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/core/src/test/java/feast/core/auth/infra/JwtHelper.java b/core/src/test/java/feast/core/auth/infra/JwtHelper.java index ebabcd665eb..091956fb888 100644 --- a/core/src/test/java/feast/core/auth/infra/JwtHelper.java +++ b/core/src/test/java/feast/core/auth/infra/JwtHelper.java @@ -25,6 +25,7 @@ import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import feast.common.auth.credentials.JwtCallCredentials; import io.grpc.*; import java.security.interfaces.RSAPublicKey; import java.time.Instant; diff --git a/sdk/java/src/main/java/com/gojek/feast/FeastClient.java b/sdk/java/src/main/java/com/gojek/feast/FeastClient.java index 374db0d2af2..eb777c2c9f0 100644 --- a/sdk/java/src/main/java/com/gojek/feast/FeastClient.java +++ b/sdk/java/src/main/java/com/gojek/feast/FeastClient.java @@ -24,10 +24,12 @@ import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesResponse; import feast.proto.serving.ServingServiceGrpc; import feast.proto.serving.ServingServiceGrpc.ServingServiceBlockingStub; +import io.grpc.CallCredentials; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -43,7 +45,7 @@ public class FeastClient implements AutoCloseable { private final ServingServiceBlockingStub stub; /** - * Create a client to access Feast + * Create a client to access Feast Serving. * * @param host hostname or ip address of Feast serving GRPC server * @param port port number of Feast serving GRPC server @@ -51,7 +53,22 @@ public class FeastClient implements AutoCloseable { */ public static FeastClient create(String host, int port) { ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); - return new FeastClient(channel); + return new FeastClient(channel, Optional.empty()); + } + + /** + * Create a authenticated client that can access Feast serving with authentication enabled. + * Supports the {@link CallCredentials} in the {@link feast.common.auth.credentials} package. + * + * @param host hostname or ip address of Feast serving GRPC server + * @param port port number of Feast serving GRPC server + * @param credentials Call credentials used to provide credentials when calling Feast. + * @return {@link FeastClient} + */ + public static FeastClient createAuthenticated( + String host, int port, CallCredentials credentials) { + ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); + return new FeastClient(channel, Optional.of(credentials)); } public GetFeastServingInfoResponse getFeastServingInfo() { @@ -156,9 +173,13 @@ public List getOnlineFeatures( .collect(Collectors.toList()); } - protected FeastClient(ManagedChannel channel) { + protected FeastClient(ManagedChannel channel, Optional credentials) { this.channel = channel; - this.stub = ServingServiceGrpc.newBlockingStub(channel); + ServingServiceBlockingStub servingStub = ServingServiceGrpc.newBlockingStub(channel); + if (credentials.isPresent()) { + servingStub = servingStub.withCallCredentials(credentials.get()); + } + this.stub = servingStub; } public void close() throws Exception { diff --git a/sdk/java/src/test/java/com/gojek/feast/FeastClientTest.java b/sdk/java/src/test/java/com/gojek/feast/FeastClientTest.java index 84d92466571..0c583be98c4 100644 --- a/sdk/java/src/test/java/com/gojek/feast/FeastClientTest.java +++ b/sdk/java/src/test/java/com/gojek/feast/FeastClientTest.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock; import com.google.protobuf.Timestamp; +import feast.common.auth.credentials.JwtCallCredentials; import feast.proto.serving.ServingAPIProto.FeatureReference; import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest; import feast.proto.serving.ServingAPIProto.GetOnlineFeaturesRequest.EntityRow; @@ -30,6 +31,11 @@ import feast.proto.serving.ServingServiceGrpc.ServingServiceImplBase; import feast.proto.types.ValueProto.Value; import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCall.Listener; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; import io.grpc.Status; import io.grpc.inprocess.InProcessChannelBuilder; import io.grpc.inprocess.InProcessServerBuilder; @@ -39,12 +45,18 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Rule; import org.junit.Test; public class FeastClientTest { + private final String AUTH_TOKEN = "test token"; + @Rule public GrpcCleanupRule grpcRule; + private AtomicBoolean isAuthenticated; + private ServingServiceImplBase servingMock = mock( ServingServiceImplBase.class, @@ -54,7 +66,6 @@ public class FeastClientTest { public void getOnlineFeatures( GetOnlineFeaturesRequest request, StreamObserver responseObserver) { - if (!request.equals(FeastClientTest.getFakeRequest())) { responseObserver.onError(Status.FAILED_PRECONDITION.asRuntimeException()); } @@ -63,17 +74,37 @@ public void getOnlineFeatures( responseObserver.onCompleted(); } })); + + // Mock Authentication interceptor will flag authenticated request by setting isAuthenticated to + // true. + private ServerInterceptor mockAuthInterceptor = + new ServerInterceptor() { + @Override + public Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + final Metadata.Key authorizationKey = + Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); + if (headers.containsKey(authorizationKey)) { + isAuthenticated.set(true); + } + return next.startCall(call, headers); + } + }; + private FeastClient client; + private FeastClient authenticatedClient; @Before public void setup() throws Exception { this.grpcRule = new GrpcCleanupRule(); + this.isAuthenticated = new AtomicBoolean(false); // setup fake serving service String serverName = InProcessServerBuilder.generateName(); this.grpcRule.register( InProcessServerBuilder.forName(serverName) .directExecutor() .addService(this.servingMock) + .intercept(mockAuthInterceptor) .build() .start()); @@ -81,13 +112,26 @@ public void setup() throws Exception { ManagedChannel channel = this.grpcRule.register( InProcessChannelBuilder.forName(serverName).directExecutor().build()); - this.client = new FeastClient(channel); + this.client = new FeastClient(channel, Optional.empty()); + this.authenticatedClient = + new FeastClient(channel, Optional.of(new JwtCallCredentials(AUTH_TOKEN))); } @Test public void shouldGetOnlineFeatures() { + shouldGetOnlineFeaturesWithClient(this.client); + } + + @Test + public void shouldAuthenticateAndGetOnlineFeatures() { + isAuthenticated.set(false); + shouldGetOnlineFeaturesWithClient(this.authenticatedClient); + assertEquals(isAuthenticated.get(), true); + } + + private void shouldGetOnlineFeaturesWithClient(FeastClient client) { List rows = - this.client.getOnlineFeatures( + client.getOnlineFeatures( Arrays.asList("driver:name", "rating", "null_value"), Arrays.asList( Row.create().set("driver_id", 1).setEntityTimestamp(Instant.ofEpochSecond(100))), From 3348f4461e3d7a610f6de6674d4001bfccb52b3c Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Tue, 1 Sep 2020 12:07:57 +0800 Subject: [PATCH 10/14] Fix issue where GrpcMessageInterceptor obtains subject claim from authorization instead of authentication options. --- .../common/logging/interceptors/GrpcMessageInterceptor.java | 2 +- sdk/java/src/main/java/com/gojek/feast/FeastClient.java | 6 ++++++ serving/src/main/resources/application.yml | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java b/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java index da43a372d73..9bbf20055b4 100644 --- a/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java +++ b/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java @@ -115,7 +115,7 @@ public void onMessage(ReqT message) { private String getIdentity(Authentication authentication) { // use subject claim as identity if set in security authorization properties if (securityProperties != null) { - Map options = securityProperties.getAuthorization().getOptions(); + Map options = securityProperties.getAuthentication().getOptions(); if (options.containsKey(AuthenticationProperties.SUBJECT_CLAIM)) { return AuthUtils.getSubjectFromAuth( authentication, options.get(AuthenticationProperties.SUBJECT_CLAIM)); diff --git a/sdk/java/src/main/java/com/gojek/feast/FeastClient.java b/sdk/java/src/main/java/com/gojek/feast/FeastClient.java index eb777c2c9f0..0d7a6bdf47b 100644 --- a/sdk/java/src/main/java/com/gojek/feast/FeastClient.java +++ b/sdk/java/src/main/java/com/gojek/feast/FeastClient.java @@ -71,6 +71,12 @@ public static FeastClient createAuthenticated( return new FeastClient(channel, Optional.of(credentials)); } + /** + * Obtain info about Feast Serving. + * + * @return {@link feast.proto.serving.ServingAPIProto.GetFeastServingInfoResponse} containing + * Feast version, Serving type etc. + */ public GetFeastServingInfoResponse getFeastServingInfo() { return stub.getFeastServingInfo(GetFeastServingInfoRequest.newBuilder().build()); } diff --git a/serving/src/main/resources/application.yml b/serving/src/main/resources/application.yml index 803059d247c..aec3960ec9e 100644 --- a/serving/src/main/resources/application.yml +++ b/serving/src/main/resources/application.yml @@ -28,6 +28,8 @@ feast: provider: jwt options: jwkEndpointURI: "https://www.googleapis.com/oauth2/v3/certs" + subjectClaim: email + authorization: enabled: false provider: http From 1e6d930d477e6f1bb5b432211a463673b1795ce6 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Tue, 1 Sep 2020 15:35:02 +0800 Subject: [PATCH 11/14] Fix unhandled IllegalStateException throw by getSubjectFromAuth in GrpcMessageInterceptor --- .../logging/interceptors/GrpcMessageInterceptor.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java b/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java index 9bbf20055b4..65c3e549bd7 100644 --- a/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java +++ b/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java @@ -117,8 +117,13 @@ private String getIdentity(Authentication authentication) { if (securityProperties != null) { Map options = securityProperties.getAuthentication().getOptions(); if (options.containsKey(AuthenticationProperties.SUBJECT_CLAIM)) { - return AuthUtils.getSubjectFromAuth( - authentication, options.get(AuthenticationProperties.SUBJECT_CLAIM)); + try { + return AuthUtils.getSubjectFromAuth( + authentication, options.get(AuthenticationProperties.SUBJECT_CLAIM)); + } catch(IllegalStateException e) { + // could not extract claim, revert to authenticated name. + return authentication.getName(); + } } } return authentication.getName(); From 8c92c5bd42ecdf8d5e9f323f807b9843905ba8f4 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Tue, 1 Sep 2020 16:18:39 +0800 Subject: [PATCH 12/14] Fix lint --- .../common/logging/interceptors/GrpcMessageInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java b/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java index 65c3e549bd7..37fd33f24f0 100644 --- a/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java +++ b/common/src/main/java/feast/common/logging/interceptors/GrpcMessageInterceptor.java @@ -120,7 +120,7 @@ private String getIdentity(Authentication authentication) { try { return AuthUtils.getSubjectFromAuth( authentication, options.get(AuthenticationProperties.SUBJECT_CLAIM)); - } catch(IllegalStateException e) { + } catch (IllegalStateException e) { // could not extract claim, revert to authenticated name. return authentication.getName(); } From 9571064cd784fe3add4098983af45a6bd5bd8e47 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Fri, 4 Sep 2020 09:04:02 +0800 Subject: [PATCH 13/14] Fix typo in GoogleAuthCredentials docs --- .../feast/common/auth/credentials/GoogleAuthCredentials.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/feast/common/auth/credentials/GoogleAuthCredentials.java b/common/src/main/java/feast/common/auth/credentials/GoogleAuthCredentials.java index 4c096877dc9..0f42325c5ac 100644 --- a/common/src/main/java/feast/common/auth/credentials/GoogleAuthCredentials.java +++ b/common/src/main/java/feast/common/auth/credentials/GoogleAuthCredentials.java @@ -41,7 +41,7 @@ public class GoogleAuthCredentials extends CallCredentials { Metadata.Key.of("Authorization", ASCII_STRING_MARSHALLER); /** - * Constructa new GoogleAuthCredentials with given options. + * Construct a new GoogleAuthCredentials with given options. * * @param options a map of options, Required unless specified: audience - Optional, Sets the * target audience of the token obtained. From 918543eba0b4430eeee319cbb53a2356b6fcd365 Mon Sep 17 00:00:00 2001 From: Zhu Zhanyan Date: Fri, 4 Sep 2020 16:08:12 +0800 Subject: [PATCH 14/14] Rename 'oAuth' to 'oauth' to make naming less weird. --- sdk/go/auth.go | 10 +++++----- sdk/go/auth_test.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/go/auth.go b/sdk/go/auth.go index 143a74dba27..156b76e1b20 100644 --- a/sdk/go/auth.go +++ b/sdk/go/auth.go @@ -72,7 +72,7 @@ func NewGoogleCredential(audience string) (*Credential, error) { // clientId, clientSecret - Client credentials used to authenticate the client when obtaining credentials. // endpointURL - target URL of the OAuth endpoint to make the OAuth request to. func NewOAuthCredential(audience string, clientId string, clientSecret string, endpointURL *url.URL) *Credential { - tokenSrc := &oAuthTokenSource{ + tokenSrc := &oauthTokenSource{ clientId: clientId, clientSecret: clientSecret, endpointURL: endpointURL, @@ -82,7 +82,7 @@ func NewOAuthCredential(audience string, clientId string, clientSecret string, e } // Defines a Token Source that obtains tokens via making a OAuth client credentials request. -type oAuthTokenSource struct { +type oauthTokenSource struct { clientId string clientSecret string endpointURL *url.URL @@ -91,7 +91,7 @@ type oAuthTokenSource struct { } // Defines a Oauth cleint credentials request. -type oAuthClientCredientialsRequest struct { +type oauthClientCredientialsRequest struct { GrantType string `json:"grant_type"` ClientId string `json:"client_id"` ClientSecret string `json:"client_secret"` @@ -99,10 +99,10 @@ type oAuthClientCredientialsRequest struct { } // Obtain or Refresh token from OAuth Token Source. -func (tokenSrc *oAuthTokenSource) Token() (*oauth2.Token, error) { +func (tokenSrc *oauthTokenSource) Token() (*oauth2.Token, error) { if tokenSrc.token == nil || !tokenSrc.token.Valid() { // Refresh Oauth Id token by making Oauth client credentials request - req := &oAuthClientCredientialsRequest{ + req := &oauthClientCredientialsRequest{ GrantType: "client_credentials", ClientId: tokenSrc.clientId, ClientSecret: tokenSrc.clientSecret, diff --git a/sdk/go/auth_test.go b/sdk/go/auth_test.go index 32226586229..7a6ee3d4f90 100644 --- a/sdk/go/auth_test.go +++ b/sdk/go/auth_test.go @@ -58,7 +58,7 @@ func mockOAuthCredential(token string, audience string) (*httptest.Server, *Cred resp.WriteHeader(http.StatusBadRequest) } - oauthReq := oAuthClientCredientialsRequest{} + oauthReq := oauthClientCredientialsRequest{} err = json.Unmarshal(reqBytes, &oauthReq) if err != nil { resp.WriteHeader(http.StatusBadRequest)