Skip to content

Commit 69a8347

Browse files
authored
feat(auth): add grpctransport package (#8625)
This package is the analog to https://pkg.go.dev/google.golang.org/api/transport/grpc.
1 parent 852a230 commit 69a8347

17 files changed

+1652
-63
lines changed

auth/go.mod

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@ require (
1010
go.opencensus.io v0.24.0
1111
golang.org/x/net v0.14.0
1212
google.golang.org/grpc v1.57.0
13+
google.golang.org/protobuf v1.31.0
1314
)
1415

1516
require (
1617
cloud.google.com/go/compute v1.19.1 // indirect
1718
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
1819
github.com/golang/protobuf v1.5.3 // indirect
1920
golang.org/x/crypto v0.12.0 // indirect
21+
golang.org/x/oauth2 v0.7.0 // indirect
22+
golang.org/x/sync v0.2.0 // indirect
2023
golang.org/x/sys v0.11.0 // indirect
2124
golang.org/x/text v0.12.0 // indirect
25+
google.golang.org/appengine v1.6.7 // indirect
2226
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
23-
google.golang.org/protobuf v1.31.0 // indirect
2427
)

auth/go.sum

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18h
1818
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
1919
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
2020
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
21+
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
2122
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
2223
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
2324
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -66,21 +67,26 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
6667
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
6768
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
6869
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
70+
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
6971
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
7072
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
7173
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
7274
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
75+
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
76+
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
7377
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7478
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7579
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7680
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
81+
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
7782
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
7883
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
7984
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
8085
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
8186
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
8287
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
8388
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
89+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
8490
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
8591
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
8692
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
@@ -92,6 +98,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
9298
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
9399
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
94100
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
101+
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
102+
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
95103
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
96104
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
97105
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=

auth/grpctransport/dial_socketopt.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//go:build linux
16+
// +build linux
17+
18+
package grpctransport
19+
20+
import (
21+
"context"
22+
"net"
23+
"syscall"
24+
25+
"google.golang.org/grpc"
26+
)
27+
28+
const (
29+
// defaultTCPUserTimeout is the default TCP_USER_TIMEOUT socket option. By
30+
// default is 20 seconds.
31+
tcpUserTimeoutMilliseconds = 20000
32+
33+
// Copied from golang.org/x/sys/unix.TCP_USER_TIMEOUT.
34+
tcpUserTimeoutOp = 0x12
35+
)
36+
37+
func init() {
38+
// timeoutDialerOption is a grpc.DialOption that contains dialer with
39+
// socket option TCP_USER_TIMEOUT. This dialer requires go versions 1.11+.
40+
timeoutDialerOption = grpc.WithContextDialer(dialTCPUserTimeout)
41+
}
42+
43+
func dialTCPUserTimeout(ctx context.Context, addr string) (net.Conn, error) {
44+
control := func(network, address string, c syscall.RawConn) error {
45+
var syscallErr error
46+
controlErr := c.Control(func(fd uintptr) {
47+
syscallErr = syscall.SetsockoptInt(
48+
int(fd), syscall.IPPROTO_TCP, tcpUserTimeoutOp, tcpUserTimeoutMilliseconds)
49+
})
50+
if syscallErr != nil {
51+
return syscallErr
52+
}
53+
if controlErr != nil {
54+
return controlErr
55+
}
56+
return nil
57+
}
58+
d := &net.Dialer{
59+
Control: control,
60+
}
61+
return d.DialContext(ctx, "tcp", addr)
62+
}
+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//go:build linux
16+
// +build linux
17+
18+
package grpctransport
19+
20+
import (
21+
"context"
22+
"errors"
23+
"fmt"
24+
"net"
25+
"syscall"
26+
"testing"
27+
"time"
28+
29+
"google.golang.org/grpc"
30+
)
31+
32+
func TestDialTCPUserTimeout(t *testing.T) {
33+
l, err := net.Listen("tcp", ":3000")
34+
if err != nil {
35+
t.Fatal(err)
36+
}
37+
defer l.Close()
38+
39+
acceptErrCh := make(chan error, 1)
40+
41+
go func() {
42+
conn, err := l.Accept()
43+
if err != nil {
44+
acceptErrCh <- err
45+
return
46+
}
47+
defer conn.Close()
48+
49+
if err := conn.Close(); err != nil {
50+
acceptErrCh <- err
51+
}
52+
}()
53+
54+
conn, err := dialTCPUserTimeout(context.Background(), ":3000")
55+
if err != nil {
56+
t.Fatal(err)
57+
}
58+
defer conn.Close()
59+
60+
timeout, err := getTCPUserTimeout(conn)
61+
if err != nil {
62+
t.Fatal(err)
63+
}
64+
if timeout != tcpUserTimeoutMilliseconds {
65+
t.Fatalf("expected %v, got %v", tcpUserTimeoutMilliseconds, timeout)
66+
}
67+
68+
select {
69+
case err := <-acceptErrCh:
70+
t.Fatalf("Accept failed with: %v", err)
71+
default:
72+
}
73+
}
74+
75+
func getTCPUserTimeout(conn net.Conn) (int, error) {
76+
tcpConn, ok := conn.(*net.TCPConn)
77+
if !ok {
78+
return 0, fmt.Errorf("conn is not *net.TCPConn. got %T", conn)
79+
}
80+
rawConn, err := tcpConn.SyscallConn()
81+
if err != nil {
82+
return 0, err
83+
}
84+
var timeout int
85+
var syscallErr error
86+
controlErr := rawConn.Control(func(fd uintptr) {
87+
timeout, syscallErr = syscall.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, tcpUserTimeoutOp)
88+
})
89+
if syscallErr != nil {
90+
return 0, syscallErr
91+
}
92+
if controlErr != nil {
93+
return 0, controlErr
94+
}
95+
return timeout, nil
96+
}
97+
98+
// Check that tcp timeout dialer overwrites user defined dialer.
99+
func TestDialWithDirectPathEnabled(t *testing.T) {
100+
t.Skip("https://github.com/googleapis/google-api-go-client/issues/790")
101+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
102+
103+
userDialer := grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
104+
t.Error("did not expect a call to user dialer, got one")
105+
cancel()
106+
return nil, errors.New("not expected")
107+
})
108+
109+
pool, err := Dial(ctx, true, &Options{
110+
TokenProvider: staticTP("hey"),
111+
GRPCDialOpts: []grpc.DialOption{userDialer},
112+
Endpoint: "example.google.com:443",
113+
InternalOptions: &InternalOptions{
114+
EnableDirectPath: true,
115+
},
116+
})
117+
if err != nil {
118+
t.Errorf("DialGRPC: error %v, want nil", err)
119+
}
120+
defer pool.Close()
121+
122+
// gRPC doesn't connect before the first call.
123+
grpc.Invoke(ctx, "foo", nil, nil, pool.Connection())
124+
}

auth/grpctransport/directpath.go

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package grpctransport
16+
17+
import (
18+
"context"
19+
"net"
20+
"os"
21+
"strconv"
22+
"strings"
23+
24+
"cloud.google.com/go/auth"
25+
"cloud.google.com/go/compute/metadata"
26+
"google.golang.org/grpc"
27+
grpcgoogle "google.golang.org/grpc/credentials/google"
28+
)
29+
30+
func isDirectPathEnabled(endpoint string, opts *Options) bool {
31+
if opts.InternalOptions != nil && !opts.InternalOptions.EnableDirectPath {
32+
return false
33+
}
34+
if !checkDirectPathEndPoint(endpoint) {
35+
return false
36+
}
37+
if b, _ := strconv.ParseBool(os.Getenv(disableDirectPathEnvVar)); b {
38+
return false
39+
}
40+
return true
41+
}
42+
43+
func checkDirectPathEndPoint(endpoint string) bool {
44+
// Only [dns:///]host[:port] is supported, not other schemes (e.g., "tcp://" or "unix://").
45+
// Also don't try direct path if the user has chosen an alternate name resolver
46+
// (i.e., via ":///" prefix).
47+
if strings.Contains(endpoint, "://") && !strings.HasPrefix(endpoint, "dns:///") {
48+
return false
49+
}
50+
51+
if endpoint == "" {
52+
return false
53+
}
54+
55+
return true
56+
}
57+
58+
func isTokenProviderDirectPathCompatible(tp auth.TokenProvider, opts *Options) bool {
59+
if tp == nil {
60+
return false
61+
}
62+
tok, err := tp.Token(context.Background())
63+
if err != nil {
64+
return false
65+
}
66+
if tok == nil {
67+
return false
68+
}
69+
if source, _ := tok.Metadata["auth.google.tokenSource"].(string); source != "compute-metadata" {
70+
return false
71+
}
72+
if acct, _ := tok.Metadata["auth.google.serviceAccount"].(string); acct != "default" {
73+
return false
74+
}
75+
return true
76+
}
77+
78+
func isDirectPathXdsUsed(o *Options) bool {
79+
// Method 1: Enable DirectPath xDS by env;
80+
if b, _ := strconv.ParseBool(os.Getenv(enableDirectPathXdsEnvVar)); b {
81+
return true
82+
}
83+
// Method 2: Enable DirectPath xDS by option;
84+
if o.InternalOptions != nil && o.InternalOptions.EnableDirectPathXds {
85+
return true
86+
}
87+
return false
88+
}
89+
90+
// configureDirectPath returns some dial options and an endpoint to use if the
91+
// configuration allows the use of direct path. If it does not the provided
92+
// grpcOpts and endpoint are returned.
93+
func configureDirectPath(grpcOpts []grpc.DialOption, opts *Options, endpoint string, creds auth.TokenProvider) ([]grpc.DialOption, string) {
94+
if isDirectPathEnabled(endpoint, opts) && metadata.OnGCE() && isTokenProviderDirectPathCompatible(creds, opts) {
95+
// Overwrite all of the previously specific DialOptions, DirectPath uses its own set of credentials and certificates.
96+
grpcOpts = []grpc.DialOption{
97+
grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(grpcgoogle.DefaultCredentialsOptions{PerRPCCreds: &grpcTokenProvider{TokenProvider: creds}}))}
98+
if timeoutDialerOption != nil {
99+
grpcOpts = append(grpcOpts, timeoutDialerOption)
100+
}
101+
// Check if google-c2p resolver is enabled for DirectPath
102+
if isDirectPathXdsUsed(opts) {
103+
// google-c2p resolver target must not have a port number
104+
if addr, _, err := net.SplitHostPort(endpoint); err == nil {
105+
endpoint = "google-c2p:///" + addr
106+
} else {
107+
endpoint = "google-c2p:///" + endpoint
108+
}
109+
} else {
110+
if !strings.HasPrefix(endpoint, "dns:///") {
111+
endpoint = "dns:///" + endpoint
112+
}
113+
grpcOpts = append(grpcOpts,
114+
// For now all DirectPath go clients will be using the following lb config, but in future
115+
// when different services need different configs, then we should change this to a
116+
// per-service config.
117+
grpc.WithDisableServiceConfig(),
118+
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`))
119+
}
120+
// TODO: add support for system parameters (quota project, request reason) via chained interceptor.
121+
}
122+
return grpcOpts, endpoint
123+
}

0 commit comments

Comments
 (0)