Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose internal/transport.GetConnection #5689

Closed
matzf opened this issue Oct 5, 2022 · 3 comments
Closed

Expose internal/transport.GetConnection #5689

matzf opened this issue Oct 5, 2022 · 3 comments
Assignees
Labels
Status: Requires Reporter Clarification Type: Feature New features or improvements in behavior

Comments

@matzf
Copy link

matzf commented Oct 5, 2022

Use case(s) - what problem will this feature solve?

The request context contains the net.Conn connection object, but it's inaccessible for users of the library because the corresponding func GetConnection(context.Context) net.Con function is in the internal internal/transport package.

Accessing the connection object allows accessing advanced connection information. In particular, if the underlying connection is a TLS session (i.e. we're not using grpc-go's TLS, but the TLS is on the underlying connection), this allows accessing the session state so that it can be injected into the requests peer information.

Specifically, in our case we (have to) use QUIC underneath of grpc. We provide a Listener implementation that creates QUIC connections with a single stream, over which the grpc library then runs http2 (yes, this is a bit weird, but it's seemingly the best we can get without #906). We want to use the TLS state of the QUIC session for peer authentication and authorization. Accessing the connection object from an interceptor or service implementation is the only missing puzzle piece to make this work.

Proposed Solution

Expose GetConnection(context.Context) net.Conn from a suitable non-internal package in the grpc library.

Alternatives Considered

We have a workaround; we smuggle the net.Conn reference out via the net.Addr returned from our connections RemoteAddr() net.Addr method. We can then extract this from the peer.Peer.Addr in a bespoke interceptor. This is obviously a bit a of a hack.

Additional Context

This can be considered a workaround for #906.

@matzf matzf added the Type: Feature New features or improvements in behavior label Oct 5, 2022
@dfawley
Copy link
Member

dfawley commented Oct 11, 2022

Did you consider implementing a credentials.TransportCredentials and storing the state you need in the AuthInfo returned to grpc, which is also provided in the peer? That seems like the right way to think about this architecturally. Even if the credentials don't implement handshaking, that is the right place to pull out connection-related security information to provide to application code. We don't really want to be providing direct access to net.Conns to that same type of code.

@matzf
Copy link
Author

matzf commented Oct 12, 2022

Thanks @dfawley, that sounds like an excellent suggestion! We've indeed not previously been aware of this option. I'll try it out and if it works I'll close this issue.

@matzf
Copy link
Author

matzf commented Oct 12, 2022

Works like a charm 🎉, thanks again @dfawley.


For future reference, here is the gist of our implementation:

type ConnectionStater interface {
	ConnectionState() tls.ConnectionState
}

// PassThroughCredentials implements the grpc/credentials.TransportCredentials interface.
// It allows to pass the TLS connection state of an underlying TLS connection,
// e.g. from an underlying QUIC session, into the grpc stack. This allows accessing
// this information in the context of grpc/peer.Peer.AuthInfo.
// The handshake methods only extract the TLS state and otherwise simply
// pass-through the underlying connection object. The underlying connection
// must implement the ConnectionStater interface.
type PassThroughCredentials struct{}

func (c PassThroughCredentials) ClientHandshake(ctx context.Context, authority string, conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
	authInfo := credentials.TLSInfo{
		State: conn.(ConnectionStater).ConnectionState(),
	}
	return conn, authInfo, nil
}

func (c PassThroughCredentials) ServerHandshake(conn net.Conn) (net.Conn, credentials.AuthInfo, error) {
	authInfo := credentials.TLSInfo{
		State: conn.(ConnectionStater).ConnectionState(),
	}
	return conn, authInfo, nil
}

func (c PassThroughCredentials) Info() credentials.ProtocolInfo {
	return credentials.ProtocolInfo{
		SecurityProtocol: "tls", // copied from grpc/credentials.tlsCreds.Info.
		SecurityVersion:  "1.2", // ditto
		ServerName:       "",    // ?
	}
}

func (c PassThroughCredentials) Clone() credentials.TransportCredentials {
	return PassThroughCredentials{}
}

func (c PassThroughCredentials) OverrideServerName(string) error {
	panic("not implemented")
}

This can then be used in the setup of the server:

grpc.NewServer(
	grpc.Creds(PassThroughCredentials{}),
	...
)
// use with a listener that returns a TLS connection

and in the client:

grpc.DialContext(ctx, addr.String(),
	grpc.WithTransportCredentials(PassThroughCredentials{}),
	...
	// use a dialer that returns a TLS connection
)

@matzf matzf closed this as completed Oct 12, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 11, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Status: Requires Reporter Clarification Type: Feature New features or improvements in behavior
Projects
None yet
Development

No branches or pull requests

2 participants