services/proxy/proxyd: Implement access control at the proxy server.

This change makes proxyd support access control by allowing only servers
that present a blessing that matches AccessList to export themselves
through the proxy.

The change itself is backward compatible, if proxyd built with this
change is started with --access-list='{"In":["..."]}'. However,
my intention is to have this in before the release, so all servers
that use the proxy will already be using this new code.

I admit that I went for a bit of "speed of implementation" over elegance
in an attempt to get this in before we release publicly. I'm happy to do
cleanups after this is in.

MultiPart: 1/3

Closes vanadium/issues#33

Change-Id: Ic6413403f6bbca9c7b2bd330910a5c3e2adb0ef3
diff --git a/cmd/servicerunner/main.go b/cmd/servicerunner/main.go
index 397747d..8d243d8 100644
--- a/cmd/servicerunner/main.go
+++ b/cmd/servicerunner/main.go
@@ -22,6 +22,7 @@
 	"v.io/v23"
 	"v.io/v23/options"
 	"v.io/v23/rpc"
+	"v.io/v23/security"
 
 	"v.io/x/ref/envvar"
 	"v.io/x/ref/lib/signals"
@@ -127,7 +128,7 @@
 
 	lspec := v23.GetListenSpec(ctx)
 	lspec.Addrs = rpc.ListenAddrs{{"ws", "127.0.0.1:0"}}
-	proxyShutdown, proxyEndpoint, err := profiles.NewProxy(ctx, lspec, "test/proxy")
+	proxyShutdown, proxyEndpoint, err := profiles.NewProxy(ctx, lspec, security.AllowEveryone(), "test/proxy")
 	defer proxyShutdown()
 	vars["PROXY_NAME"] = proxyEndpoint.Name()
 
diff --git a/profiles/internal/rpc/server.go b/profiles/internal/rpc/server.go
index 102f82a..770b113 100644
--- a/profiles/internal/rpc/server.go
+++ b/profiles/internal/rpc/server.go
@@ -34,6 +34,7 @@
 	"v.io/x/ref/profiles/internal/lib/publisher"
 	inaming "v.io/x/ref/profiles/internal/naming"
 	"v.io/x/ref/profiles/internal/rpc/stream"
+	"v.io/x/ref/profiles/internal/rpc/stream/manager"
 	"v.io/x/ref/profiles/internal/rpc/stream/vc"
 )
 
@@ -88,6 +89,7 @@
 	state             serverState          // track state of the server.
 	streamMgr         stream.Manager       // stream manager to listen for new flows.
 	publisher         publisher.Publisher  // publisher to publish mounttable mounts.
+	dc                vc.DischargeClient   // fetches discharges of blessings
 	listenerOpts      []stream.ListenerOpt // listener opts for Listen.
 	settingsPublisher *pubsub.Publisher    // pubsub publisher for dhcp
 	settingsName      string               // pubwsub stream name for dhcp
@@ -234,8 +236,8 @@
 	}
 	// Make dischargeExpiryBuffer shorter than the VC discharge buffer to ensure we have fetched
 	// the discharges by the time the VC asks for them.`
-	dc := InternalNewDischargeClient(ctx, client, dischargeExpiryBuffer-(5*time.Second))
-	s.listenerOpts = append(s.listenerOpts, dc)
+	s.dc = InternalNewDischargeClient(ctx, client, dischargeExpiryBuffer-(5*time.Second))
+	s.listenerOpts = append(s.listenerOpts, s.dc)
 	s.listenerOpts = append(s.listenerOpts, vc.DialContext{ctx})
 	blessingsStatsName := naming.Join(statsPrefix, "security", "blessings")
 	// TODO(caprita): revist printing the blessings with %s, and
@@ -464,7 +466,8 @@
 	if err != nil {
 		return nil, nil, verror.New(errFailedToResolveProxy, s.ctx, proxy, err)
 	}
-	ln, ep, err := s.streamMgr.Listen(inaming.Network, resolved, s.principal, s.blessings, s.listenerOpts...)
+	opts := append([]stream.ListenerOpt{proxyAuth{s}}, s.listenerOpts...)
+	ln, ep, err := s.streamMgr.Listen(inaming.Network, resolved, s.principal, s.blessings, opts...)
 	if err != nil {
 		return nil, nil, verror.New(errFailedToListenForProxy, s.ctx, resolved, err)
 	}
@@ -1334,3 +1337,41 @@
 	//nologcall
 	return fs.flow.RemoteEndpoint()
 }
+
+type proxyAuth struct {
+	s *server
+}
+
+func (a proxyAuth) RPCStreamListenerOpt() {}
+
+func (a proxyAuth) Login(proxy stream.Flow) (security.Blessings, []security.Discharge, error) {
+	var (
+		principal = a.s.principal
+		dc        = a.s.dc
+		ctx       = a.s.ctx
+	)
+	if principal == nil {
+		return security.Blessings{}, nil, nil
+	}
+	proxyNames, _ := security.RemoteBlessingNames(ctx, security.NewCall(&security.CallParams{
+		LocalPrincipal:   principal,
+		RemoteBlessings:  proxy.RemoteBlessings(),
+		RemoteDischarges: proxy.RemoteDischarges(),
+		RemoteEndpoint:   proxy.RemoteEndpoint(),
+		LocalEndpoint:    proxy.LocalEndpoint(),
+	}))
+	blessings := principal.BlessingStore().ForPeer(proxyNames...)
+	tpc := blessings.ThirdPartyCaveats()
+	if len(tpc) == 0 {
+		return blessings, nil, nil
+	}
+	// Ugh! Have to convert from proxyNames to BlessingPatterns
+	proxyPats := make([]security.BlessingPattern, len(proxyNames))
+	for idx, n := range proxyNames {
+		proxyPats[idx] = security.BlessingPattern(n)
+	}
+	discharges := dc.PrepareDischarges(ctx, tpc, security.DischargeImpetus{Server: proxyPats})
+	return blessings, discharges, nil
+}
+
+var _ manager.ProxyAuthenticator = proxyAuth{}
diff --git a/profiles/internal/rpc/stream/manager/listener.go b/profiles/internal/rpc/stream/manager/listener.go
index 3824089..2af4149 100644
--- a/profiles/internal/rpc/stream/manager/listener.go
+++ b/profiles/internal/rpc/stream/manager/listener.go
@@ -27,6 +27,16 @@
 	"v.io/x/ref/profiles/internal/rpc/stream"
 )
 
+// ProxyAuthenticator is a stream.ListenerOpt that is used when listening via a
+// proxy to authenticate with the proxy.
+type ProxyAuthenticator interface {
+	stream.ListenerOpt
+	// Login returns the Blessings (and the set of Discharges to make them
+	// valid) to send to the proxy. Typically, the proxy uses these to
+	// determine whether it wants to authorize use.
+	Login(proxy stream.Flow) (security.Blessings, []security.Discharge, error)
+}
+
 func reg(id, msg string) verror.IDAction {
 	return verror.Register(verror.ID(pkgPath+id), verror.NoRetry, msg)
 }
@@ -47,6 +57,7 @@
 	errAcceptFailed               = reg(".errAcceptFailed", "accept failed{:3}")
 	errFailedToEstablishVC        = reg(".errFailedToEstablishVC", "VC establishment with proxy failed{:_}")
 	errListenerAlreadyClosed      = reg(".errListenerAlreadyClosed", "listener already closed")
+	errRefusedProxyLogin          = reg(".errRefusedProxyLogin", "server did not want to listen via proxy{:_}")
 )
 
 // listener extends stream.Listener with a DebugString method.
@@ -288,12 +299,19 @@
 	vlog.VI(1).Infof("Connecting to proxy at %v", ln.proxyEP)
 	// Requires dialing a VC to the proxy, need to extract options from ln.opts to do so.
 	var dialOpts []stream.VCOpt
+	var auth ProxyAuthenticator
 	for _, opt := range opts {
-		if dopt, ok := opt.(stream.VCOpt); ok {
-			dialOpts = append(dialOpts, dopt)
+		switch v := opt.(type) {
+		case stream.VCOpt:
+			dialOpts = append(dialOpts, v)
+		case ProxyAuthenticator:
+			auth = v
 		}
 	}
-	// TODO(cnicolaou, ashankar): probably want to set a timeout here. (is this covered by opts?)
+	// TODO(cnicolaou, ashankar): probably want to set a timeout here. (is
+	// this covered by opts?)
+	// TODO(ashankar): Authorize the proxy server as well (similar to how
+	// clients authorize servers in RPCs).
 	vf, err := ln.manager.FindOrDialVIF(ln.proxyEP, principal, dialOpts...)
 	if err != nil {
 		return nil, nil, err
@@ -322,6 +340,12 @@
 	}
 	var request proxy.Request
 	var response proxy.Response
+	if auth != nil {
+		if request.Blessings, request.Discharges, err = auth.Login(flow); err != nil {
+			vf.StopAccepting()
+			return nil, nil, verror.New(stream.ErrSecurity, nil, verror.New(errRefusedProxyLogin, nil, err))
+		}
+	}
 	enc, err := vom.NewEncoder(flow)
 	if err != nil {
 		flow.Close()
diff --git a/profiles/internal/rpc/stream/proxy/protocol.vdl b/profiles/internal/rpc/stream/proxy/protocol.vdl
index 087365f..da87aa0 100644
--- a/profiles/internal/rpc/stream/proxy/protocol.vdl
+++ b/profiles/internal/rpc/stream/proxy/protocol.vdl
@@ -4,6 +4,8 @@
 
 package proxy
 
+import "v.io/v23/security"
+
 // The proxy protocol is:
 // (1) Server establishes a VC to the proxy to register its routing id and authenticate.
 // (2) The server opens a flow and sends a "Request" message and waits for a "Response"
@@ -16,6 +18,11 @@
 // traffic intended for the server's RoutingId to the network connection
 // between the server and the proxy.
 type Request struct {
+  // Blessings of the server that wishes to be proxied.
+  // Used to authorize the use of the proxy.
+  Blessings security.WireBlessings
+  // Discharges required to make Blessings valid.
+  Discharges []security.WireDischarge
 }
 
 // Response is sent by the proxy to the server after processing Request.
diff --git a/profiles/internal/rpc/stream/proxy/protocol.vdl.go b/profiles/internal/rpc/stream/proxy/protocol.vdl.go
index ff7c265..e02fe6b 100644
--- a/profiles/internal/rpc/stream/proxy/protocol.vdl.go
+++ b/profiles/internal/rpc/stream/proxy/protocol.vdl.go
@@ -10,12 +10,20 @@
 import (
 	// VDL system imports
 	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
 )
 
 // Request is the message sent by a server to request that the proxy route
 // traffic intended for the server's RoutingId to the network connection
 // between the server and the proxy.
 type Request struct {
+	// Blessings of the server that wishes to be proxied.
+	// Used to authorize the use of the proxy.
+	Blessings security.Blessings
+	// Discharges required to make Blessings valid.
+	Discharges []security.Discharge
 }
 
 func (Request) __VDLReflect(struct {
diff --git a/profiles/internal/rpc/stream/proxy/proxy.go b/profiles/internal/rpc/stream/proxy/proxy.go
index 3944e13..9cf4d92 100644
--- a/profiles/internal/rpc/stream/proxy/proxy.go
+++ b/profiles/internal/rpc/stream/proxy/proxy.go
@@ -7,6 +7,7 @@
 import (
 	"fmt"
 	"net"
+	"reflect"
 	"sync"
 	"time"
 
@@ -77,10 +78,12 @@
 // Proxy routes virtual circuit (VC) traffic between multiple underlying
 // network connections.
 type Proxy struct {
+	ctx        *context.T
 	ln         net.Listener
 	rid        naming.RoutingID
 	principal  security.Principal
 	blessings  security.Blessings
+	authorizer security.Authorizer
 	mu         sync.RWMutex
 	servers    *servermap
 	processes  map[*process]struct{}
@@ -182,17 +185,17 @@
 }
 
 // New creates a new Proxy that listens for network connections on the provided
-// (network, address) pair and routes VC traffic between accepted connections.
-// TODO(mattr): This should take a ListenSpec instead of network, address, and
-// pubAddress.  However using a ListenSpec requires a great deal of supporting
-// code that should be refactored out of v.io/x/ref/profiles/internal/rpc/server.go.
-func New(ctx *context.T, spec rpc.ListenSpec, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
+// ListenSpec and routes VC traffic between accepted connections.
+//
+// Servers wanting to "listen through the proxy" will only be allowed to do so
+// if the blessings they present are accepted to the provided authorization
+// policy (authorizer).
+func New(ctx *context.T, spec rpc.ListenSpec, authorizer security.Authorizer, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
 	rid, err := naming.NewRoutingID()
 	if err != nil {
 		return nil, nil, err
 	}
-
-	proxy, err := internalNew(rid, v23.GetPrincipal(ctx), spec)
+	proxy, err := internalNew(rid, ctx, spec, authorizer)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -221,7 +224,7 @@
 	return shutdown, proxy.endpoint(), nil
 }
 
-func internalNew(rid naming.RoutingID, principal security.Principal, spec rpc.ListenSpec) (*Proxy, error) {
+func internalNew(rid naming.RoutingID, ctx *context.T, spec rpc.ListenSpec, authorizer security.Authorizer) (*Proxy, error) {
 	if len(spec.Addrs) == 0 {
 		return nil, verror.New(stream.ErrProxy, nil, verror.New(errEmptyListenSpec, nil))
 	}
@@ -245,19 +248,23 @@
 		ln.Close()
 		return nil, verror.New(stream.ErrProxy, nil, verror.New(errNoAccessibleAddresses, nil, ln.Addr().String()))
 	}
-
+	if authorizer == nil {
+		authorizer = security.DefaultAuthorizer()
+	}
 	proxy := &Proxy{
-		ln:        ln,
-		rid:       rid,
-		servers:   &servermap{m: make(map[naming.RoutingID]*server)},
-		processes: make(map[*process]struct{}),
+		ctx:        ctx,
+		ln:         ln,
+		rid:        rid,
+		authorizer: authorizer,
+		servers:    &servermap{m: make(map[naming.RoutingID]*server)},
+		processes:  make(map[*process]struct{}),
 		// TODO(cnicolaou): should use all of the available addresses
 		pubAddress: pub[0].String(),
-		principal:  principal,
+		principal:  v23.GetPrincipal(ctx),
 		statsName:  naming.Join("rpc", "proxy", "routing-id", rid.String(), "debug"),
 	}
-	if principal != nil {
-		proxy.blessings = principal.BlessingStore().Default()
+	if proxy.principal != nil {
+		proxy.blessings = proxy.principal.BlessingStore().Default()
 	}
 	stats.NewStringFunc(proxy.statsName, proxy.debugString)
 
@@ -341,6 +348,8 @@
 		response.Error = verror.New(stream.ErrProxy, nil, verror.New(errVomDecoder, nil, err))
 	} else if err := dec.Decode(&request); err != nil {
 		response.Error = verror.New(stream.ErrProxy, nil, verror.New(errNoRequest, nil, err))
+	} else if err := p.authorize(server.VC, request); err != nil {
+		response.Error = err
 	} else if err := p.servers.Add(server); err != nil {
 		response.Error = verror.Convert(verror.ErrUnknown, nil, err)
 	} else {
@@ -379,6 +388,30 @@
 	server.Close(nil)
 }
 
+func (p *Proxy) authorize(vc *vc.VC, request Request) error {
+	var dmap map[string]security.Discharge
+	if len(request.Discharges) > 0 {
+		dmap := make(map[string]security.Discharge)
+		for _, d := range request.Discharges {
+			dmap[d.ID()] = d
+		}
+	}
+	// Blessings must be bound to the same public key as the VC.
+	// (Repeating logic in the RPC server authorization code).
+	if got, want := request.Blessings.PublicKey(), vc.RemoteBlessings().PublicKey(); !request.Blessings.IsZero() && !reflect.DeepEqual(got, want) {
+		return verror.New(verror.ErrNoAccess, nil, fmt.Errorf("malformed request: Blessings sent in proxy.Request are bound to public key %v and not %v", got, want))
+	}
+	return p.authorizer.Authorize(p.ctx, security.NewCall(&security.CallParams{
+		LocalPrincipal:   vc.LocalPrincipal(),
+		LocalBlessings:   vc.LocalBlessings(),
+		RemoteBlessings:  request.Blessings,
+		LocalEndpoint:    vc.LocalEndpoint(),
+		RemoteEndpoint:   vc.RemoteEndpoint(),
+		LocalDischarges:  vc.LocalDischarges(),
+		RemoteDischarges: dmap,
+	}))
+}
+
 func (p *Proxy) routeCounters(process *process, counters message.Counters) {
 	// Since each VC can be routed to a different process, split up the
 	// Counters into one message per VC.
diff --git a/profiles/internal/rpc/stream/proxy/proxy_test.go b/profiles/internal/rpc/stream/proxy/proxy_test.go
index c84e7cf..cffa224 100644
--- a/profiles/internal/rpc/stream/proxy/proxy_test.go
+++ b/profiles/internal/rpc/stream/proxy/proxy_test.go
@@ -13,10 +13,10 @@
 	"testing"
 	"time"
 
-	"v.io/x/lib/vlog"
-
 	"v.io/v23"
+	"v.io/v23/context"
 	"v.io/v23/naming"
+	"v.io/v23/security"
 	"v.io/v23/verror"
 
 	_ "v.io/x/ref/profiles"
@@ -33,12 +33,10 @@
 //go:generate v23 test generate
 
 func TestProxy(t *testing.T) {
-	ctx, shutdown := test.InitForTest()
+	ctx, shutdown := v23Init()
 	defer shutdown()
 
-	pproxy := testutil.NewPrincipal("proxy")
-
-	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, v23.GetListenSpec(ctx))
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -46,8 +44,6 @@
 	principal := testutil.NewPrincipal("test")
 	blessings := principal.BlessingStore().Default()
 
-	vlog.Infof("PROXYEP: %s", proxyEp)
-
 	// Create the stream.Manager for the server.
 	server1 := manager.InternalNew(naming.FixedRoutingID(0x1111111111111111))
 	defer server1.Shutdown()
@@ -102,12 +98,60 @@
 	}
 }
 
-func TestDuplicateRoutingID(t *testing.T) {
-	ctx, shutdown := test.InitForTest()
+func TestProxyAuthorization(t *testing.T) {
+	ctx, shutdown := v23Init()
 	defer shutdown()
 
-	pproxy := testutil.NewPrincipal("proxy")
-	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, v23.GetListenSpec(ctx))
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, testAuth{"alice", "carol"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer shutdown()
+
+	var (
+		alice = testutil.NewPrincipal("alice")
+		bob   = testutil.NewPrincipal("bob")
+		carol = testutil.NewPrincipal("carol")
+		dave  = testutil.NewPrincipal("dave")
+	)
+	// Make the proxy recognize "alice", "bob" and "carol", but not "dave"
+	v23.GetPrincipal(ctx).AddToRoots(alice.BlessingStore().Default())
+	v23.GetPrincipal(ctx).AddToRoots(bob.BlessingStore().Default())
+	v23.GetPrincipal(ctx).AddToRoots(carol.BlessingStore().Default())
+
+	testcases := []struct {
+		p  security.Principal
+		ok bool
+	}{
+		{alice, true}, // passes the auth policy
+		{bob, false},  // recognized, but not included in auth policy
+		{carol, true}, // passes the auth policy
+		{dave, false}, // not recognized, thus doesn't pass the auth policy
+	}
+	for idx, test := range testcases {
+		server := manager.InternalNew(naming.FixedRoutingID(uint64(idx)))
+		_, ep, err := server.Listen(proxyEp.Network(), proxyEp.String(), test.p, test.p.BlessingStore().Default(), proxyAuth{test.p})
+		if (err == nil) != test.ok {
+			t.Errorf("Got ep=%v, err=%v - wanted error:%v", ep, err, !test.ok)
+		}
+		server.Shutdown()
+	}
+}
+
+type proxyAuth struct {
+	p security.Principal
+}
+
+func (proxyAuth) RPCStreamListenerOpt() {}
+func (a proxyAuth) Login(stream.Flow) (security.Blessings, []security.Discharge, error) {
+	return a.p.BlessingStore().Default(), nil, nil
+}
+
+func TestDuplicateRoutingID(t *testing.T) {
+	ctx, shutdown := v23Init()
+	defer shutdown()
+
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -137,11 +181,11 @@
 }
 
 func TestProxyAuthentication(t *testing.T) {
-	ctx, shutdown := test.InitForTest()
+	ctx, shutdown := v23Init()
 	defer shutdown()
 
-	pproxy := testutil.NewPrincipal("proxy")
-	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, v23.GetListenSpec(ctx))
+	pproxy := v23.GetPrincipal(ctx)
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -168,16 +212,15 @@
 }
 
 func TestServerBlessings(t *testing.T) {
-	ctx, shutdown := test.InitForTest()
+	ctx, shutdown := v23Init()
 	defer shutdown()
 
 	var (
-		pproxy  = testutil.NewPrincipal("proxy")
 		pserver = testutil.NewPrincipal("server")
 		pclient = testutil.NewPrincipal("client")
 	)
 
-	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, v23.GetListenSpec(ctx))
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -221,11 +264,10 @@
 }
 
 func TestHostPort(t *testing.T) {
-	ctx, shutdown := test.InitForTest()
+	ctx, shutdown := v23Init()
 	defer shutdown()
 
-	pproxy := testutil.NewPrincipal("proxy")
-	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, v23.GetListenSpec(ctx))
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -244,11 +286,10 @@
 }
 
 func TestClientBecomesServer(t *testing.T) {
-	ctx, shutdown := test.InitForTest()
+	ctx, shutdown := v23Init()
 	defer shutdown()
 
-	pproxy := testutil.NewPrincipal("proxy")
-	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, v23.GetListenSpec(ctx))
+	_, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -302,7 +343,7 @@
 }
 
 func testProxyIdleTimeout(t *testing.T, testServer bool) {
-	ctx, shutdown := test.InitForTest()
+	ctx, shutdown := v23Init()
 	defer shutdown()
 
 	const (
@@ -313,7 +354,6 @@
 	)
 
 	var (
-		pproxy  = testutil.NewPrincipal("proxy")
 		pserver = testutil.NewPrincipal("server")
 		pclient = testutil.NewPrincipal("client")
 
@@ -329,7 +369,7 @@
 	// Pause the idle timers.
 	triggerTimers := vif.SetFakeTimers()
 
-	Proxy, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, v23.GetListenSpec(ctx))
+	Proxy, shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), ctx, security.AllowEveryone())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -444,3 +484,26 @@
 		buf.Write(tmp[:n])
 	}
 }
+
+func v23Init() (*context.T, func()) {
+	ctx, shutdown := test.InitForTest()
+	ctx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("proxy"))
+	if err != nil {
+		panic(err)
+	}
+	return ctx, shutdown
+}
+
+type testAuth []string
+
+func (l testAuth) Authorize(ctx *context.T, call security.Call) error {
+	remote, rejected := security.RemoteBlessingNames(ctx, call)
+	for _, n := range remote {
+		for _, a := range l {
+			if n == a {
+				return nil
+			}
+		}
+	}
+	return fmt.Errorf("%v not in authorized set of %v (rejected: %v)", remote, l, rejected)
+}
diff --git a/profiles/internal/rpc/stream/proxy/testutil_test.go b/profiles/internal/rpc/stream/proxy/testutil_test.go
index 8a1568c..727b8a5 100644
--- a/profiles/internal/rpc/stream/proxy/testutil_test.go
+++ b/profiles/internal/rpc/stream/proxy/testutil_test.go
@@ -5,15 +5,16 @@
 package proxy
 
 import (
+	"v.io/v23"
+	"v.io/v23/context"
 	"v.io/v23/naming"
-	"v.io/v23/rpc"
 	"v.io/v23/security"
 )
 
 // These are the internal functions only for use in the proxy_test package.
 
-func InternalNew(rid naming.RoutingID, p security.Principal, spec rpc.ListenSpec) (*Proxy, func(), naming.Endpoint, error) {
-	proxy, err := internalNew(rid, p, spec)
+func InternalNew(rid naming.RoutingID, ctx *context.T, auth security.Authorizer) (*Proxy, func(), naming.Endpoint, error) {
+	proxy, err := internalNew(rid, ctx, v23.GetListenSpec(ctx), auth)
 	if err != nil {
 		return nil, nil, nil, err
 	}
diff --git a/profiles/internal/rpc/test/proxy_test.go b/profiles/internal/rpc/test/proxy_test.go
index e6bd616..4316f55 100644
--- a/profiles/internal/rpc/test/proxy_test.go
+++ b/profiles/internal/rpc/test/proxy_test.go
@@ -56,7 +56,7 @@
 	expected := len(args)
 
 	listenSpec := rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
-	proxyShutdown, proxyEp, err := proxy.New(ctx, listenSpec)
+	proxyShutdown, proxyEp, err := proxy.New(ctx, listenSpec, security.AllowEveryone())
 	if err != nil {
 		fmt.Fprintf(stderr, "%s\n", verror.DebugString(err))
 		return err
diff --git a/profiles/proxy.go b/profiles/proxy.go
index 0a12b4e..9b4a350 100644
--- a/profiles/proxy.go
+++ b/profiles/proxy.go
@@ -8,12 +8,16 @@
 	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/rpc"
+	"v.io/v23/security"
 
 	"v.io/x/ref/profiles/internal/rpc/stream/proxy"
 )
 
 // NewProxy creates a new Proxy that listens for network connections on the provided
 // (network, address) pair and routes VC traffic between accepted connections.
-func NewProxy(ctx *context.T, spec rpc.ListenSpec, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
-	return proxy.New(ctx, spec, names...)
+//
+// auth encapsulates the authorization policy of the proxy - which
+// servers it is willing to proxy for.
+func NewProxy(ctx *context.T, spec rpc.ListenSpec, auth security.Authorizer, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
+	return proxy.New(ctx, spec, auth, names...)
 }
diff --git a/profiles/roaming/proxy.go b/profiles/roaming/proxy.go
index 845ad11..5590a7c 100644
--- a/profiles/roaming/proxy.go
+++ b/profiles/roaming/proxy.go
@@ -8,12 +8,16 @@
 	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/rpc"
+	"v.io/v23/security"
 
 	"v.io/x/ref/profiles/internal/rpc/stream/proxy"
 )
 
 // NewProxy creates a new Proxy that listens for network connections on the provided
 // (network, address) pair and routes VC traffic between accepted connections.
-func NewProxy(ctx *context.T, spec rpc.ListenSpec, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
-	return proxy.New(ctx, spec, names...)
+//
+// auth encapsulates the authorization policy of the proxy - which
+// servers it is willing to proxy for.
+func NewProxy(ctx *context.T, spec rpc.ListenSpec, auth security.Authorizer, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
+	return proxy.New(ctx, spec, auth, names...)
 }
diff --git a/profiles/static/proxy.go b/profiles/static/proxy.go
index a398460..8693142 100644
--- a/profiles/static/proxy.go
+++ b/profiles/static/proxy.go
@@ -8,12 +8,16 @@
 	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/rpc"
+	"v.io/v23/security"
 
 	"v.io/x/ref/profiles/internal/rpc/stream/proxy"
 )
 
 // NewProxy creates a new Proxy that listens for network connections on the provided
 // (network, address) pair and routes VC traffic between accepted connections.
-func NewProxy(ctx *context.T, spec rpc.ListenSpec, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
-	return proxy.New(ctx, spec, names...)
+//
+// auth encapsulates the authorization policy of the proxy - which
+// servers it is willing to proxy for.
+func NewProxy(ctx *context.T, spec rpc.ListenSpec, auth security.Authorizer, names ...string) (shutdown func(), endpoint naming.Endpoint, err error) {
+	return proxy.New(ctx, spec, auth, names...)
 }
diff --git a/services/device/internal/starter/starter.go b/services/device/internal/starter/starter.go
index 4ad1dac..50a2e9b 100644
--- a/services/device/internal/starter/starter.go
+++ b/services/device/internal/starter/starter.go
@@ -25,6 +25,7 @@
 	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/rpc"
+	"v.io/v23/security"
 	"v.io/v23/verror"
 	"v.io/x/lib/vlog"
 )
@@ -282,7 +283,9 @@
 	// under.
 	ls := v23.GetListenSpec(ctx)
 	ls.Addrs = rpc.ListenAddrs{{protocol, addr}}
-	shutdown, ep, err := roaming.NewProxy(ctx, ls)
+	// TODO(ashankar): Revisit this choice of security.AllowEveryone
+	// See: https://v.io/i/387
+	shutdown, ep, err := roaming.NewProxy(ctx, ls, security.AllowEveryone())
 	if err != nil {
 		return nil, verror.New(errCantCreateProxy, ctx, err)
 	}
diff --git a/services/proxy/proxyd/main.go b/services/proxy/proxyd/main.go
index d9114eb..a617145 100644
--- a/services/proxy/proxyd/main.go
+++ b/services/proxy/proxyd/main.go
@@ -7,15 +7,17 @@
 package main
 
 import (
+	"bytes"
+	"encoding/json"
 	"flag"
 	"fmt"
 	"net/http"
-	_ "net/http/pprof"
 	"time"
 
 	"v.io/v23"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
+	"v.io/v23/security/access"
 	"v.io/x/lib/vlog"
 
 	"v.io/x/ref/lib/signals"
@@ -26,6 +28,7 @@
 	pubAddress  = flag.String("published-address", "", "deprecated - the proxy now uses listenspecs and the address chooser mechanism")
 	healthzAddr = flag.String("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")
 	name        = flag.String("name", "", "Name to mount the proxy as")
+	acl         = flag.String("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.")
 )
 
 func main() {
@@ -39,7 +42,20 @@
 	if listenSpec.Proxy != "" {
 		vlog.Fatalf("proxyd cannot listen through another proxy")
 	}
-	proxyShutdown, proxyEndpoint, err := static.NewProxy(ctx, listenSpec, *name)
+	var authorizer security.Authorizer
+	if len(*acl) > 0 {
+		var list access.AccessList
+		if err := json.NewDecoder(bytes.NewBufferString(*acl)).Decode(&list); err != nil {
+			vlog.Fatalf("invalid --access-list: %v", err)
+		}
+		// Always add ourselves, for the the reserved methods server
+		// started below.
+		list.In = append(list.In, security.DefaultBlessingPatterns(v23.GetPrincipal(ctx))...)
+		vlog.Infof("Using access list to control proxy use: %v", list)
+		authorizer = list
+	}
+
+	proxyShutdown, proxyEndpoint, err := static.NewProxy(ctx, listenSpec, authorizer, *name)
 	if err != nil {
 		vlog.Fatal(err)
 	}
diff --git a/services/proxy/proxyd/proxyd_v23_test.go b/services/proxy/proxyd/proxyd_v23_test.go
index 2e4d4e0..e58c90d 100644
--- a/services/proxy/proxyd/proxyd_v23_test.go
+++ b/services/proxy/proxyd/proxyd_v23_test.go
@@ -42,7 +42,7 @@
 	)
 	// Start proxyd
 	proxyd.WithStartOpts(proxyd.StartOpts().WithCustomCredentials(proxydCreds)).
-		Start("--v23.tcp.address=127.0.0.1:0", "--name="+proxyName)
+		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),
diff --git a/services/wspr/internal/app/app_test.go b/services/wspr/internal/app/app_test.go
index 6e14735..e259890 100644
--- a/services/wspr/internal/app/app_test.go
+++ b/services/wspr/internal/app/app_test.go
@@ -316,7 +316,7 @@
 		return nil, fmt.Errorf("unable to start mounttable: %v", err)
 	}
 	proxySpec := rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
-	proxyShutdown, proxyEndpoint, err := profiles.NewProxy(ctx, proxySpec)
+	proxyShutdown, proxyEndpoint, err := profiles.NewProxy(ctx, proxySpec, security.AllowEveryone())
 	if err != nil {
 		return nil, fmt.Errorf("unable to start proxy: %v", err)
 	}
diff --git a/services/wspr/internal/browspr/browspr_test.go b/services/wspr/internal/browspr/browspr_test.go
index bbb0e0a..effb936 100644
--- a/services/wspr/internal/browspr/browspr_test.go
+++ b/services/wspr/internal/browspr/browspr_test.go
@@ -17,6 +17,7 @@
 	"v.io/v23/naming"
 	"v.io/v23/options"
 	"v.io/v23/rpc"
+	"v.io/v23/security"
 	"v.io/v23/vdl"
 	vdltime "v.io/v23/vdlroot/time"
 	"v.io/v23/vom"
@@ -83,7 +84,7 @@
 	defer shutdown()
 
 	proxySpec := rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
-	proxyShutdown, proxyEndpoint, err := profiles.NewProxy(ctx, proxySpec)
+	proxyShutdown, proxyEndpoint, err := profiles.NewProxy(ctx, proxySpec, security.AllowEveryone())
 	if err != nil {
 		t.Fatalf("Failed to start proxy: %v", err)
 	}