mirror of https://github.com/hashicorp/consul
86 lines
2.7 KiB
Go
86 lines
2.7 KiB
Go
|
package middleware
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/hashicorp/consul/tlsutil"
|
||
|
"google.golang.org/grpc"
|
||
|
"google.golang.org/grpc/codes"
|
||
|
"google.golang.org/grpc/credentials"
|
||
|
"google.golang.org/grpc/peer"
|
||
|
"google.golang.org/grpc/status"
|
||
|
)
|
||
|
|
||
|
const AllowedPeerEndpointPrefix = "/hashicorp.consul.internal.peerstream.PeerStreamService/"
|
||
|
|
||
|
// AuthInterceptor provides gRPC interceptors for restricting endpoint access based
|
||
|
// on SNI. If the connection is plaintext, this filter will not activate, and the
|
||
|
// connection will be allowed to proceed.
|
||
|
type AuthInterceptor struct {
|
||
|
TLS *tlsutil.Configurator
|
||
|
Logger Logger
|
||
|
}
|
||
|
|
||
|
// InterceptUnary prevents non-streaming gRPC calls from calling certain endpoints,
|
||
|
// based on the SNI information.
|
||
|
func (a *AuthInterceptor) InterceptUnary(
|
||
|
ctx context.Context,
|
||
|
req interface{},
|
||
|
info *grpc.UnaryServerInfo,
|
||
|
handler grpc.UnaryHandler,
|
||
|
) (interface{}, error) {
|
||
|
p, ok := peer.FromContext(ctx)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("unable to fetch peer info from grpc context")
|
||
|
}
|
||
|
err := restrictPeeringEndpoints(p.AuthInfo, a.TLS.PeeringServerName(), info.FullMethod)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return handler(ctx, req)
|
||
|
}
|
||
|
|
||
|
// InterceptUnary prevents streaming gRPC calls from calling certain endpoints,
|
||
|
// based on the SNI information.
|
||
|
func (a *AuthInterceptor) InterceptStream(
|
||
|
srv interface{},
|
||
|
ss grpc.ServerStream,
|
||
|
info *grpc.StreamServerInfo,
|
||
|
handler grpc.StreamHandler,
|
||
|
) error {
|
||
|
p, ok := peer.FromContext(ss.Context())
|
||
|
if !ok {
|
||
|
return fmt.Errorf("unable to fetch peer info from grpc context")
|
||
|
}
|
||
|
err := restrictPeeringEndpoints(p.AuthInfo, a.TLS.PeeringServerName(), info.FullMethod)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return handler(srv, ss)
|
||
|
}
|
||
|
|
||
|
// restrictPeeringEndpoints will return an error if a peering TLS connection attempts to call
|
||
|
// a non-peering endpoint. This is necessary, because the peer streaming workflow does not
|
||
|
// present a mutual TLS certificate, and is allowed to bypass the `tls.grpc.verify_incoming`
|
||
|
// check as a special case. See the `tlsutil.Configurator` for this bypass.
|
||
|
func restrictPeeringEndpoints(authInfo credentials.AuthInfo, peeringSNI string, endpoint string) error {
|
||
|
// This indicates a plaintext connection.
|
||
|
if authInfo == nil {
|
||
|
return nil
|
||
|
}
|
||
|
// Otherwise attempt to check the AuthInfo for TLS credentials.
|
||
|
tlsAuth, ok := authInfo.(credentials.TLSInfo)
|
||
|
if !ok {
|
||
|
return status.Error(codes.Unauthenticated, "invalid transport credentials")
|
||
|
}
|
||
|
if tlsAuth.State.ServerName == peeringSNI {
|
||
|
// Prevent any calls, except those in the PeerStreamService
|
||
|
if !strings.HasPrefix(endpoint, AllowedPeerEndpointPrefix) {
|
||
|
return status.Error(codes.PermissionDenied, "invalid permissions to the specified endpoint")
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|