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