-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: morvencao <[email protected]>
- Loading branch information
Showing
6 changed files
with
178 additions
and
144 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,169 @@ | ||
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 new unary interceptor that retrieves the user and groups from | ||
// the access token in the incoming RPC context. If retrieval from the token fails, it falls back to | ||
// retrieving the user and groups from the client certificate. | ||
// It then creates a new context with the retrieved user and groups before invoking 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 new stream interceptor that looks up the client certificate from the incoming RPC context, | ||
// retrieves the user and groups from it and creates a new context with the user and groups before invoking the provided handler. | ||
// otherwise, it falls back retrieving the user and groups from the access token. | ||
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
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