Skip to content

Commit 9dbfb73

Browse files
authored
feat(transport): add support for API keys for gprc (#2326)
fixes: #485
1 parent 858fb57 commit 9dbfb73

File tree

2 files changed

+99
-41
lines changed

2 files changed

+99
-41
lines changed

transport/grpc/dial.go

+70-41
Original file line numberDiff line numberDiff line change
@@ -168,52 +168,56 @@ func dial(ctx context.Context, insecure bool, o *internal.DialSettings) (*grpc.C
168168
// when dialing an insecure connection?
169169
if !o.NoAuth && !insecure {
170170
if o.APIKey != "" {
171-
log.Print("API keys are not supported for gRPC APIs. Remove the WithAPIKey option from your client-creating call.")
172-
}
173-
creds, err := internal.Creds(ctx, o)
174-
if err != nil {
175-
return nil, err
176-
}
177-
178-
grpcOpts = append(grpcOpts,
179-
grpc.WithPerRPCCredentials(grpcTokenSource{
180-
TokenSource: oauth.TokenSource{creds.TokenSource},
181-
quotaProject: internal.GetQuotaProject(creds, o.QuotaProject),
171+
grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(grpcAPIKey{
172+
apiKey: o.APIKey,
182173
requestReason: o.RequestReason,
183-
}),
184-
)
185-
186-
// Attempt Direct Path:
187-
logRateLimiter.Do(func() {
188-
logDirectPathMisconfig(endpoint, creds.TokenSource, o)
189-
})
190-
if isDirectPathEnabled(endpoint, o) && isTokenSourceDirectPathCompatible(creds.TokenSource, o) && metadata.OnGCE() {
191-
// Overwrite all of the previously specific DialOptions, DirectPath uses its own set of credentials and certificates.
192-
grpcOpts = []grpc.DialOption{
193-
grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(grpcgoogle.DefaultCredentialsOptions{oauth.TokenSource{creds.TokenSource}}))}
194-
if timeoutDialerOption != nil {
195-
grpcOpts = append(grpcOpts, timeoutDialerOption)
174+
}))
175+
} else {
176+
creds, err := internal.Creds(ctx, o)
177+
if err != nil {
178+
return nil, err
196179
}
197-
// Check if google-c2p resolver is enabled for DirectPath
198-
if isDirectPathXdsUsed(o) {
199-
// google-c2p resolver target must not have a port number
200-
if addr, _, err := net.SplitHostPort(endpoint); err == nil {
201-
endpoint = "google-c2p:///" + addr
202-
} else {
203-
endpoint = "google-c2p:///" + endpoint
180+
grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(grpcTokenSource{
181+
TokenSource: oauth.TokenSource{TokenSource: creds.TokenSource},
182+
quotaProject: internal.GetQuotaProject(creds, o.QuotaProject),
183+
requestReason: o.RequestReason,
184+
}))
185+
// Attempt Direct Path:
186+
logRateLimiter.Do(func() {
187+
logDirectPathMisconfig(endpoint, creds.TokenSource, o)
188+
})
189+
if isDirectPathEnabled(endpoint, o) && isTokenSourceDirectPathCompatible(creds.TokenSource, o) && metadata.OnGCE() {
190+
// Overwrite all of the previously specific DialOptions, DirectPath uses its own set of credentials and certificates.
191+
grpcOpts = []grpc.DialOption{
192+
grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(
193+
grpcgoogle.DefaultCredentialsOptions{
194+
PerRPCCreds: oauth.TokenSource{TokenSource: creds.TokenSource},
195+
})),
204196
}
205-
} else {
206-
if !strings.HasPrefix(endpoint, "dns:///") {
207-
endpoint = "dns:///" + endpoint
197+
if timeoutDialerOption != nil {
198+
grpcOpts = append(grpcOpts, timeoutDialerOption)
208199
}
209-
grpcOpts = append(grpcOpts,
210-
// For now all DirectPath go clients will be using the following lb config, but in future
211-
// when different services need different configs, then we should change this to a
212-
// per-service config.
213-
grpc.WithDisableServiceConfig(),
214-
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`))
200+
// Check if google-c2p resolver is enabled for DirectPath
201+
if isDirectPathXdsUsed(o) {
202+
// google-c2p resolver target must not have a port number
203+
if addr, _, err := net.SplitHostPort(endpoint); err == nil {
204+
endpoint = "google-c2p:///" + addr
205+
} else {
206+
endpoint = "google-c2p:///" + endpoint
207+
}
208+
} else {
209+
if !strings.HasPrefix(endpoint, "dns:///") {
210+
endpoint = "dns:///" + endpoint
211+
}
212+
grpcOpts = append(grpcOpts,
213+
// For now all DirectPath go clients will be using the following lb config, but in future
214+
// when different services need different configs, then we should change this to a
215+
// per-service config.
216+
grpc.WithDisableServiceConfig(),
217+
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`))
218+
}
219+
// TODO(cbro): add support for system parameters (quota project, request reason) via chained interceptor.
215220
}
216-
// TODO(cbro): add support for system parameters (quota project, request reason) via chained interceptor.
217221
}
218222
}
219223

@@ -271,6 +275,31 @@ func (ts grpcTokenSource) GetRequestMetadata(ctx context.Context, uri ...string)
271275
return metadata, nil
272276
}
273277

278+
// grpcAPIKey supplies PerRPCCredentials from an API Key.
279+
type grpcAPIKey struct {
280+
apiKey string
281+
282+
// Additional metadata attached as headers.
283+
requestReason string
284+
}
285+
286+
// GetRequestMetadata gets the request metadata as a map from a grpcAPIKey.
287+
func (ts grpcAPIKey) GetRequestMetadata(ctx context.Context, uri ...string) (
288+
map[string]string, error) {
289+
metadata := map[string]string{
290+
"X-goog-api-key": ts.apiKey,
291+
}
292+
if ts.requestReason != "" {
293+
metadata["X-goog-request-reason"] = ts.requestReason
294+
}
295+
return metadata, nil
296+
}
297+
298+
// RequireTransportSecurity indicates whether the credentials requires transport security.
299+
func (ts grpcAPIKey) RequireTransportSecurity() bool {
300+
return true
301+
}
302+
274303
func isDirectPathEnabled(endpoint string, o *internal.DialSettings) bool {
275304
if !o.EnableDirectPath {
276305
return false

transport/grpc/dial_test.go

+29
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"testing"
1313

1414
"cloud.google.com/go/compute/metadata"
15+
"github.com/google/go-cmp/cmp"
1516
"golang.org/x/oauth2/google"
1617
"google.golang.org/api/internal"
1718
"google.golang.org/grpc"
@@ -143,3 +144,31 @@ func TestLogDirectPathMisconfigNotOnGCE(t *testing.T) {
143144
}
144145

145146
}
147+
148+
func TestGRPCAPIKey_GetRequestMetadata(t *testing.T) {
149+
for _, test := range []struct {
150+
apiKey string
151+
reason string
152+
}{
153+
{
154+
apiKey: "MY_API_KEY",
155+
reason: "MY_REQUEST_REASON",
156+
},
157+
} {
158+
ts := grpcAPIKey{
159+
apiKey: test.apiKey,
160+
requestReason: test.reason,
161+
}
162+
got, err := ts.GetRequestMetadata(context.Background())
163+
if err != nil {
164+
t.Fatal(err)
165+
}
166+
want := map[string]string{
167+
"X-goog-api-key": ts.apiKey,
168+
"X-goog-request-reason": ts.requestReason,
169+
}
170+
if diff := cmp.Diff(want, got); diff != "" {
171+
t.Errorf("mismatch (-want +got):\n%s", diff)
172+
}
173+
}
174+
}

0 commit comments

Comments
 (0)