-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
support grpc auth for grpc server. (#168)
* support grpc auth. Signed-off-by: morvencao <[email protected]> * address comments. Signed-off-by: morvencao <[email protected]> --------- Signed-off-by: morvencao <[email protected]>
- Loading branch information
Showing
18 changed files
with
789 additions
and
101 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
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,172 @@ | ||
package server | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/golang/glog" | ||
"github.com/openshift-online/maestro/pkg/client/grpcauthorizer" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/credentials" | ||
"google.golang.org/grpc/metadata" | ||
"google.golang.org/grpc/peer" | ||
"google.golang.org/grpc/status" | ||
) | ||
|
||
// Context key type defined to avoid collisions in other pkgs using context | ||
// See https://golang.org/pkg/context/#WithValue | ||
type contextKey string | ||
|
||
const ( | ||
contextUserKey contextKey = "user" | ||
contextGroupsKey contextKey = "groups" | ||
) | ||
|
||
func newContextWithIdentity(ctx context.Context, user string, groups []string) context.Context { | ||
ctx = context.WithValue(ctx, contextUserKey, user) | ||
return context.WithValue(ctx, contextGroupsKey, groups) | ||
} | ||
|
||
// identityFromCertificate retrieves the user and groups from the client certificate if they are present. | ||
func identityFromCertificate(ctx context.Context) (string, []string, error) { | ||
p, ok := peer.FromContext(ctx) | ||
if !ok { | ||
return "", nil, status.Error(codes.Unauthenticated, "no peer found") | ||
} | ||
|
||
tlsAuth, ok := p.AuthInfo.(credentials.TLSInfo) | ||
if !ok { | ||
return "", nil, status.Error(codes.Unauthenticated, "unexpected peer transport credentials") | ||
} | ||
|
||
if len(tlsAuth.State.VerifiedChains) == 0 || len(tlsAuth.State.VerifiedChains[0]) == 0 { | ||
return "", nil, status.Error(codes.Unauthenticated, "could not verify peer certificate") | ||
} | ||
|
||
if tlsAuth.State.VerifiedChains[0][0] == nil { | ||
return "", nil, status.Error(codes.Unauthenticated, "could not verify peer certificate") | ||
} | ||
|
||
user := tlsAuth.State.VerifiedChains[0][0].Subject.CommonName | ||
groups := tlsAuth.State.VerifiedChains[0][0].Subject.Organization | ||
|
||
if user == "" { | ||
return "", nil, status.Error(codes.Unauthenticated, "could not find user in peer certificate") | ||
} | ||
|
||
if len(groups) == 0 { | ||
return "", nil, status.Error(codes.Unauthenticated, "could not find group in peer certificate") | ||
} | ||
|
||
return user, groups, nil | ||
} | ||
|
||
// identityFromToken retrieves the user and groups from the access token if they are present. | ||
func identityFromToken(ctx context.Context, grpcAuthorizer grpcauthorizer.GRPCAuthorizer) (string, []string, error) { | ||
// Extract the metadata from the context | ||
md, ok := metadata.FromIncomingContext(ctx) | ||
if !ok { | ||
return "", nil, status.Error(codes.InvalidArgument, "missing metadata") | ||
} | ||
|
||
// Extract the access token from the metadata | ||
authorization, ok := md["authorization"] | ||
if !ok || len(authorization) == 0 { | ||
return "", nil, status.Error(codes.Unauthenticated, "invalid token") | ||
} | ||
|
||
token := strings.TrimPrefix(authorization[0], "Bearer ") | ||
// Extract the user and groups from the access token | ||
return grpcAuthorizer.TokenReview(ctx, token) | ||
} | ||
|
||
// newAuthUnaryInterceptor creates a unary interceptor that retrieves the user and groups | ||
// based on the specified authentication type. It supports retrieving from either the access | ||
// token or the client certificate depending on the provided authNType. | ||
// The interceptor then adds the retrieved identity information (user and groups) to the | ||
// context and invokes the provided handler. | ||
func newAuthUnaryInterceptor(authNType string, authorizer grpcauthorizer.GRPCAuthorizer) grpc.UnaryServerInterceptor { | ||
return func( | ||
ctx context.Context, | ||
req interface{}, | ||
info *grpc.UnaryServerInfo, | ||
handler grpc.UnaryHandler, | ||
) (interface{}, error) { | ||
var user string | ||
var groups []string | ||
var err error | ||
switch authNType { | ||
case "token": | ||
user, groups, err = identityFromToken(ctx, authorizer) | ||
if err != nil { | ||
glog.Errorf("unable to get user and groups from token: %v", err) | ||
return nil, err | ||
} | ||
case "mtls": | ||
user, groups, err = identityFromCertificate(ctx) | ||
if err != nil { | ||
glog.Errorf("unable to get user and groups from certificate: %v", err) | ||
return nil, err | ||
} | ||
default: | ||
return nil, fmt.Errorf("unsupported authentication type %s", authNType) | ||
} | ||
|
||
// call the handler with the new context containing the user and groups | ||
return handler(newContextWithIdentity(ctx, user, groups), req) | ||
} | ||
} | ||
|
||
// wrappedStream wraps a grpc.ServerStream associated with an incoming RPC, and | ||
// a custom context containing the user and groups derived from the client certificate | ||
// specified in the incoming RPC metadata | ||
type wrappedStream struct { | ||
grpc.ServerStream | ||
ctx context.Context | ||
} | ||
|
||
func (w *wrappedStream) Context() context.Context { | ||
return w.ctx | ||
} | ||
|
||
func newWrappedStream(ctx context.Context, s grpc.ServerStream) grpc.ServerStream { | ||
return &wrappedStream{s, ctx} | ||
} | ||
|
||
// newAuthStreamInterceptor creates a stream interceptor that retrieves the user and groups | ||
// based on the specified authentication type. It supports retrieving from either the access | ||
// token or the client certificate depending on the provided authNType. | ||
// The interceptor then adds the retrieved identity information (user and groups) to the | ||
// context and invokes the provided handler. | ||
func newAuthStreamInterceptor(authNType string, authorizer grpcauthorizer.GRPCAuthorizer) grpc.StreamServerInterceptor { | ||
return func( | ||
srv interface{}, | ||
ss grpc.ServerStream, | ||
info *grpc.StreamServerInfo, | ||
handler grpc.StreamHandler, | ||
) error { | ||
var user string | ||
var groups []string | ||
var err error | ||
switch authNType { | ||
case "token": | ||
user, groups, err = identityFromToken(ss.Context(), authorizer) | ||
if err != nil { | ||
glog.Errorf("unable to get user and groups from token: %v", err) | ||
return err | ||
} | ||
case "mtls": | ||
user, groups, err = identityFromCertificate(ss.Context()) | ||
if err != nil { | ||
glog.Errorf("unable to get user and groups from certificate: %v", err) | ||
return err | ||
} | ||
default: | ||
return fmt.Errorf("unsupported authentication Type %s", authNType) | ||
} | ||
|
||
return handler(srv, newWrappedStream(newContextWithIdentity(ss.Context(), user, groups), ss)) | ||
} | ||
} |
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
Oops, something went wrong.