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

authz: create interceptors for gRPC security policy API #4664

Merged
merged 14 commits into from Sep 2, 2021
8 changes: 4 additions & 4 deletions authz/rbac_translator.go
Expand Up @@ -285,7 +285,7 @@ func translatePolicy(policyStr string) ([]*v3rbacpb.RBAC, error) {
if len(policy.AllowRules) == 0 {
return nil, fmt.Errorf(`"allow_rules" is not present`)
}
var RBACPolicies []*v3rbacpb.RBAC
var rbacs []*v3rbacpb.RBAC
ashithasantosh marked this conversation as resolved.
Show resolved Hide resolved
if len(policy.DenyRules) > 0 {
denyPolicies, err := parseRules(policy.DenyRules, policy.Name)
if err != nil {
Expand All @@ -295,13 +295,13 @@ func translatePolicy(policyStr string) ([]*v3rbacpb.RBAC, error) {
Action: v3rbacpb.RBAC_DENY,
Policies: denyPolicies,
}
RBACPolicies = append(RBACPolicies, denyRBAC)
rbacs = append(rbacs, denyRBAC)
}
allowPolicies, err := parseRules(policy.AllowRules, policy.Name)
if err != nil {
return nil, fmt.Errorf(`"allow_rules" %v`, err)
}
allowRBAC := &v3rbacpb.RBAC{Action: v3rbacpb.RBAC_ALLOW, Policies: allowPolicies}
RBACPolicies = append(RBACPolicies, allowRBAC)
return RBACPolicies, nil
rbacs = append(rbacs, allowRBAC)
return rbacs, nil
ashithasantosh marked this conversation as resolved.
Show resolved Hide resolved
}
265 changes: 213 additions & 52 deletions authz/sdk_end2end_test.go
Expand Up @@ -16,14 +16,22 @@
*
*/

package authz
// Package authz_test contains tests for authz.
//
// Experimental
//
// Notice: This package is EXPERIMENTAL and may be changed or removed
// in a later release.
ashithasantosh marked this conversation as resolved.
Show resolved Hide resolved
package authz_test

import (
"context"
"io"
"net"
"testing"

"google.golang.org/grpc"
"google.golang.org/grpc/authz"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
Expand All @@ -38,65 +46,55 @@ func (s *testServer) UnaryCall(ctx context.Context, req *pb.SimpleRequest) (*pb.
return &pb.SimpleResponse{}, nil
}

func startServer(t *testing.T, policy string) string {
i, _ := NewStatic(policy)
serverOpts := []grpc.ServerOption{
grpc.ChainUnaryInterceptor(i.UnaryInterceptor),
grpc.ChainStreamInterceptor(i.StreamInterceptor),
func (s *testServer) StreamingInputCall(stream pb.TestService_StreamingInputCallServer) error {
for {
_, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.StreamingInputCallResponse{})
}
if err != nil {
return err
}
}
lis, err := net.Listen("tcp", ":0")
if err != nil {
t.Fatalf("error listening: %v", err)
}
s := grpc.NewServer(serverOpts...)
pb.RegisterTestServiceServer(s, &testServer{})
go s.Serve(lis)
return lis.Addr().String()
}

func runClient(ctx context.Context, t *testing.T, serverAddr string) (*pb.SimpleResponse, error) {
dialOptions := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithBlock(),
}
clientConn, err := grpc.Dial(serverAddr, dialOptions...)
if err != nil {
t.Fatalf("grpc.Dial(%v, %v) failed: %v", serverAddr, dialOptions, err)
}
defer clientConn.Close()
c := pb.NewTestServiceClient(clientConn)
return c.UnaryCall(ctx, &pb.SimpleRequest{}, grpc.WaitForReady(true))
}

func TestSdkEnd2End(t *testing.T) {
func TestSDKEnd2End(t *testing.T) {
tests := map[string]struct {
authzPolicy string
md metadata.MD
wantStatusCode codes.Code
wantErr string
}{
"DeniesUnauthorizedRpcRequest": {
"DeniesRpcRequestMatchInDenyNoMatchInAllow": {
authzPolicy: `{
"name": "authz",
"allow_rules":
[
{
"name": "allow_all"
"name": "allow_StreamingOutputCall",
"request": {
"paths":
[
"/grpc.testing.TestService/StreamingOutputCall"
]
}
}
],
"deny_rules":
"deny_rules":
[
{
"name": "deny_Echo",
"name": "deny_TestServiceCalls",
"request": {
"paths":
"paths":
[
"/grpc.testing.TestService/UnaryCall"
"/grpc.testing.TestService/UnaryCall",
"/grpc.testing.TestService/StreamingInputCall"
],
"headers":
"headers":
[
{
"key": "key-abc",
"values":
"values":
[
"val-abc",
"val-def"
Expand All @@ -109,29 +107,58 @@ func TestSdkEnd2End(t *testing.T) {
}`,
md: metadata.Pairs("key-abc", "val-abc"),
wantStatusCode: codes.PermissionDenied,
wantErr: "Unauthorized RPC request rejected.",
},
"AllowsAuthorizedRpcRequest": {
"DeniesRpcRequestMatchInDenyAndAllow": {
authzPolicy: `{
"name": "authz",
"allow_rules":
"allow_rules":
[
{
"name": "allow_Echo",
"request":
{
"paths":
"name": "allow_TestServiceCalls",
"request": {
"paths":
[
"/grpc.testing.TestService/UnaryCall"
"/grpc.testing.TestService/*"
]
}
}
],
"deny_rules":
"deny_rules":
[
{
"name": "deny_all",
"request":
{
"name": "deny_TestServiceCalls",
"request": {
"paths":
[
"/grpc.testing.TestService/*"
]
}
}
]
}`,
wantStatusCode: codes.PermissionDenied,
wantErr: "Unauthorized RPC request rejected.",
},
"AllowsRpcRequestNoMatchInDenyMatchInAllow": {
authzPolicy: `{
"name": "authz",
"allow_rules":
[
{
"name": "allow_all"
}
],
"deny_rules":
[
{
"name": "deny_TestServiceCalls",
"request": {
"paths":
[
"/grpc.testing.TestService/UnaryCall",
"/grpc.testing.TestService/StreamingInputCall"
],
"headers":
[
{
Expand All @@ -150,15 +177,149 @@ func TestSdkEnd2End(t *testing.T) {
md: metadata.Pairs("key-xyz", "val-xyz"),
wantStatusCode: codes.OK,
},
"AllowsRpcRequestNoMatchInDenyAndAllow": {
authzPolicy: `{
"name": "authz",
"allow_rules":
[
{
"name": "allow_some_user",
"source": {
"principals":
[
"some_user"
]
}
}
],
"deny_rules":
[
{
"name": "deny_StreamingOutputCall",
"request": {
"paths":
[
"/grpc.testing.TestService/StreamingOutputCall"
]
}
}
]
}`,
wantStatusCode: codes.PermissionDenied,
wantErr: "Unauthorized RPC request rejected.",
},
"AllowsRpcRequestEmptyDenyMatchInAllow": {
authzPolicy: `{
"name": "authz",
"allow_rules":
[
{
"name": "allow_UnaryCall",
"request":
{
"paths":
[
"/grpc.testing.TestService/UnaryCall"
]
}
},
{
"name": "allow_StreamingInputCall",
"request":
{
"paths":
[
"/grpc.testing.TestService/StreamingInputCall"
]
}
}
]
}`,
wantStatusCode: codes.OK,
},
"DeniesRpcRequestEmptyDenyNoMatchInAllow": {
authzPolicy: `{
"name": "authz",
"allow_rules":
[
{
"name": "allow_StreamingOutputCall",
"request":
{
"paths":
[
"/grpc.testing.TestService/StreamingOutputCall"
]
}
}
]
}`,
wantStatusCode: codes.PermissionDenied,
wantErr: "Unauthorized RPC request rejected.",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
serverAddr := startServer(t, test.authzPolicy)
// Start a gRPC server with SDK unary and stream server interceptors.
i, _ := authz.NewStatic(test.authzPolicy)
serverOpts := []grpc.ServerOption{
grpc.ChainUnaryInterceptor(i.UnaryInterceptor),
grpc.ChainStreamInterceptor(i.StreamInterceptor),
}
lis, err := net.Listen("tcp", ":0")
ashithasantosh marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
t.Fatalf("error listening: %v", err)
}
s := grpc.NewServer(serverOpts...)
pb.RegisterTestServiceServer(s, &testServer{})
go s.Serve(lis)

// Establish a connection to the server.
dialOptions := []grpc.DialOption{
ashithasantosh marked this conversation as resolved.
Show resolved Hide resolved
grpc.WithInsecure(),
grpc.WithBlock(),
ashithasantosh marked this conversation as resolved.
Show resolved Hide resolved
}
clientConn, err := grpc.Dial(lis.Addr().String(), dialOptions...)
if err != nil {
t.Fatalf("grpc.Dial(%v, %v) failed: %v", lis.Addr().String(), dialOptions, err)
}
defer clientConn.Close()
client := pb.NewTestServiceClient(clientConn)

ctx := metadata.NewOutgoingContext(context.Background(), test.md)
_, err := runClient(ctx, t, serverAddr)
if gotStatusCode := status.Code(err); gotStatusCode != test.wantStatusCode {
t.Fatalf("unexpected authorization decision. status code want:%v got:%v", test.wantStatusCode, gotStatusCode)

// Verifying authorization decision for Unary RPC.
_, err = client.UnaryCall(ctx, &pb.SimpleRequest{}, grpc.WaitForReady(true))
ashithasantosh marked this conversation as resolved.
Show resolved Hide resolved
gotStatus, _ := status.FromError(err)
dfawley marked this conversation as resolved.
Show resolved Hide resolved
if gotStatus.Code() != test.wantStatusCode {
t.Fatalf("[UnaryCall] status code want:%v got:%v", test.wantStatusCode, gotStatus.Code())
}
if gotStatus.Message() != test.wantErr {
t.Fatalf("[UnaryCall] error message want:%v got:%v", test.wantErr, gotStatus.Message())
}

// Verifying authorization decision for Streaming RPC.
stream, err := client.StreamingInputCall(ctx, grpc.WaitForReady(true))
if err != nil {
t.Fatalf("failed StreamingInputCall err: %v", err)
}
req := &pb.StreamingInputCallRequest{
Payload: &pb.Payload{
Body: []byte("hi"),
},
}
if err := stream.Send(req); err != nil {
t.Fatalf("failed stream.Send err: %v", err)
}
_, err = stream.CloseAndRecv()
gotStatus, _ = status.FromError(err)
if gotStatus.Code() != test.wantStatusCode {
t.Fatalf("[StreamingCall] status code want:%v got:%v", test.wantStatusCode, gotStatus.Code())
}
if gotStatus.Message() != test.wantErr {
t.Fatalf("[StreamingCall] error message want:%v got:%v", test.wantErr, gotStatus.Message())
}

})
}
}