1/* 2 * 3 * Copyright 2021 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19package authz_test 20 21import ( 22 "context" 23 "io" 24 "net" 25 "testing" 26 "time" 27 28 "google.golang.org/grpc" 29 "google.golang.org/grpc/authz" 30 "google.golang.org/grpc/codes" 31 "google.golang.org/grpc/metadata" 32 "google.golang.org/grpc/status" 33 pb "google.golang.org/grpc/test/grpc_testing" 34) 35 36type testServer struct { 37 pb.UnimplementedTestServiceServer 38} 39 40func (s *testServer) UnaryCall(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) { 41 return &pb.SimpleResponse{}, nil 42} 43 44func (s *testServer) StreamingInputCall(stream pb.TestService_StreamingInputCallServer) error { 45 for { 46 _, err := stream.Recv() 47 if err == io.EOF { 48 return stream.SendAndClose(&pb.StreamingInputCallResponse{}) 49 } 50 if err != nil { 51 return err 52 } 53 } 54} 55 56func TestSDKEnd2End(t *testing.T) { 57 tests := map[string]struct { 58 authzPolicy string 59 md metadata.MD 60 wantStatusCode codes.Code 61 wantErr string 62 }{ 63 "DeniesRpcRequestMatchInDenyNoMatchInAllow": { 64 authzPolicy: `{ 65 "name": "authz", 66 "allow_rules": 67 [ 68 { 69 "name": "allow_StreamingOutputCall", 70 "request": { 71 "paths": 72 [ 73 "/grpc.testing.TestService/StreamingOutputCall" 74 ] 75 } 76 } 77 ], 78 "deny_rules": 79 [ 80 { 81 "name": "deny_TestServiceCalls", 82 "request": { 83 "paths": 84 [ 85 "/grpc.testing.TestService/UnaryCall", 86 "/grpc.testing.TestService/StreamingInputCall" 87 ], 88 "headers": 89 [ 90 { 91 "key": "key-abc", 92 "values": 93 [ 94 "val-abc", 95 "val-def" 96 ] 97 } 98 ] 99 } 100 } 101 ] 102 }`, 103 md: metadata.Pairs("key-abc", "val-abc"), 104 wantStatusCode: codes.PermissionDenied, 105 wantErr: "unauthorized RPC request rejected", 106 }, 107 "DeniesRpcRequestMatchInDenyAndAllow": { 108 authzPolicy: `{ 109 "name": "authz", 110 "allow_rules": 111 [ 112 { 113 "name": "allow_TestServiceCalls", 114 "request": { 115 "paths": 116 [ 117 "/grpc.testing.TestService/*" 118 ] 119 } 120 } 121 ], 122 "deny_rules": 123 [ 124 { 125 "name": "deny_TestServiceCalls", 126 "request": { 127 "paths": 128 [ 129 "/grpc.testing.TestService/*" 130 ] 131 } 132 } 133 ] 134 }`, 135 wantStatusCode: codes.PermissionDenied, 136 wantErr: "unauthorized RPC request rejected", 137 }, 138 "AllowsRpcRequestNoMatchInDenyMatchInAllow": { 139 authzPolicy: `{ 140 "name": "authz", 141 "allow_rules": 142 [ 143 { 144 "name": "allow_all" 145 } 146 ], 147 "deny_rules": 148 [ 149 { 150 "name": "deny_TestServiceCalls", 151 "request": { 152 "paths": 153 [ 154 "/grpc.testing.TestService/UnaryCall", 155 "/grpc.testing.TestService/StreamingInputCall" 156 ], 157 "headers": 158 [ 159 { 160 "key": "key-abc", 161 "values": 162 [ 163 "val-abc", 164 "val-def" 165 ] 166 } 167 ] 168 } 169 } 170 ] 171 }`, 172 md: metadata.Pairs("key-xyz", "val-xyz"), 173 wantStatusCode: codes.OK, 174 }, 175 "AllowsRpcRequestNoMatchInDenyAndAllow": { 176 authzPolicy: `{ 177 "name": "authz", 178 "allow_rules": 179 [ 180 { 181 "name": "allow_some_user", 182 "source": { 183 "principals": 184 [ 185 "some_user" 186 ] 187 } 188 } 189 ], 190 "deny_rules": 191 [ 192 { 193 "name": "deny_StreamingOutputCall", 194 "request": { 195 "paths": 196 [ 197 "/grpc.testing.TestService/StreamingOutputCall" 198 ] 199 } 200 } 201 ] 202 }`, 203 wantStatusCode: codes.PermissionDenied, 204 wantErr: "unauthorized RPC request rejected", 205 }, 206 "AllowsRpcRequestEmptyDenyMatchInAllow": { 207 authzPolicy: `{ 208 "name": "authz", 209 "allow_rules": 210 [ 211 { 212 "name": "allow_UnaryCall", 213 "request": 214 { 215 "paths": 216 [ 217 "/grpc.testing.TestService/UnaryCall" 218 ] 219 } 220 }, 221 { 222 "name": "allow_StreamingInputCall", 223 "request": 224 { 225 "paths": 226 [ 227 "/grpc.testing.TestService/StreamingInputCall" 228 ] 229 } 230 } 231 ] 232 }`, 233 wantStatusCode: codes.OK, 234 }, 235 "DeniesRpcRequestEmptyDenyNoMatchInAllow": { 236 authzPolicy: `{ 237 "name": "authz", 238 "allow_rules": 239 [ 240 { 241 "name": "allow_StreamingOutputCall", 242 "request": 243 { 244 "paths": 245 [ 246 "/grpc.testing.TestService/StreamingOutputCall" 247 ] 248 } 249 } 250 ] 251 }`, 252 wantStatusCode: codes.PermissionDenied, 253 wantErr: "unauthorized RPC request rejected", 254 }, 255 } 256 for name, test := range tests { 257 t.Run(name, func(t *testing.T) { 258 // Start a gRPC server with SDK unary and stream server interceptors. 259 i, _ := authz.NewStatic(test.authzPolicy) 260 lis, err := net.Listen("tcp", "localhost:0") 261 if err != nil { 262 t.Fatalf("error listening: %v", err) 263 } 264 s := grpc.NewServer( 265 grpc.ChainUnaryInterceptor(i.UnaryInterceptor), 266 grpc.ChainStreamInterceptor(i.StreamInterceptor)) 267 pb.RegisterTestServiceServer(s, &testServer{}) 268 go s.Serve(lis) 269 270 // Establish a connection to the server. 271 clientConn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure()) 272 if err != nil { 273 t.Fatalf("grpc.Dial(%v) failed: %v", lis.Addr().String(), err) 274 } 275 defer clientConn.Close() 276 client := pb.NewTestServiceClient(clientConn) 277 278 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 279 defer cancel() 280 ctx = metadata.NewOutgoingContext(ctx, test.md) 281 282 // Verifying authorization decision for Unary RPC. 283 _, err = client.UnaryCall(ctx, &pb.SimpleRequest{}) 284 if got := status.Convert(err); got.Code() != test.wantStatusCode || got.Message() != test.wantErr { 285 t.Fatalf("[UnaryCall] error want:{%v %v} got:{%v %v}", test.wantStatusCode, test.wantErr, got.Code(), got.Message()) 286 } 287 288 // Verifying authorization decision for Streaming RPC. 289 stream, err := client.StreamingInputCall(ctx) 290 if err != nil { 291 t.Fatalf("failed StreamingInputCall err: %v", err) 292 } 293 req := &pb.StreamingInputCallRequest{ 294 Payload: &pb.Payload{ 295 Body: []byte("hi"), 296 }, 297 } 298 if err := stream.Send(req); err != nil && err != io.EOF { 299 t.Fatalf("failed stream.Send err: %v", err) 300 } 301 _, err = stream.CloseAndRecv() 302 if got := status.Convert(err); got.Code() != test.wantStatusCode || got.Message() != test.wantErr { 303 t.Fatalf("[StreamingCall] error want:{%v %v} got:{%v %v}", test.wantStatusCode, test.wantErr, got.Code(), got.Message()) 304 } 305 }) 306 } 307} 308