service/xproxyd: Add authorization to xproxy.

Also add integration and unit tests for authorization.

Change-Id: If5d21714c3868c0669d853f566675d459157673f
diff --git a/services/proxy/proxyd/proxyd_v23_test.go b/services/proxy/proxyd/proxyd_v23_test.go
index 9efde9c..4633e91 100644
--- a/services/proxy/proxyd/proxyd_v23_test.go
+++ b/services/proxy/proxyd/proxyd_v23_test.go
@@ -11,6 +11,7 @@
 	"v.io/v23/context"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
+	"v.io/x/ref"
 	"v.io/x/ref/test/modules"
 	"v.io/x/ref/test/v23tests"
 )
@@ -24,6 +25,9 @@
 )
 
 func V23TestProxyd(t *v23tests.T) {
+	if ref.RPCTransitionState() >= ref.XServers {
+		t.Skip("This test only runs under the old rpc system.")
+	}
 	v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
 	var (
 		proxydCreds, _ = t.Shell().NewChildCredentials("proxyd")
diff --git a/services/wspr/internal/app/app_test.go b/services/wspr/internal/app/app_test.go
index 37ca2b1..1795972 100644
--- a/services/wspr/internal/app/app_test.go
+++ b/services/wspr/internal/app/app_test.go
@@ -335,7 +335,7 @@
 	var proxyEndpoint naming.Endpoint
 	if ref.RPCTransitionState() >= ref.XServers {
 		pctx, cancel := context.WithCancel(ctx)
-		proxy, perr := xproxy.New(v23.WithListenSpec(pctx, proxySpec), "")
+		proxy, perr := xproxy.New(v23.WithListenSpec(pctx, proxySpec), "", security.AllowEveryone())
 		proxyEndpoint = proxy.ListeningEndpoints()[0]
 		if protocol := proxyEndpoint.Addr().Network(); protocol != "tcp" {
 			return nil, fmt.Errorf("Got %s want tcp", protocol)
diff --git a/services/xproxy/xproxy/proxy.go b/services/xproxy/xproxy/proxy.go
index f0e3347..2e51dd8 100644
--- a/services/xproxy/xproxy/proxy.go
+++ b/services/xproxy/xproxy/proxy.go
@@ -14,6 +14,7 @@
 	"v.io/v23/flow"
 	"v.io/v23/flow/message"
 	"v.io/v23/naming"
+	"v.io/v23/security"
 
 	"v.io/x/ref/lib/publisher"
 )
@@ -26,24 +27,29 @@
 	m      flow.Manager
 	pub    publisher.Publisher
 	closed chan struct{}
+	auth   security.Authorizer
 
 	mu                 sync.Mutex
 	listeningEndpoints map[string]naming.Endpoint   // keyed by endpoint string
 	proxyEndpoints     map[string][]naming.Endpoint // keyed by proxy address
 }
 
-func New(ctx *context.T, name string) (*proxy, error) {
+func New(ctx *context.T, name string, auth security.Authorizer) (*proxy, error) {
 	mgr, err := v23.NewFlowManager(ctx)
 	if err != nil {
 		return nil, err
 	}
 	p := &proxy{
 		m:                  mgr,
+		auth:               auth,
 		proxyEndpoints:     make(map[string][]naming.Endpoint),
 		listeningEndpoints: make(map[string]naming.Endpoint),
 		pub:                publisher.New(ctx, v23.GetNamespace(ctx), time.Minute),
 		closed:             make(chan struct{}),
 	}
+	if p.auth == nil {
+		p.auth = security.DefaultAuthorizer()
+	}
 	if len(name) > 0 {
 		p.pub.AddName(name, false, false)
 	}
@@ -217,6 +223,19 @@
 }
 
 func (p *proxy) replyToServer(ctx *context.T, f flow.Flow) error {
+	call := security.NewCall(&security.CallParams{
+		LocalPrincipal:   v23.GetPrincipal(ctx),
+		LocalBlessings:   f.LocalBlessings(),
+		RemoteBlessings:  f.RemoteBlessings(),
+		LocalEndpoint:    f.LocalEndpoint(),
+		RemoteEndpoint:   f.RemoteEndpoint(),
+		RemoteDischarges: f.RemoteDischarges(),
+	})
+	if err := p.auth.Authorize(ctx, call); err != nil {
+		// TODO(suharshs): should we return the err to the server in the ProxyResponse message?
+		f.Close()
+		return err
+	}
 	rid := f.RemoteEndpoint().RoutingID()
 	eps, err := p.returnEndpoints(ctx, rid, "")
 	if err != nil {
diff --git a/services/xproxy/xproxy/proxy_test.go b/services/xproxy/xproxy/proxy_test.go
index 3a1d8cb..1f7c776 100644
--- a/services/xproxy/xproxy/proxy_test.go
+++ b/services/xproxy/xproxy/proxy_test.go
@@ -50,7 +50,7 @@
 	defer shutdown()
 
 	// Start the proxy.
-	pname, stop := startProxy(t, ctx, "proxy", "", address{"tcp", "127.0.0.1:0"})
+	pname, stop := startProxy(t, ctx, "proxy", security.AllowEveryone(), "", address{"tcp", "127.0.0.1:0"})
 	defer stop()
 
 	// Start the server listening through the proxy.
@@ -81,9 +81,9 @@
 	defer shutdown()
 
 	// Start the proxies.
-	p1name, stop := startProxy(t, ctx, "p1", "", address{"kill", "127.0.0.1:0"})
+	p1name, stop := startProxy(t, ctx, "p1", security.AllowEveryone(), "", address{"kill", "127.0.0.1:0"})
 	defer stop()
-	p2name, stop := startProxy(t, ctx, "p2", p1name, address{"kill", "127.0.0.1:0"})
+	p2name, stop := startProxy(t, ctx, "p2", security.AllowEveryone(), p1name, address{"kill", "127.0.0.1:0"})
 	defer stop()
 
 	// Start the server listening through the proxy.
@@ -139,7 +139,7 @@
 
 	// Now the proxy's blessings would fail authorization from the client using the
 	// default authorizer.
-	pname, stop := startProxy(t, pctx, "proxy", "", address{"tcp", "127.0.0.1:0"})
+	pname, stop := startProxy(t, pctx, "proxy", security.AllowEveryone(), "", address{"tcp", "127.0.0.1:0"})
 	defer stop()
 
 	// Start the server listening through the proxy.
@@ -161,6 +161,63 @@
 	}
 }
 
+func TestProxyAuthorizesServer(t *testing.T) {
+	if ref.RPCTransitionState() != ref.XServers {
+		t.Skip("Test only runs under 'V23_RPC_TRANSITION_STATE==xservers'")
+	}
+	ctx, shutdown := test.V23InitWithMounttable()
+	defer shutdown()
+
+	// Make principals for the proxy and server.
+	pctx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("proxy"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	sctx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("server"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Have the root bless the proxy and server.
+	root := testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx))
+	if err := root.Bless(v23.GetPrincipal(pctx), "proxy"); err != nil {
+		t.Fatal(err)
+	}
+	if err := root.Bless(v23.GetPrincipal(sctx), "server"); err != nil {
+		t.Fatal(err)
+	}
+
+	// Start a proxy that accepts connections from everyone and ensure that it does.
+	pname, stop := startProxy(t, pctx, "acceptproxy", security.AllowEveryone(), "", address{"tcp", "127.0.0.1:0"})
+	defer stop()
+
+	sctx = v23.WithListenSpec(sctx, rpc.ListenSpec{Proxy: pname})
+	sctx, cancel := context.WithCancel(sctx)
+	defer cancel()
+	_, server, err := v23.WithNewServer(sctx, "", &testService{}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for server.Status().Endpoints[0].Addr().Network() == bidiProtocol {
+		time.Sleep(pollTime)
+	}
+
+	// A proxy using the default authorizer should not authorize the server.
+	pname, stop = startProxy(t, pctx, "acceptproxy", nil, "", address{"tcp", "127.0.0.1:0"})
+	defer stop()
+
+	_, server, err = v23.WithNewServer(sctx, "", &testService{}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// TODO(suharshs): Find a way to test this without having to have this time.Sleep.
+	// Perhaps the manager can return errors about why connecting to the proxy failed.
+	time.Sleep(time.Second)
+	if server.Status().Endpoints[0].Addr().Network() != bidiProtocol {
+		t.Errorf("proxy should have rejected the server's request to listen through it")
+	}
+}
+
 type rejectProxyAuthorizer struct{}
 
 func (rejectProxyAuthorizer) Authorize(ctx *context.T, call security.Call) error {
@@ -189,7 +246,7 @@
 		t.Fatal(err)
 	}
 
-	pname, stop := startProxy(t, ctx, "", "", address{"kill", "127.0.0.1:0"})
+	pname, stop := startProxy(t, ctx, "", security.AllowEveryone(), "", address{"kill", "127.0.0.1:0"})
 	defer stop()
 	address, _ := naming.SplitAddressName(pname)
 	pep, err := v23.NewEndpoint(address)
@@ -227,12 +284,12 @@
 		t.Fatal(err)
 	}
 
-	p1name, stop := startProxy(t, ctx, "", "", address{"kill", "127.0.0.1:0"})
+	p1name, stop := startProxy(t, ctx, "", security.AllowEveryone(), "", address{"kill", "127.0.0.1:0"})
 	defer stop()
-	p2name, stop := startProxy(t, ctx, "", p1name, address{"kill", "127.0.0.1:0"})
+	p2name, stop := startProxy(t, ctx, "", security.AllowEveryone(), p1name, address{"kill", "127.0.0.1:0"})
 	defer stop()
 
-	p3name, stop := startProxy(t, ctx, "", p2name, address{"kill", "127.0.0.1:0"})
+	p3name, stop := startProxy(t, ctx, "", security.AllowEveryone(), p2name, address{"kill", "127.0.0.1:0"})
 	defer stop()
 	address, _ := naming.SplitAddressName(p3name)
 	p3ep, err := v23.NewEndpoint(address)
@@ -333,7 +390,7 @@
 	Protocol, Address string
 }
 
-func startProxy(t *testing.T, ctx *context.T, name string, listenOnProxy string, addrs ...address) (string, func()) {
+func startProxy(t *testing.T, ctx *context.T, name string, auth security.Authorizer, listenOnProxy string, addrs ...address) (string, func()) {
 	var ls rpc.ListenSpec
 	hasProxies := len(listenOnProxy) > 0
 	for _, addr := range addrs {
@@ -342,7 +399,7 @@
 	ls.Proxy = listenOnProxy
 	ctx = v23.WithListenSpec(ctx, ls)
 	ctx, cancel := context.WithCancel(ctx)
-	proxy, err := xproxy.New(ctx, name)
+	proxy, err := xproxy.New(ctx, name, auth)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/services/xproxy/xproxyd/doc.go b/services/xproxy/xproxyd/doc.go
index 6dcfd08..f90aa82 100644
--- a/services/xproxy/xproxyd/doc.go
+++ b/services/xproxy/xproxyd/doc.go
@@ -10,9 +10,13 @@
 (typically behind NATs) and proxies these services to the outside world.
 
 Usage:
-   proxyd [flags]
+   xproxyd [flags]
 
-The proxyd flags are:
+The xproxyd flags are:
+ -access-list=
+   Blessings that are authorized to listen via the proxy.  JSON-encoded
+   representation of access.AccessList.  An empty string implies the default
+   authorization policy.
  -healthz-address=
    Network address on which the HTTP healthz server runs.  It is intended to be
    used with a load balancer.  The load balancer must be able to reach this
diff --git a/services/xproxy/xproxyd/main.go b/services/xproxy/xproxyd/main.go
index 834ac4d..3902e2e 100644
--- a/services/xproxy/xproxyd/main.go
+++ b/services/xproxy/xproxyd/main.go
@@ -8,6 +8,8 @@
 package main
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"time"
@@ -16,24 +18,23 @@
 	"v.io/v23/context"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
+	"v.io/v23/security/access"
 
 	"v.io/x/lib/cmdline"
 	"v.io/x/ref/lib/signals"
 	"v.io/x/ref/lib/v23cmd"
 	_ "v.io/x/ref/runtime/factories/roaming"
-
 	"v.io/x/ref/services/xproxy/xproxy"
 )
 
-// TODO(suharshs): add authorization of server listening through proxy.
-
-var healthzAddr, name string
+var healthzAddr, name, acl string
 
 const healthTimeout = 10 * time.Second
 
 func main() {
 	cmdProxyD.Flags.StringVar(&healthzAddr, "healthz-address", "", "Network address on which the HTTP healthz server runs.  It is intended to be used with a load balancer.  The load balancer must be able to reach this address in order to verify that the proxy server is running.")
 	cmdProxyD.Flags.StringVar(&name, "name", "", "Name to mount the proxy as.")
+	cmdProxyD.Flags.StringVar(&acl, "access-list", "", "Blessings that are authorized to listen via the proxy.  JSON-encoded representation of access.AccessList.  An empty string implies the default authorization policy.")
 
 	cmdline.HideGlobalFlagsExcept()
 	cmdline.Main(cmdProxyD)
@@ -41,7 +42,7 @@
 
 var cmdProxyD = &cmdline.Command{
 	Runner: v23cmd.RunnerFunc(runProxyD),
-	Name:   "proxyd",
+	Name:   "xproxyd",
 	Short:  "Proxies services to the outside world",
 	Long: `
 Command proxyd is a daemon that listens for connections from Vanadium services
@@ -51,7 +52,11 @@
 
 func runProxyD(ctx *context.T, env *cmdline.Env, args []string) error {
 	// TODO(suharshs): Add ability to specify multiple proxies through this tool.
-	proxy, err := xproxy.New(ctx, name)
+	auth, err := authorizer(ctx)
+	if err != nil {
+		return err
+	}
+	proxy, err := xproxy.New(ctx, name, auth)
 	if err != nil {
 		return err
 	}
@@ -111,3 +116,18 @@
 		ctx.Fatal(err)
 	}
 }
+
+func authorizer(ctx *context.T) (security.Authorizer, error) {
+	if len(acl) > 0 {
+		var list access.AccessList
+		if err := json.NewDecoder(bytes.NewBufferString(acl)).Decode(&list); err != nil {
+			return nil, err
+		}
+		// Always add ourselves, for the the reserved methods server
+		// started below.
+		list.In = append(list.In, security.DefaultBlessingPatterns(v23.GetPrincipal(ctx))...)
+		ctx.Infof("Using access list to control proxy use: %v", list)
+		return list, nil
+	}
+	return nil, nil
+}
diff --git a/services/xproxy/xproxyd/proxyd_v23_test.go b/services/xproxy/xproxyd/proxyd_v23_test.go
new file mode 100644
index 0000000..d817e7d
--- /dev/null
+++ b/services/xproxy/xproxyd/proxyd_v23_test.go
@@ -0,0 +1,90 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main_test
+
+import (
+	"fmt"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/x/ref"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+//go:generate jiri test generate
+
+const (
+	proxyName   = "proxy"    // Name which the proxy mounts itself at
+	serverName  = "server"   // Name which the server mounts itself at
+	responseVar = "RESPONSE" // Name of the variable used by client program to output the response
+)
+
+func V23TestProxyd(t *v23tests.T) {
+	if ref.RPCTransitionState() < ref.XServers {
+		t.Skip("This test only runs under the new rpc system.")
+	}
+	v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
+	var (
+		proxydCreds, _ = t.Shell().NewChildCredentials("proxyd")
+		serverCreds, _ = t.Shell().NewChildCredentials("server")
+		clientCreds, _ = t.Shell().NewChildCredentials("client")
+		proxyd         = t.BuildV23Pkg("v.io/x/ref/services/xproxy/xproxyd")
+	)
+	// Start proxyd
+	proxyd.WithStartOpts(proxyd.StartOpts().WithCustomCredentials(proxydCreds)).
+		Start("--v23.tcp.address=127.0.0.1:0", "--name="+proxyName, "--access-list", "{\"In\":[\"root/server\"]}")
+	// Start the server that only listens via the proxy
+	if _, err := t.Shell().StartWithOpts(
+		t.Shell().DefaultStartOpts().WithCustomCredentials(serverCreds),
+		nil,
+		runServer); err != nil {
+		t.Fatal(err)
+	}
+	// Run the client.
+	client, err := t.Shell().StartWithOpts(
+		t.Shell().DefaultStartOpts().WithCustomCredentials(clientCreds),
+		nil,
+		runClient)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if got, want := client.ExpectVar(responseVar), "server [root/server] saw client [root/client]"; got != want {
+		t.Fatalf("Got %q, want %q", got, want)
+	}
+}
+
+var runServer = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	// Set the listen spec to listen only via the proxy.
+	ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{Proxy: proxyName})
+	if _, _, err := v23.WithNewServer(ctx, serverName, service{}, security.AllowEveryone()); err != nil {
+		return err
+	}
+	modules.WaitForEOF(env.Stdin)
+	return nil
+}, "runServer")
+
+var runClient = modules.Register(func(env *modules.Env, args ...string) error {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+	var response string
+	if err := v23.GetClient(ctx).Call(ctx, serverName, "Echo", nil, []interface{}{&response}); err != nil {
+		return err
+	}
+	fmt.Fprintf(env.Stdout, "%v=%v\n", responseVar, response)
+	return nil
+}, "runClient")
+
+type service struct{}
+
+func (service) Echo(ctx *context.T, call rpc.ServerCall) (string, error) {
+	client, _ := security.RemoteBlessingNames(ctx, call.Security())
+	server := security.LocalBlessingNames(ctx, call.Security())
+	return fmt.Sprintf("server %v saw client %v", server, client), nil
+}
diff --git a/services/xproxy/xproxyd/v23_test.go b/services/xproxy/xproxyd/v23_test.go
new file mode 100644
index 0000000..acff563
--- /dev/null
+++ b/services/xproxy/xproxyd/v23_test.go
@@ -0,0 +1,28 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+package main_test
+
+import (
+	"os"
+	"testing"
+
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
+
+func TestMain(m *testing.M) {
+	modules.DispatchAndExitIfChild()
+	cleanup := v23tests.UseSharedBinDir()
+	r := m.Run()
+	cleanup()
+	os.Exit(r)
+}
+
+func TestV23Proxyd(t *testing.T) {
+	v23tests.RunTest(t, V23TestProxyd)
+}