-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(auth): add grpctransport package (#8625)
This package is the analog to https://pkg.go.dev/google.golang.org/api/transport/grpc.
- Loading branch information
Showing
17 changed files
with
1,652 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Copyright 2023 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//go:build linux | ||
// +build linux | ||
|
||
package grpctransport | ||
|
||
import ( | ||
"context" | ||
"net" | ||
"syscall" | ||
|
||
"google.golang.org/grpc" | ||
) | ||
|
||
const ( | ||
// defaultTCPUserTimeout is the default TCP_USER_TIMEOUT socket option. By | ||
// default is 20 seconds. | ||
tcpUserTimeoutMilliseconds = 20000 | ||
|
||
// Copied from golang.org/x/sys/unix.TCP_USER_TIMEOUT. | ||
tcpUserTimeoutOp = 0x12 | ||
) | ||
|
||
func init() { | ||
// timeoutDialerOption is a grpc.DialOption that contains dialer with | ||
// socket option TCP_USER_TIMEOUT. This dialer requires go versions 1.11+. | ||
timeoutDialerOption = grpc.WithContextDialer(dialTCPUserTimeout) | ||
} | ||
|
||
func dialTCPUserTimeout(ctx context.Context, addr string) (net.Conn, error) { | ||
control := func(network, address string, c syscall.RawConn) error { | ||
var syscallErr error | ||
controlErr := c.Control(func(fd uintptr) { | ||
syscallErr = syscall.SetsockoptInt( | ||
int(fd), syscall.IPPROTO_TCP, tcpUserTimeoutOp, tcpUserTimeoutMilliseconds) | ||
}) | ||
if syscallErr != nil { | ||
return syscallErr | ||
} | ||
if controlErr != nil { | ||
return controlErr | ||
} | ||
return nil | ||
} | ||
d := &net.Dialer{ | ||
Control: control, | ||
} | ||
return d.DialContext(ctx, "tcp", addr) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright 2023 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//go:build linux | ||
// +build linux | ||
|
||
package grpctransport | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"net" | ||
"syscall" | ||
"testing" | ||
"time" | ||
|
||
"google.golang.org/grpc" | ||
) | ||
|
||
func TestDialTCPUserTimeout(t *testing.T) { | ||
l, err := net.Listen("tcp", ":3000") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer l.Close() | ||
|
||
acceptErrCh := make(chan error, 1) | ||
|
||
go func() { | ||
conn, err := l.Accept() | ||
if err != nil { | ||
acceptErrCh <- err | ||
return | ||
} | ||
defer conn.Close() | ||
|
||
if err := conn.Close(); err != nil { | ||
acceptErrCh <- err | ||
} | ||
}() | ||
|
||
conn, err := dialTCPUserTimeout(context.Background(), ":3000") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer conn.Close() | ||
|
||
timeout, err := getTCPUserTimeout(conn) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if timeout != tcpUserTimeoutMilliseconds { | ||
t.Fatalf("expected %v, got %v", tcpUserTimeoutMilliseconds, timeout) | ||
} | ||
|
||
select { | ||
case err := <-acceptErrCh: | ||
t.Fatalf("Accept failed with: %v", err) | ||
default: | ||
} | ||
} | ||
|
||
func getTCPUserTimeout(conn net.Conn) (int, error) { | ||
tcpConn, ok := conn.(*net.TCPConn) | ||
if !ok { | ||
return 0, fmt.Errorf("conn is not *net.TCPConn. got %T", conn) | ||
} | ||
rawConn, err := tcpConn.SyscallConn() | ||
if err != nil { | ||
return 0, err | ||
} | ||
var timeout int | ||
var syscallErr error | ||
controlErr := rawConn.Control(func(fd uintptr) { | ||
timeout, syscallErr = syscall.GetsockoptInt(int(fd), syscall.IPPROTO_TCP, tcpUserTimeoutOp) | ||
}) | ||
if syscallErr != nil { | ||
return 0, syscallErr | ||
} | ||
if controlErr != nil { | ||
return 0, controlErr | ||
} | ||
return timeout, nil | ||
} | ||
|
||
// Check that tcp timeout dialer overwrites user defined dialer. | ||
func TestDialWithDirectPathEnabled(t *testing.T) { | ||
t.Skip("https://github.com/googleapis/google-api-go-client/issues/790") | ||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) | ||
|
||
userDialer := grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { | ||
t.Error("did not expect a call to user dialer, got one") | ||
cancel() | ||
return nil, errors.New("not expected") | ||
}) | ||
|
||
pool, err := Dial(ctx, true, &Options{ | ||
TokenProvider: staticTP("hey"), | ||
GRPCDialOpts: []grpc.DialOption{userDialer}, | ||
Endpoint: "example.google.com:443", | ||
InternalOptions: &InternalOptions{ | ||
EnableDirectPath: true, | ||
}, | ||
}) | ||
if err != nil { | ||
t.Errorf("DialGRPC: error %v, want nil", err) | ||
} | ||
defer pool.Close() | ||
|
||
// gRPC doesn't connect before the first call. | ||
grpc.Invoke(ctx, "foo", nil, nil, pool.Connection()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Copyright 2023 Google LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package grpctransport | ||
|
||
import ( | ||
"context" | ||
"net" | ||
"os" | ||
"strconv" | ||
"strings" | ||
|
||
"cloud.google.com/go/auth" | ||
"cloud.google.com/go/compute/metadata" | ||
"google.golang.org/grpc" | ||
grpcgoogle "google.golang.org/grpc/credentials/google" | ||
) | ||
|
||
func isDirectPathEnabled(endpoint string, opts *Options) bool { | ||
if opts.InternalOptions != nil && !opts.InternalOptions.EnableDirectPath { | ||
return false | ||
} | ||
if !checkDirectPathEndPoint(endpoint) { | ||
return false | ||
} | ||
if b, _ := strconv.ParseBool(os.Getenv(disableDirectPathEnvVar)); b { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
func checkDirectPathEndPoint(endpoint string) bool { | ||
// Only [dns:///]host[:port] is supported, not other schemes (e.g., "tcp://" or "unix://"). | ||
// Also don't try direct path if the user has chosen an alternate name resolver | ||
// (i.e., via ":///" prefix). | ||
if strings.Contains(endpoint, "://") && !strings.HasPrefix(endpoint, "dns:///") { | ||
return false | ||
} | ||
|
||
if endpoint == "" { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func isTokenProviderDirectPathCompatible(tp auth.TokenProvider, opts *Options) bool { | ||
if tp == nil { | ||
return false | ||
} | ||
tok, err := tp.Token(context.Background()) | ||
if err != nil { | ||
return false | ||
} | ||
if tok == nil { | ||
return false | ||
} | ||
if source, _ := tok.Metadata["auth.google.tokenSource"].(string); source != "compute-metadata" { | ||
return false | ||
} | ||
if acct, _ := tok.Metadata["auth.google.serviceAccount"].(string); acct != "default" { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
func isDirectPathXdsUsed(o *Options) bool { | ||
// Method 1: Enable DirectPath xDS by env; | ||
if b, _ := strconv.ParseBool(os.Getenv(enableDirectPathXdsEnvVar)); b { | ||
return true | ||
} | ||
// Method 2: Enable DirectPath xDS by option; | ||
if o.InternalOptions != nil && o.InternalOptions.EnableDirectPathXds { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
// configureDirectPath returns some dial options and an endpoint to use if the | ||
// configuration allows the use of direct path. If it does not the provided | ||
// grpcOpts and endpoint are returned. | ||
func configureDirectPath(grpcOpts []grpc.DialOption, opts *Options, endpoint string, creds auth.TokenProvider) ([]grpc.DialOption, string) { | ||
if isDirectPathEnabled(endpoint, opts) && metadata.OnGCE() && isTokenProviderDirectPathCompatible(creds, opts) { | ||
// Overwrite all of the previously specific DialOptions, DirectPath uses its own set of credentials and certificates. | ||
grpcOpts = []grpc.DialOption{ | ||
grpc.WithCredentialsBundle(grpcgoogle.NewDefaultCredentialsWithOptions(grpcgoogle.DefaultCredentialsOptions{PerRPCCreds: &grpcTokenProvider{TokenProvider: creds}}))} | ||
if timeoutDialerOption != nil { | ||
grpcOpts = append(grpcOpts, timeoutDialerOption) | ||
} | ||
// Check if google-c2p resolver is enabled for DirectPath | ||
if isDirectPathXdsUsed(opts) { | ||
// google-c2p resolver target must not have a port number | ||
if addr, _, err := net.SplitHostPort(endpoint); err == nil { | ||
endpoint = "google-c2p:///" + addr | ||
} else { | ||
endpoint = "google-c2p:///" + endpoint | ||
} | ||
} else { | ||
if !strings.HasPrefix(endpoint, "dns:///") { | ||
endpoint = "dns:///" + endpoint | ||
} | ||
grpcOpts = append(grpcOpts, | ||
// For now all DirectPath go clients will be using the following lb config, but in future | ||
// when different services need different configs, then we should change this to a | ||
// per-service config. | ||
grpc.WithDisableServiceConfig(), | ||
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"grpclb":{"childPolicy":[{"pick_first":{}}]}}]}`)) | ||
} | ||
// TODO: add support for system parameters (quota project, request reason) via chained interceptor. | ||
} | ||
return grpcOpts, endpoint | ||
} |
Oops, something went wrong.