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)
+}