proxy: add test for auth whitelist

This commit is contained in:
Oliver Gugger
2019-11-29 17:52:39 +01:00
parent 51c7deac05
commit b60460feee
3 changed files with 238 additions and 6 deletions

View File

@@ -29,7 +29,7 @@ const (
testProxyAddr = "localhost:10019" testProxyAddr = "localhost:10019"
testHostRegexp = "^localhost:.*$" testHostRegexp = "^localhost:.*$"
testPathRegexpHTTP = "^/http/.*$" testPathRegexpHTTP = "^/http/.*$"
testPathRegexpGRPC = "^/proxy_test.*$" testPathRegexpGRPC = "^/proxy_test\\.Greeter/.*$"
testTargetServiceAddress = "localhost:8082" testTargetServiceAddress = "localhost:8082"
testHTTPResponseBody = "HTTP Hello" testHTTPResponseBody = "HTTP Hello"
) )
@@ -47,6 +47,17 @@ func (s *helloServer) SayHello(ctx context.Context,
}, nil }, nil
} }
// SayHello returns a simple string that also contains a string from the
// request. This RPC method should be whitelisted to be called without any
// authentication required.
func (s *helloServer) SayHelloNoAuth(ctx context.Context,
req *proxytest.HelloRequest) (*proxytest.HelloReply, error) {
return &proxytest.HelloReply{
Message: fmt.Sprintf("Hello %s", req.Name),
}, nil
}
// TestProxyHTTP tests that the proxy can forward HTTP requests to a backend // TestProxyHTTP tests that the proxy can forward HTTP requests to a backend
// service and handle LSAT authentication correctly. // service and handle LSAT authentication correctly.
func TestProxyHTTP(t *testing.T) { func TestProxyHTTP(t *testing.T) {
@@ -238,6 +249,192 @@ func TestProxyGRPC(t *testing.T) {
} }
} }
// TestWhitelistHTTP verifies that a white list entry for a service allows an
// authentication exception to be configured.
func TestWhitelistHTTP(t *testing.T) {
// Create a service with authentication on by default, with one
// exception configured as whitelist.
services := []*proxy.Service{{
Address: testTargetServiceAddress,
HostRegexp: testHostRegexp,
PathRegexp: testPathRegexpHTTP,
Protocol: "http",
Auth: "on",
AuthWhitelistPaths: []string{"^/http/white.*$"},
}}
mockAuth := auth.NewMockAuthenticator()
p, err := proxy.New(mockAuth, services, "static")
if err != nil {
t.Fatalf("failed to create new proxy: %v", err)
}
// Start server that gives requests to the proxy.
server := &http.Server{
Addr: testProxyAddr,
Handler: http.HandlerFunc(p.ServeHTTP),
}
go server.ListenAndServe()
defer server.Close()
// Start the target backend service.
backendService := &http.Server{Addr: testTargetServiceAddress}
go startBackendHTTP(backendService)
defer backendService.Close()
// Wait for servers to start.
time.Sleep(100 * time.Millisecond)
// Test making a request to the backend service to an URL where
// authentication is enabled.
client := &http.Client{}
url := fmt.Sprintf("http://%s/http/black", testProxyAddr)
resp, err := client.Get(url)
if err != nil {
t.Fatalf("errored making http request: %v", err)
}
if resp.Status != "402 Payment Required" {
t.Fatalf("expected 402 status code, got: %v", resp.Status)
}
authHeader := resp.Header.Get("Www-Authenticate")
if !strings.Contains(authHeader, "LSAT") {
t.Fatalf("expected partial LSAT in response header, got: %v",
authHeader)
}
// Make sure that if we query an URL that is on the whitelist, we don't
// get the 402 response.
url = fmt.Sprintf("http://%s/http/white", testProxyAddr)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
t.Fatalf("error creating request: %v", err)
}
resp, err = client.Do(req)
if err != nil {
t.Fatalf("errored making http request: %v", err)
}
if resp.Status != "200 OK" {
t.Fatalf("expected 200 OK status code, got: %v", resp.Status)
}
// Ensure that we got the response body we expect.
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
if string(bodyBytes) != testHTTPResponseBody {
t.Fatalf("expected response body %v, got %v",
testHTTPResponseBody, string(bodyBytes))
}
}
// TestWhitelistGRPC verifies that a white list entry for a service allows an
// authentication exception to be configured.
func TestWhitelistGRPC(t *testing.T) {
// Since gRPC only really works over TLS, we need to generate a
// certificate and key pair first.
tempDirName, err := ioutil.TempDir("", "proxytest")
if err != nil {
t.Fatalf("unable to create temp dir: %v", err)
}
certFile := path.Join(tempDirName, "proxy.cert")
keyFile := path.Join(tempDirName, "proxy.key")
certPool, creds, certData, err := genCertPair(certFile, keyFile)
if err != nil {
t.Fatalf("unable to create cert pair: %v", err)
}
opts := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
// Create a list of services to proxy between.
services := []*proxy.Service{{
Address: testTargetServiceAddress,
HostRegexp: testHostRegexp,
PathRegexp: testPathRegexpGRPC,
Protocol: "https",
TLSCertPath: certFile,
Auth: "on",
AuthWhitelistPaths: []string{
"^/proxy_test\\.Greeter/SayHelloNoAuth.*$",
},
}}
// Create the proxy server and start serving on TLS.
mockAuth := auth.NewMockAuthenticator()
p, err := proxy.New(mockAuth, services, "static")
if err != nil {
t.Fatalf("failed to create new proxy: %v", err)
}
server := &http.Server{
Addr: testProxyAddr,
Handler: http.HandlerFunc(p.ServeHTTP),
TLSConfig: &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: true,
},
}
go server.ListenAndServeTLS(certFile, keyFile)
defer server.Close()
// Start the target backend service also on TLS.
tlsConf := cert.TLSConfFromCert(certData)
serverOpts := []grpc.ServerOption{
grpc.Creds(credentials.NewTLS(tlsConf)),
}
backendService := grpc.NewServer(serverOpts...)
go startBackendGRPC(backendService)
defer backendService.Stop()
// Dial to the proxy now, without any authentication.
conn, err := grpc.Dial(testProxyAddr, opts...)
if err != nil {
t.Fatalf("unable to connect to RPC server: %v", err)
}
client := proxytest.NewGreeterClient(conn)
// Test making a request to the backend service to an URL where
// authentication is enabled.
req := &proxytest.HelloRequest{Name: "foo"}
res, err := client.SayHello(
context.Background(), req, grpc.WaitForReady(true),
)
if err == nil {
t.Fatalf("expected error to be returned without auth")
}
statusErr, ok := status.FromError(err)
if !ok {
t.Fatalf("expected error to be status.Status")
}
if statusErr.Code() != codes.Internal {
t.Fatalf("unexpected code. wanted %d, got %d",
codes.Internal, statusErr.Code())
}
if statusErr.Message() != "payment required" {
t.Fatalf("invalid error. expected [%s] got [%s]",
"payment required", err.Error())
}
// Make sure that if we query an URL that is on the whitelist, we don't
// get the 402 response.
conn, err = grpc.Dial(testProxyAddr, opts...)
if err != nil {
t.Fatalf("unable to connect to RPC server: %v", err)
}
client = proxytest.NewGreeterClient(conn)
// Make the request. This time no error should be returned.
req = &proxytest.HelloRequest{Name: "foo"}
res, err = client.SayHelloNoAuth(context.Background(), req)
if err != nil {
t.Fatalf("unable to call service: %v", err)
}
if res.Message != "Hello foo" {
t.Fatalf("unexpected reply, wanted %s, got %s",
"Hello foo", res.Message)
}
}
// startBackendHTTP starts the given HTTP server and blocks until the server // startBackendHTTP starts the given HTTP server and blocks until the server
// is shut down. // is shut down.
func startBackendHTTP(server *http.Server) error { func startBackendHTTP(server *http.Server) error {

View File

@@ -108,16 +108,17 @@ func init() {
func init() { proto.RegisterFile("hello.proto", fileDescriptor_61ef911816e0a8ce) } func init() { proto.RegisterFile("hello.proto", fileDescriptor_61ef911816e0a8ce) }
var fileDescriptor_61ef911816e0a8ce = []byte{ var fileDescriptor_61ef911816e0a8ce = []byte{
// 145 bytes of a gzipped FileDescriptorProto // 161 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9,
0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2a, 0x28, 0xca, 0xaf, 0xa8, 0x8c, 0x2f, 0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2a, 0x28, 0xca, 0xaf, 0xa8, 0x8c, 0x2f,
0x49, 0x2d, 0x2e, 0x51, 0x52, 0xe2, 0xe2, 0xf1, 0x00, 0x49, 0x05, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x49, 0x2d, 0x2e, 0x51, 0x52, 0xe2, 0xe2, 0xf1, 0x00, 0x49, 0x05, 0xa5, 0x16, 0x96, 0xa6, 0x16,
0x97, 0x08, 0x09, 0x71, 0xb1, 0xe4, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x97, 0x08, 0x09, 0x71, 0xb1, 0xe4, 0x25, 0xe6, 0xa6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06,
0x81, 0xd9, 0x4a, 0x6a, 0x5c, 0x5c, 0x50, 0x35, 0x05, 0x39, 0x95, 0x42, 0x12, 0x5c, 0xec, 0xb9, 0x81, 0xd9, 0x4a, 0x6a, 0x5c, 0x5c, 0x50, 0x35, 0x05, 0x39, 0x95, 0x42, 0x12, 0x5c, 0xec, 0xb9,
0xa9, 0xc5, 0xc5, 0x89, 0xe9, 0x30, 0x45, 0x30, 0xae, 0x91, 0x27, 0x17, 0xbb, 0x7b, 0x51, 0x6a, 0xa9, 0xc5, 0xc5, 0x89, 0xe9, 0x30, 0x45, 0x30, 0xae, 0x51, 0x3f, 0x23, 0x17, 0xbb, 0x7b, 0x51,
0x6a, 0x49, 0x6a, 0x91, 0x90, 0x1d, 0x17, 0x47, 0x70, 0x62, 0x25, 0x58, 0x97, 0x90, 0x84, 0x1e, 0x6a, 0x6a, 0x49, 0x6a, 0x91, 0x90, 0x1d, 0x17, 0x47, 0x70, 0x62, 0x25, 0x58, 0x9b, 0x90, 0x84,
0xc2, 0x3e, 0x3d, 0x64, 0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x14, 0xe4, 0x54, 0x2a, 0x31, 0x24, 0xb1, 0x1e, 0xc2, 0x42, 0x3d, 0x64, 0xdb, 0xa4, 0xc4, 0xb0, 0xc8, 0x14, 0xe4, 0x54, 0x2a, 0x31, 0x08,
0x81, 0x5d, 0x6a, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x7f, 0xe6, 0xbe, 0xb8, 0x00, 0x00, 0xb9, 0x70, 0xf1, 0xc1, 0xf4, 0xfb, 0xe5, 0x3b, 0x96, 0x96, 0x64, 0x90, 0x63, 0x4a, 0x12, 0x1b,
0xd8, 0xc3, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x61, 0x16, 0xd9, 0xde, 0xff, 0x00, 0x00,
0x00, 0x00,
} }
@@ -134,6 +135,7 @@ const _ = grpc.SupportPackageIsVersion4
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreeterClient interface { type GreeterClient interface {
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
SayHelloNoAuth(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
} }
type greeterClient struct { type greeterClient struct {
@@ -153,9 +155,19 @@ func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...
return out, nil return out, nil
} }
func (c *greeterClient) SayHelloNoAuth(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := c.cc.Invoke(ctx, "/proxy_test.Greeter/SayHelloNoAuth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GreeterServer is the server API for Greeter service. // GreeterServer is the server API for Greeter service.
type GreeterServer interface { type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error) SayHello(context.Context, *HelloRequest) (*HelloReply, error)
SayHelloNoAuth(context.Context, *HelloRequest) (*HelloReply, error)
} }
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
@@ -180,6 +192,24 @@ func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(in
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _Greeter_SayHelloNoAuth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelloRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GreeterServer).SayHelloNoAuth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proxy_test.Greeter/SayHelloNoAuth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHelloNoAuth(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Greeter_serviceDesc = grpc.ServiceDesc{ var _Greeter_serviceDesc = grpc.ServiceDesc{
ServiceName: "proxy_test.Greeter", ServiceName: "proxy_test.Greeter",
HandlerType: (*GreeterServer)(nil), HandlerType: (*GreeterServer)(nil),
@@ -188,6 +218,10 @@ var _Greeter_serviceDesc = grpc.ServiceDesc{
MethodName: "SayHello", MethodName: "SayHello",
Handler: _Greeter_SayHello_Handler, Handler: _Greeter_SayHello_Handler,
}, },
{
MethodName: "SayHelloNoAuth",
Handler: _Greeter_SayHelloNoAuth_Handler,
},
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "hello.proto", Metadata: "hello.proto",

View File

@@ -4,6 +4,7 @@ package proxy_test;
service Greeter { service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {} rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloNoAuth (HelloRequest) returns (HelloReply) {}
} }
message HelloRequest { message HelloRequest {