Merge "groups: profile: s/generic/roaming/"
diff --git a/lib/mgmt/model.go b/lib/mgmt/model.go
index e1f3032..1aa425f 100644
--- a/lib/mgmt/model.go
+++ b/lib/mgmt/model.go
@@ -14,4 +14,6 @@
ParentBlessingConfigKey = "MGMT_PARENT_BLESSING_PEER_PATTERN"
SecurityAgentEndpointConfigKey = "MGMT_SECURITY_AGENT_EP"
AppOriginConfigKey = "MGMT_APP_ORIGIN"
+ PublisherBlessingPrefixesKey = "MGMT_PUBLISHER_BLESSING_PREFIXES"
+ InstanceNameKey = "MGMT_INSTANCE_NAME"
)
diff --git a/runtime/internal/flow/flowcontrol/flowcontrol.go b/runtime/internal/flow/flowcontrol/flowcontrol.go
new file mode 100644
index 0000000..1a47ada
--- /dev/null
+++ b/runtime/internal/flow/flowcontrol/flowcontrol.go
@@ -0,0 +1,339 @@
+// 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 flowcontrol
+
+import (
+ "bytes"
+ "fmt"
+ "sync"
+
+ "v.io/v23/context"
+ "v.io/v23/verror"
+)
+
+const pkgPath = "v.io/x/ref/runtime/internal/flow/flowcontrol"
+
+var ErrConcurrentRun = verror.Register(
+ verror.ID(pkgPath+".ErrConcurrentRun"),
+ verror.NoRetry, "Run called concurrently.")
+var ErrWrongFlowController = verror.Register(
+ verror.ID(pkgPath+".ErrWrongFlowController"),
+ verror.NoRetry, "Release called for worker from different flow controller.")
+
+// Runners are called by Workers. For a given flow controller
+// only one Runner will be running at a time. tokens specifies
+// the number of tokens available for this call. Implementors
+// should return the number of tokens used, whether they are done
+// with all their work, and any error encountered.
+// Runners will be called repeatedly within a single Run call until
+// either err != nil or done is true.
+type Runner func(tokens int) (used int, done bool, err error)
+
+type counterState struct {
+ // TODO(mattr): Add deficit if we allow multi-slice writes.
+ borrowed int // Number of tokens borrowed from the shared pool.
+ released int // Number of tokens available via our flow control counters.
+ everReleased bool // True if tokens have ever been released to this worker.
+}
+
+type state int
+
+const (
+ idle = state(iota)
+ running
+ active
+)
+
+// Worker represents a single flowcontrolled worker.
+// Workers keep track of flow control counters to ensure
+// producers do not overwhelm consumers. Only one Worker
+// will be executing at a time.
+type Worker struct {
+ fc *FlowController
+ priority int
+ work chan struct{}
+
+ // These variables are protected by fc.mu.
+ counters *counterState // State related to the flow control counters.
+ state state
+ next, prev *Worker // Used as a list when in an active queue.
+}
+
+// Run runs r potentially multiple times.
+// Only one worker's r function will run at a time for a given FlowController.
+// A single worker's Run function should not be called concurrently from multiple
+// goroutines.
+func (w *Worker) Run(ctx *context.T, r Runner) (err error) {
+ w.fc.mu.Lock()
+ if w.state != idle {
+ w.fc.mu.Unlock()
+ return verror.New(ErrConcurrentRun, ctx)
+ }
+
+ w.state = running
+ if w.readyLocked() {
+ w.fc.activateLocked(w)
+ w.state = active
+ }
+
+ for {
+ next := w.fc.nextWorkerLocked()
+ for w.fc.writing != w && err == nil {
+ w.fc.mu.Unlock()
+ if next != nil {
+ next.work <- struct{}{}
+ }
+ select {
+ case <-ctx.Done():
+ err = ctx.Err()
+ case <-w.work:
+ }
+ w.fc.mu.Lock()
+ }
+ if err != nil {
+ break
+ }
+
+ toWrite := w.fc.mtu
+ if w.counters != nil {
+ if !w.counters.everReleased {
+ toWrite = min(w.fc.shared, w.fc.mtu)
+ w.counters.released += toWrite
+ w.counters.borrowed += toWrite
+ w.fc.shared -= toWrite
+ } else {
+ toWrite = min(w.counters.released, w.fc.mtu)
+ }
+ }
+
+ w.fc.mu.Unlock()
+ var written int
+ var done bool
+ written, done, err = r(toWrite)
+ w.fc.mu.Lock()
+
+ if w.counters != nil {
+ w.counters.released -= written
+ if w.counters.released > 0 && w.counters.borrowed > 0 {
+ toReturn := min(w.counters.released, w.counters.borrowed)
+ w.counters.borrowed -= toReturn
+ w.counters.released -= toReturn
+ w.fc.shared += toReturn
+ }
+ }
+
+ w.fc.writing = nil
+ if err != nil || done {
+ break
+ }
+ if !w.readyLocked() {
+ w.fc.deactivateLocked(w)
+ w.state = running
+ }
+ }
+
+ w.state = idle
+ w.fc.deactivateLocked(w)
+ next := w.fc.nextWorkerLocked()
+ w.fc.mu.Unlock()
+ if next != nil {
+ next.work <- struct{}{}
+ }
+ return err
+}
+
+func (w *Worker) releaseLocked(tokens int) {
+ if w.counters == nil {
+ return
+ }
+ w.counters.everReleased = true
+ if w.counters.borrowed > 0 {
+ n := min(w.counters.borrowed, tokens)
+ w.counters.borrowed -= n
+ w.fc.shared += n
+ tokens -= n
+ }
+ w.counters.released += tokens
+ if w.state == running && w.readyLocked() {
+ w.fc.activateLocked(w)
+ }
+}
+
+// Release releases tokens to this worker.
+// Workers will first repay any debts to the flow controllers shared pool
+// and use any surplus in subsequent calls to Run.
+func (w *Worker) Release(tokens int) {
+ w.fc.mu.Lock()
+ w.releaseLocked(tokens)
+ next := w.fc.nextWorkerLocked()
+ w.fc.mu.Unlock()
+ if next != nil {
+ next.work <- struct{}{}
+ }
+}
+
+func (w *Worker) readyLocked() bool {
+ if w.counters == nil {
+ return true
+ }
+ return w.counters.released > 0 || (!w.counters.everReleased && w.fc.shared > 0)
+}
+
+// FlowController manages multiple Workers to ensure only one runs at a time.
+// The workers also obey counters so that producers don't overwhelm consumers.
+type FlowController struct {
+ mtu int
+
+ mu sync.Mutex
+ shared int
+ active []*Worker
+ writing *Worker
+}
+
+// New creates a new FlowController. Shared is the number of shared tokens
+// that flows can borrow from before they receive their first Release.
+// Mtu is the maximum number of tokens to be consumed by a single Runner
+// invocation.
+func New(shared, mtu int) *FlowController {
+ return &FlowController{shared: shared, mtu: mtu}
+}
+
+// NewWorker creates a new worker. Workers keep track of token counters
+// for a flow controlled process. The order that workers
+// execute is controlled by priority. Higher priority
+// workers that are ready will run before any lower priority
+// workers.
+func (fc *FlowController) NewWorker(priority int) *Worker {
+ w := &Worker{
+ fc: fc,
+ priority: priority,
+ work: make(chan struct{}),
+ counters: &counterState{},
+ }
+ w.next, w.prev = w, w
+ return w
+}
+
+type Release struct {
+ Worker *Worker
+ Tokens int
+}
+
+// Release releases to many Workers atomically. It is conceptually
+// the same as calling release on each worker indepedently.
+func (fc *FlowController) Release(to []Release) error {
+ fc.mu.Lock()
+ for _, t := range to {
+ if t.Worker.fc != fc {
+ return verror.New(ErrWrongFlowController, nil)
+ }
+ t.Worker.releaseLocked(t.Tokens)
+ }
+ next := fc.nextWorkerLocked()
+ fc.mu.Unlock()
+ if next != nil {
+ next.work <- struct{}{}
+ }
+ return nil
+}
+
+// Run runs the given runner on a non-flow controlled Worker. This
+// worker does not wait for any flow control tokens and is limited
+// only by the MTU.
+func (fc *FlowController) Run(ctx *context.T, p int, r Runner) error {
+ w := &Worker{
+ fc: fc,
+ priority: p,
+ work: make(chan struct{}),
+ }
+ w.next, w.prev = w, w
+ return w.Run(ctx, r)
+}
+
+func (fc *FlowController) nextWorkerLocked() *Worker {
+ if fc.writing == nil {
+ for p, head := range fc.active {
+ if head != nil {
+ fc.active[p] = head.next
+ fc.writing = head
+ return head
+ }
+ }
+ }
+ return nil
+}
+
+func (fc *FlowController) activateLocked(w *Worker) {
+ if w.priority >= len(fc.active) {
+ newActive := make([]*Worker, int(w.priority)+1)
+ copy(newActive, fc.active)
+ fc.active = newActive
+ }
+ head := fc.active[w.priority]
+ if head == nil {
+ fc.active[w.priority] = w
+ } else {
+ w.prev, w.next = head.prev, head
+ w.prev.next, w.next.prev = w, w
+ }
+}
+
+func (fc *FlowController) deactivateLocked(w *Worker) {
+ if head := fc.active[w.priority]; head == w {
+ if w.next == w {
+ fc.active[w.priority] = nil
+ } else {
+ fc.active[w.priority] = w.next
+ }
+ }
+ w.next.prev, w.prev.next = w.prev, w.next
+ w.next, w.prev = w, w
+}
+
+func (fc *FlowController) numActive() int {
+ n := 0
+ fc.mu.Lock()
+ for _, head := range fc.active {
+ if head != nil {
+ n++
+ for cur := head.next; cur != head; cur = cur.next {
+ n++
+ }
+ }
+ }
+ fc.mu.Unlock()
+ return n
+}
+
+// String writes a string representation of the flow controller.
+// This can be helpful in debugging.
+func (fc *FlowController) String() string {
+ buf := &bytes.Buffer{}
+ fmt.Fprintf(buf, "FlowController %p: \n", fc)
+
+ fc.mu.Lock()
+ fmt.Fprintf(buf, "writing: %p\n", fc.writing)
+ fmt.Fprintln(buf, "active:")
+ for p, head := range fc.active {
+ fmt.Fprintf(buf, " %v: %p", p, head)
+ if head != nil {
+ for cur := head.next; cur != head; cur = cur.next {
+ fmt.Fprintf(buf, " %p", cur)
+ }
+ }
+ fmt.Fprintln(buf, "")
+ }
+ fc.mu.Unlock()
+ return buf.String()
+}
+
+func min(head int, rest ...int) int {
+ for _, r := range rest {
+ if r < head {
+ head = r
+ }
+ }
+ return head
+}
diff --git a/runtime/internal/flow/flowcontrol/flowcontrol_test.go b/runtime/internal/flow/flowcontrol/flowcontrol_test.go
new file mode 100644
index 0000000..7388b8f
--- /dev/null
+++ b/runtime/internal/flow/flowcontrol/flowcontrol_test.go
@@ -0,0 +1,293 @@
+// 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 flowcontrol
+
+import (
+ "bytes"
+ "crypto/rand"
+ "fmt"
+ "io"
+ "net"
+ "sync"
+ "testing"
+ "time"
+
+ "v.io/v23/context"
+ "v.io/v23/verror"
+ "v.io/x/ref/test"
+)
+
+var testdata = make([]byte, 1<<20)
+
+func init() {
+ test.Init()
+ _, err := io.ReadFull(rand.Reader, testdata)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func TestFlowControl(t *testing.T) {
+ const (
+ workers = 10
+ messages = 10
+ )
+
+ msgs := make(map[int][]byte)
+ fc := New(256, 64)
+
+ ctx, cancel := context.RootContext()
+ defer cancel()
+
+ var wg sync.WaitGroup
+ wg.Add(workers)
+ for i := 0; i < workers; i++ {
+ go func(idx int) {
+ el := fc.NewWorker(0)
+ go el.Release(messages * 5) // Try to make races happen
+ j := 0
+ el.Run(ctx, func(tokens int) (used int, done bool, err error) {
+ msgs[idx] = append(msgs[idx], []byte(fmt.Sprintf("%d-%d,", idx, j))...)
+ j++
+ return 3, j >= messages, nil
+ })
+ wg.Done()
+ }(i)
+ }
+ wg.Wait()
+
+ for i := 0; i < workers; i++ {
+ buf := &bytes.Buffer{}
+ for j := 0; j < messages; j++ {
+ fmt.Fprintf(buf, "%d-%d,", i, j)
+ }
+ if want, got := buf.String(), string(msgs[i]); want != got {
+ t.Errorf("Got %s, want %s for %d", got, want, i)
+ }
+ }
+}
+
+func expect(t *testing.T, work chan interface{}, values ...interface{}) {
+ for i, w := range values {
+ if got := <-work; got != w {
+ t.Errorf("expected %p in pos %d got %p", w, i, got)
+ }
+ }
+}
+
+func TestOrdering(t *testing.T) {
+ const mtu = 10
+
+ ctx, cancel := context.RootContext()
+ defer cancel()
+ fc := New(0, mtu)
+
+ work := make(chan interface{})
+ worker := func(p int) *Worker {
+ w := fc.NewWorker(p)
+ go w.Run(ctx, func(t int) (int, bool, error) {
+ work <- w
+ return t, false, nil
+ })
+ w.Release(mtu)
+ <-work
+ return w
+ }
+
+ w0 := worker(0)
+ w1a := worker(1)
+ w1b := worker(1)
+ w1c := worker(1)
+ w2 := worker(2)
+
+ // Release to all the flows at once and ensure the writes
+ // happen in the correct order.
+ fc.Release([]Release{{w0, 2 * mtu}, {w1a, 2 * mtu}, {w1b, 3 * mtu}, {w1c, 0}, {w2, mtu}})
+ expect(t, work, w0, w0, w1a, w1b, w1a, w1b, w1b, w2)
+}
+
+func TestSharedCounters(t *testing.T) {
+ const (
+ mtu = 10
+ shared = 2 * mtu
+ )
+
+ ctx, cancel := context.RootContext()
+ defer cancel()
+
+ fc := New(shared, mtu)
+
+ work := make(chan interface{})
+
+ worker := func(p int) *Worker {
+ w := fc.NewWorker(p)
+ go w.Run(ctx, func(t int) (int, bool, error) {
+ work <- w
+ return t, false, nil
+ })
+ return w
+ }
+
+ // w0 should run twice on shared counters.
+ w0 := worker(0)
+ expect(t, work, w0, w0)
+
+ w1 := worker(1)
+ // Now Release to w0 which shouldn't allow it to run since it's just repaying, but
+ // should allow w1 to run on the returned shared counters.
+ w0.Release(2 * mtu)
+ expect(t, work, w1, w1)
+
+ // Releasing again will allow w0 to run.
+ w0.Release(mtu)
+ expect(t, work, w0)
+}
+
+func TestConcurrentRun(t *testing.T) {
+ ctx, cancel := context.RootContext()
+ defer cancel()
+ const mtu = 10
+ fc := New(mtu, mtu)
+
+ ready, wait := make(chan struct{}), make(chan struct{})
+ w := fc.NewWorker(0)
+ go w.Run(ctx, func(t int) (int, bool, error) {
+ close(ready)
+ <-wait
+ return t, true, nil
+ })
+ <-ready
+ if err := w.Run(ctx, nil); verror.ErrorID(err) != ErrConcurrentRun.ID {
+ t.Errorf("expected concurrent run error got: %v", err)
+ }
+ close(wait)
+}
+
+func TestNonFlowControlledRun(t *testing.T) {
+ ctx, cancel := context.RootContext()
+ defer cancel()
+ const mtu = 10
+ fc := New(0, mtu)
+
+ work := make(chan interface{})
+ ready, wait := make(chan struct{}), make(chan struct{})
+ // Start one worker running
+ go fc.Run(ctx, 0, func(t int) (int, bool, error) {
+ close(ready)
+ <-wait
+ return t, true, nil
+ })
+ <-ready
+ // Now queue up sever workers and make sure they execute in order.
+ go fc.Run(ctx, 2, func(t int) (int, bool, error) {
+ work <- "c"
+ return t, true, nil
+ })
+ go fc.Run(ctx, 1, func(t int) (int, bool, error) {
+ work <- "b"
+ return t, true, nil
+ })
+ go fc.Run(ctx, 0, func(t int) (int, bool, error) {
+ work <- "a"
+ return t, true, nil
+ })
+ for fc.numActive() < 4 {
+ time.Sleep(time.Millisecond)
+ }
+ close(wait)
+ expect(t, work, "a", "b", "c")
+}
+
+func newNullConn(mtu int) net.Conn {
+ ln, err := net.Listen("tcp", ":0")
+ if err != nil {
+ panic(err)
+ }
+ addr := ln.Addr()
+
+ go func() {
+ conn, err := ln.Accept()
+ if err != nil {
+ panic(err)
+ }
+ ln.Close()
+ buf := make([]byte, mtu)
+ for {
+ _, err := conn.Read(buf)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ panic(err)
+ }
+ }
+ conn.Close()
+ }()
+
+ conn, err := net.Dial(addr.Network(), addr.String())
+ if err != nil {
+ panic(err)
+ }
+ return conn
+}
+
+func BenchmarkWithFlowControl(b *testing.B) {
+ const (
+ mtu = 1 << 16
+ shared = 1 << 20
+ workers = 100
+ )
+ ctx, cancel := context.RootContext()
+ defer cancel()
+ s := newNullConn(mtu)
+
+ for n := 0; n < b.N; n++ {
+ fc := New(shared, mtu)
+ var wg sync.WaitGroup
+ wg.Add(workers)
+ for i := 0; i < workers; i++ {
+ go func(idx int) {
+ w := fc.NewWorker(0)
+ w.Release(len(testdata))
+ t := testdata
+ err := w.Run(ctx, func(tokens int) (used int, done bool, err error) {
+ towrite := min(tokens, len(t))
+ written, err := s.Write(t[:min(tokens, len(t))])
+ t = t[written:]
+ return towrite, len(t) == 0, err
+ })
+ if err != nil {
+ panic(err)
+ }
+ wg.Done()
+ }(i)
+ }
+ wg.Wait()
+ }
+ if err := s.Close(); err != nil {
+ panic(err)
+ }
+}
+
+func BenchmarkWithoutFlowControl(b *testing.B) {
+ const (
+ workers = 100
+ mtu = 1 << 16
+ )
+ s := newNullConn(mtu)
+ for n := 0; n < b.N; n++ {
+ for cursor := 0; cursor < len(testdata); cursor += mtu {
+ for i := 0; i < workers; i++ {
+ _, err := s.Write(testdata[cursor : cursor+mtu])
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
+ }
+ if err := s.Close(); err != nil {
+ panic(err)
+ }
+}
diff --git a/runtime/internal/flow/model.go b/runtime/internal/flow/model.go
index b43aeed..53de3e1 100644
--- a/runtime/internal/flow/model.go
+++ b/runtime/internal/flow/model.go
@@ -13,24 +13,39 @@
// Manager is the interface for managing the creation of Flows.
type Manager interface {
- // Listen creates a Listener that can be used to listen on addresses
- // and Accept flows initiates to those addresses.
+ // Acceptor creates a Acceptor that can be used to accept new flows from addresses
+ // that the Manager is listening on.
//
// For example:
- // ln, err := sm.NewListener(ctx, blessings)
- // ep, err := ln.Listen("tcp", ":0")
+ // err := m.Listen(ctx, "tcp", ":0")
+ // a, err := m.Acceptor(ctx, blessings)
// for {
- // flow, err := ln.Accept(ctx)
+ // flow, err := a.Accept(ctx)
// // process flow
// }
- // can be used to accept Flows initiated by remote processes to the endpoint
- // returned by ln.Listen.
//
- // blessings are the Blessings presented to the Client during authentication.
- NewListener(ctx *context.T, blessings security.Blessings) (Listener, error)
+ // can be used to accept Flows initiated by remote processes to the endpoints returned
+ // by Acceptor.Endpoints. All created Acceptors accept Flows from all addresses the
+ // Manager is listening on, but are differentiated by a RoutingID unique to each
+ // Acceptor.
+ //
+ // 'blessings' are the Blessings presented to the Client during authentication.
+ Acceptor(ctx *context.T, blessings security.Blessings) (Acceptor, error)
- // Dial creates a Flow to the provided remote endpoint.
- Dial(ctx *context.T, remote naming.Endpoint) (Flow, error)
+ // Listen causes the Manager to accept flows from the provided protocol and address.
+ // Listen may be called muliple times.
+ Listen(ctx *context.T, protocol, address string) error
+
+ // Dial creates a Flow to the provided remote endpoint. 'auth' is used to authorize
+ // the remote end.
+ //
+ // To maximize re-use of connections, the Manager will also Listen on Dialed
+ // connections for the lifetime of the connection.
+ //
+ // TODO(suharshs): Revisit passing in an authorizer here. Perhaps restrict server
+ // authorization to a smaller set of policies, or use a different mechanism to
+ // allow the user to specify a policy.
+ Dial(ctx *context.T, remote naming.Endpoint, auth security.Authorizer) (Flow, error)
// Closed returns a channel that remains open for the lifetime of the Manager
// object. Once the channel is closed any operations on the Manager will
@@ -52,26 +67,28 @@
RemoteBlessings() security.Blessings
// LocalDischarges returns the discharges presented by the local end of the flow during authentication.
//
- // The discharges are organized in a map keyed by the discharge-identifier.
+ // Discharges are organized in a map keyed by the discharge-identifier.
LocalDischarges() map[string]security.Discharge
// RemoteDischarges returns the discharges presented by the remote end of the flow during authentication.
//
- // The discharges are organized in a map keyed by the discharge-identifier.
+ // Discharges are organized in a map keyed by the discharge-identifier.
RemoteDischarges() map[string]security.Discharge
// Closed returns a channel that remains open until the flow has been closed or
- // the ctx to the Dail or Accept call used to create the flow has been cancelled.
+ // the ctx to the Dial or Accept call used to create the flow has been cancelled.
Closed() <-chan struct{}
}
-// Listener is the interface for accepting Flows created by a remote process.
-type Listener interface {
- // Listen listens on the protocol and address specified. This may be called
- // multiple times.
- Listen(protocol, address string) (naming.Endpoint, error)
+// Acceptor is the interface for accepting Flows created by a remote process.
+type Acceptor interface {
+ // ListeningEndpoints returns the endpoints that the Manager has explicitly
+ // listened on. The Acceptor will accept new flows on these endpoints.
+ // Returned endpoints all have a RoutingID unique to the Acceptor.
+ ListeningEndpoints() []naming.Endpoint
// Accept blocks until a new Flow has been initiated by a remote process.
Accept(ctx *context.T) (Flow, error)
- // Closed returns a channel that remains open until the Listener has been closed.
- // i.e. the context provided to Manager.NewListener() has been cancelled.
+ // Closed returns a channel that remains open until the Acceptor has been closed.
+ // i.e. the context provided to Manager.Acceptor() has been cancelled. Once the
+ // channel is closed, all calls to Accept will result in an error.
Closed() <-chan struct{}
}
diff --git a/services/application/applicationd/perms_test.go b/services/application/applicationd/perms_test.go
index c4446f9..207c0a2 100644
--- a/services/application/applicationd/perms_test.go
+++ b/services/application/applicationd/perms_test.go
@@ -39,8 +39,6 @@
publishName := args[0]
storedir := args[1]
- v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
-
defer fmt.Fprintf(env.Stdout, "%v terminating\n", publishName)
defer ctx.VI(1).Infof("%v terminating", publishName)
@@ -50,7 +48,7 @@
}
server, err := xrpc.NewDispatchingServer(ctx, publishName, dispatcher)
if err != nil {
- ctx.Fatalf("Serve(%v) failed: %v", publishName, err)
+ ctx.Fatalf("NewDispatchingServer(%v) failed: %v", publishName, err)
}
ctx.VI(1).Infof("applicationd name: %v", server.Status().Endpoints[0].Name())
@@ -63,17 +61,18 @@
func TestApplicationUpdatePermissions(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
- v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
- // By default, all principals in this test will have blessings generated based
- // on the username/machine running this process. Give them recognizable names
- // ("root/self" etc.), so the Permissions can be set deterministically.
- idp := testutil.NewIDProvider("root")
+ // V23Init sets the context up with a self-signed principal, whose
+ // blessing (test-blessing) will act as the root blessing for the test.
+ const rootBlessing = test.TestBlessing
+ idp := testutil.IDProviderFromPrincipal(v23.GetPrincipal(ctx))
+ // Call ourselves test-blessing/self, distinct from test-blessing/other
+ // which we'll give to the 'other' context.
if err := idp.Bless(v23.GetPrincipal(ctx), "self"); err != nil {
t.Fatal(err)
}
- sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, v23.GetPrincipal(ctx))
+ sh, deferFn := servicetest.CreateShell(t, ctx, nil)
defer deferFn()
// setup mock up directory to put state in
@@ -122,29 +121,29 @@
}
expected := access.Permissions{
"Admin": access.AccessList{
- In: []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+ In: []security.BlessingPattern{test.TestBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
NotIn: []string(nil)},
"Read": access.AccessList{
- In: []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+ In: []security.BlessingPattern{rootBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
NotIn: []string(nil)},
"Write": access.AccessList{
- In: []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+ In: []security.BlessingPattern{rootBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
NotIn: []string(nil)},
"Debug": access.AccessList{
- In: []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+ In: []security.BlessingPattern{rootBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
NotIn: []string(nil)},
"Resolve": access.AccessList{
- In: []security.BlessingPattern{"root/$", "root/self/$", "root/self/child"},
+ In: []security.BlessingPattern{rootBlessing + "/$", rootBlessing + "/self/$", rootBlessing + "/self/child"},
NotIn: []string(nil)}}
if got := perms; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
- t.Errorf("got %#v, exected %#v ", got, expected)
+ t.Errorf("got %#v, expected %#v ", got, expected)
}
ctx.VI(2).Infof("self attempting to give other permission to update application")
newPerms := make(access.Permissions)
for _, tag := range access.AllTypicalTags() {
- newPerms.Add("root/self", string(tag))
- newPerms.Add("root/other", string(tag))
+ newPerms.Add(rootBlessing+"/self", string(tag))
+ newPerms.Add(rootBlessing+"/other", string(tag))
}
if err := repostub.SetPermissions(ctx, newPerms, ""); err != nil {
t.Fatalf("SetPermissions failed: %v", err)
@@ -170,7 +169,7 @@
t.Fatalf("GetPermissions 2 should not have failed: %v", err)
}
perms["Admin"] = access.AccessList{
- In: []security.BlessingPattern{"root/other"},
+ In: []security.BlessingPattern{rootBlessing + "/other"},
NotIn: []string{}}
if err = repostub.SetPermissions(otherCtx, perms, version); err != nil {
t.Fatalf("SetPermissions failed: %v", err)
@@ -186,19 +185,19 @@
}
expected = access.Permissions{
"Admin": access.AccessList{
- In: []security.BlessingPattern{"root/other"},
+ In: []security.BlessingPattern{rootBlessing + "/other"},
NotIn: []string{}},
- "Read": access.AccessList{In: []security.BlessingPattern{"root/other",
- "root/self"},
+ "Read": access.AccessList{In: []security.BlessingPattern{rootBlessing + "/other",
+ rootBlessing + "/self"},
NotIn: []string{}},
- "Write": access.AccessList{In: []security.BlessingPattern{"root/other",
- "root/self"},
+ "Write": access.AccessList{In: []security.BlessingPattern{rootBlessing + "/other",
+ rootBlessing + "/self"},
NotIn: []string{}},
- "Debug": access.AccessList{In: []security.BlessingPattern{"root/other",
- "root/self"},
+ "Debug": access.AccessList{In: []security.BlessingPattern{rootBlessing + "/other",
+ rootBlessing + "/self"},
NotIn: []string{}},
- "Resolve": access.AccessList{In: []security.BlessingPattern{"root/other",
- "root/self"},
+ "Resolve": access.AccessList{In: []security.BlessingPattern{rootBlessing + "/other",
+ rootBlessing + "/self"},
NotIn: []string{}}}
if got := perms; !reflect.DeepEqual(expected.Normalize(), got.Normalize()) {
diff --git a/services/device/internal/impl/app_service.go b/services/device/internal/impl/app_service.go
index fabd4cd..4712114 100644
--- a/services/device/internal/impl/app_service.go
+++ b/services/device/internal/impl/app_service.go
@@ -127,6 +127,7 @@
"path"
"path/filepath"
"reflect"
+ "regexp"
"strconv"
"strings"
"text/template"
@@ -204,6 +205,8 @@
reap *reaper
// mtAddress is the address of the local mounttable.
mtAddress string
+ // appServiceName is a name by which the appService can be reached
+ appServiceName string
}
// appService implements the Device manager's Application interface.
@@ -337,6 +340,12 @@
// title, and thereby being installed in the same app dir. Do we want to
// prevent that for the same user or across users?
+const (
+ appDirPrefix = "app-"
+ installationPrefix = "installation-"
+ instancePrefix = "instance-"
+)
+
// applicationDirName generates a cryptographic hash of the application title,
// to be used as a directory name for installations of the application with the
// given title.
@@ -344,15 +353,15 @@
h := md5.New()
h.Write([]byte(title))
hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
- return "app-" + hash
+ return appDirPrefix + hash
}
func installationDirName(installationID string) string {
- return "installation-" + installationID
+ return installationPrefix + installationID
}
func instanceDirName(instanceID string) string {
- return "instance-" + instanceID
+ return instancePrefix + instanceID
}
func mkdir(dir string) error {
@@ -837,6 +846,20 @@
return suidHelper.getAppCmd(&saArgs)
}
+// instanceNameFromDir returns the instance name, given the instanceDir.
+func instanceNameFromDir(ctx *context.T, instanceDir string) (string, error) {
+ _, _, installation, instance := parseInstanceDir(instanceDir)
+ if installation == "" || instance == "" {
+ return "", fmt.Errorf("Unable to parse instanceDir %v", instanceDir)
+ }
+
+ env, err := loadEnvelopeForInstance(ctx, instanceDir)
+ if err != nil {
+ return "", err
+ }
+ return env.Title + "/" + installation + "/" + instance, nil
+}
+
func (i *appRunner) startCmd(ctx *context.T, instanceDir string, cmd *exec.Cmd) (int, error) {
info, err := loadInstanceInfo(ctx, instanceDir)
if err != nil {
@@ -863,6 +886,14 @@
cfg.Set(mgmt.ProtocolConfigKey, "tcp")
cfg.Set(mgmt.AddressConfigKey, "127.0.0.1:0")
cfg.Set(mgmt.ParentBlessingConfigKey, info.DeviceManagerPeerPattern)
+ cfg.Set(mgmt.PublisherBlessingPrefixesKey,
+ v23.GetPrincipal(ctx).BlessingStore().Default().String())
+
+ if instanceName, err := instanceNameFromDir(ctx, instanceDir); err != nil {
+ return 0, err
+ } else {
+ cfg.Set(mgmt.InstanceNameKey, naming.Join(i.appServiceName, instanceName))
+ }
appPermsDir := filepath.Join(instanceDir, "debugacls", "data")
cfg.Set("v23.permissions.file", "runtime:"+appPermsDir)
@@ -990,7 +1021,7 @@
// device manager then crashes between the reaper marking the app not
// running and the go routine invoking this function having a chance to
// complete.
-func (i *appRunner) restartAppIfNecessary(instanceDir string) {
+func (i *appRunner) restartAppIfNecessary(ctx *context.T, instanceDir string) {
if err := transitionInstance(instanceDir, device.InstanceStateNotRunning, device.InstanceStateLaunching); err != nil {
vlog.Error(err)
return
@@ -1007,7 +1038,7 @@
return
}
- if err := i.run(nil, instanceDir); err != nil {
+ if err := i.run(ctx, instanceDir); err != nil {
vlog.Error(err)
}
}
@@ -1040,6 +1071,17 @@
return instanceDir, nil
}
+// parseInstanceDir is a partial inverse of instanceDir. It cannot retrieve the app name,
+// as that has been hashed so it returns an appDir instead.
+func parseInstanceDir(dir string) (prefix, appDir, installation, instance string) {
+ dirRE := regexp.MustCompile("(/.*)(/" + appDirPrefix + "[^/]+)/" + installationPrefix + "([^/]+)/" + "instances/" + instancePrefix + "([^/]+)$")
+ matches := dirRE.FindStringSubmatch(dir)
+ if len(matches) < 5 {
+ return "", "", "", ""
+ }
+ return matches[1], matches[2], matches[3], matches[4]
+}
+
// instanceDir returns the path to the directory containing the app instance
// referred to by the invoker's suffix, as well as the corresponding not-running
// instance dir. Returns an error if the suffix does not name an instance.
@@ -1322,7 +1364,7 @@
func (i *appService) scanEnvelopes(ctx *context.T, tree *treeNode, appDir string) {
// Find all envelopes, extract installID.
- envGlob := []string{i.config.Root, appDir, "installation-*", "*", "envelope"}
+ envGlob := []string{i.config.Root, appDir, installationPrefix + "*", "*", "envelope"}
envelopes, err := filepath.Glob(filepath.Join(envGlob...))
if err != nil {
vlog.Errorf("unexpected error: %v", err)
@@ -1339,7 +1381,7 @@
vlog.Errorf("unexpected number of path components: %q (%q)", elems, path)
continue
}
- installID := strings.TrimPrefix(elems[1], "installation-")
+ installID := strings.TrimPrefix(elems[1], installationPrefix)
tree.find([]string{env.Title, installID}, true)
}
return
@@ -1357,7 +1399,7 @@
// Add the node corresponding to the installation itself.
tree.find(i.suffix[:2], true)
// Find all instances.
- infoGlob := []string{installDir, "instances", "instance-*", "info"}
+ infoGlob := []string{installDir, "instances", instancePrefix + "*", "info"}
instances, err := filepath.Glob(filepath.Join(infoGlob...))
if err != nil {
vlog.Errorf("unexpected error: %v", err)
@@ -1374,14 +1416,12 @@
if _, err := loadInstanceInfo(ctx, instanceDir); err != nil {
return
}
- relpath, _ := filepath.Rel(i.config.Root, instanceDir)
- elems := strings.Split(relpath, string(filepath.Separator))
- if len(elems) < 4 {
- vlog.Errorf("unexpected number of path components: %q (%q)", elems, instanceDir)
+ rootDir, _, installID, instanceID := parseInstanceDir(instanceDir)
+ if installID == "" || instanceID == "" || filepath.Clean(i.config.Root) != filepath.Clean(rootDir) {
+ vlog.Errorf("failed to parse instanceDir %v (got: %v %v %v)", instanceDir, rootDir, installID, instanceID)
return
}
- installID := strings.TrimPrefix(elems[1], "installation-")
- instanceID := strings.TrimPrefix(elems[3], "instance-")
+
tree.find([]string{title, installID, instanceID, "logs"}, true)
if instanceStateIs(instanceDir, device.InstanceStateRunning) {
for _, obj := range []string{"pprof", "stats"} {
@@ -1394,7 +1434,7 @@
tree := newTreeNode()
switch len(i.suffix) {
case 0:
- i.scanEnvelopes(ctx, tree, "app-*")
+ i.scanEnvelopes(ctx, tree, appDirPrefix+"*")
case 1:
appDir := applicationDirName(i.suffix[0])
i.scanEnvelopes(ctx, tree, appDir)
diff --git a/services/device/internal/impl/applife/app_life_test.go b/services/device/internal/impl/applife/app_life_test.go
index cde8c9a..0e42790 100644
--- a/services/device/internal/impl/applife/app_life_test.go
+++ b/services/device/internal/impl/applife/app_life_test.go
@@ -161,10 +161,32 @@
verifyAppPeerBlessings(t, ctx, pubCtx, instanceDebug, envelope)
// Wait until the app pings us that it's ready.
- pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
-
+ pingResult := pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
v1EP1 := utiltest.Resolve(t, ctx, "appV1", 1, true)[0]
+ // Check that the instance name handed to the app looks plausible
+ nameRE := regexp.MustCompile(".*/apps/google naps/[^/]+/[^/]+$")
+ if nameRE.FindString(pingResult.InstanceName) == "" {
+ t.Fatalf("Unexpected instance name: %v", pingResult.InstanceName)
+ }
+
+ // There should be at least one publisher blessing prefix, and all prefixes should
+ // end in "/mydevice" because they are just the device manager's blessings
+ prefixes := strings.Split(pingResult.PubBlessingPrefixes, ",")
+ if len(prefixes) == 0 {
+ t.Fatalf("No publisher blessing prefixes found: %v", pingResult)
+ }
+ for _, p := range prefixes {
+ if !strings.HasSuffix(p, "/mydevice") {
+ t.Fatalf("publisher Blessing prefixes don't look right: %v", pingResult.PubBlessingPrefixes)
+ }
+ }
+
+ // We used a signed envelope, so there should have been some publisher blessings
+ if !hasPrefixMatches(pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings) {
+ t.Fatalf("Publisher Blessing Prefixes are not as expected: %v vs %v", pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings)
+ }
+
// Stop the app instance.
utiltest.KillApp(t, ctx, appID, instance1ID)
utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance1ID)
@@ -265,10 +287,21 @@
}
// Resume the first instance and verify it's running v2 now.
utiltest.RunApp(t, ctx, appID, instance1ID)
- pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+ pingResult = pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
utiltest.Resolve(t, ctx, "appV1", 1, false)
utiltest.Resolve(t, ctx, "appV2", 1, false)
+ // Although v2 does not have a signed envelope, this was an update of v1, which did.
+ // This app's config still includes publisher blessing prefixes, and it should still
+ // have the publisher blessing it acquired earlier.
+ //
+ // TODO: This behavior is non-ideal. A reasonable requirement in future would be that
+ // the publisher blessing string remain unchanged on updates to an installation, just as the
+ // title is not allowed to change.
+ if !hasPrefixMatches(pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings) {
+ t.Fatalf("Publisher Blessing Prefixes are not as expected: %v vs %v", pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings)
+ }
+
// Reverting first instance fails since it's still running.
utiltest.RevertAppExpectError(t, ctx, appID+"/"+instance1ID, errors.ErrInvalidOperation.ID)
// Stop first instance and try again.
@@ -289,7 +322,12 @@
t.Fatalf("Instance version expected to be %v, got %v instead", v2, v)
}
// Wait until the app pings us that it's ready.
- pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+ pingResult = pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+ // This app should not have publisher blessings. It was started from an installation
+ // that did not have a signed envelope.
+ if hasPrefixMatches(pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings) {
+ t.Fatalf("Publisher Blessing Prefixes are not as expected: %v vs %v", pingResult.PubBlessingPrefixes, pingResult.DefaultPeerBlessings)
+ }
utiltest.Resolve(t, ctx, "appV2", 1, true)
@@ -561,6 +599,22 @@
return pubCtx, nil
}
+// findPrefixMatches takes a set of comma-separated prefixes, and a set of comma-separated
+// strings, and checks if any of the strings match any of the prefixes
+func hasPrefixMatches(prefixList, stringList string) bool {
+ prefixes := strings.Split(prefixList, ",")
+ inStrings := strings.Split(stringList, ",")
+
+ for _, s := range inStrings {
+ for _, p := range prefixes {
+ if strings.HasPrefix(s, p) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
// verifyAppPeerBlessings checks the instanceDebug string to ensure that the app is running with
// the expected blessings for peer "..." (i.e. security.AllPrincipals) .
//
diff --git a/services/device/internal/impl/dispatcher.go b/services/device/internal/impl/dispatcher.go
index 2afb044..f016313 100644
--- a/services/device/internal/impl/dispatcher.go
+++ b/services/device/internal/impl/dispatcher.go
@@ -135,8 +135,9 @@
}
}
reap, err := newReaper(ctx, config.Root, &appRunner{
- callback: d.internal.callback,
- securityAgent: d.internal.securityAgent,
+ callback: d.internal.callback,
+ securityAgent: d.internal.securityAgent,
+ appServiceName: naming.Join(d.config.Name, appsSuffix),
})
if err != nil {
return nil, verror.New(errCantCreateAppWatcher, ctx, err)
@@ -324,10 +325,11 @@
uat: d.uat,
permsStore: d.permsStore,
runner: &appRunner{
- reap: d.internal.reap,
- callback: d.internal.callback,
- securityAgent: d.internal.securityAgent,
- mtAddress: d.mtAddress,
+ reap: d.internal.reap,
+ callback: d.internal.callback,
+ securityAgent: d.internal.securityAgent,
+ mtAddress: d.mtAddress,
+ appServiceName: naming.Join(d.config.Name, appsSuffix),
},
})
appSpecificAuthorizer, err := newAppSpecificAuthorizer(auth, d.config, components[1:], d.permsStore)
diff --git a/services/device/internal/impl/instance_reaping.go b/services/device/internal/impl/instance_reaping.go
index 9bfb4e1..7f17aed 100644
--- a/services/device/internal/impl/instance_reaping.go
+++ b/services/device/internal/impl/instance_reaping.go
@@ -46,6 +46,7 @@
type reaper struct {
c chan pidInstanceDirPair
startState *appRunner
+ ctx *context.T
}
var stashedPidMap map[string]int
@@ -63,13 +64,14 @@
r := &reaper{
c: make(chan pidInstanceDirPair),
startState: startState,
+ ctx: ctx,
}
r.startState.reap = r
go r.processStatusPolling(pidMap)
// Restart daemon jobs if they're not running (say because the machine crashed.)
for _, idir := range restartCandidates {
- go r.startState.restartAppIfNecessary(idir)
+ go r.startState.restartAppIfNecessary(ctx, idir)
}
return r, nil
}
@@ -99,7 +101,7 @@
// No such PID.
vlog.VI(2).Infof("processStatusPolling discovered pid %d ended", pid)
markNotRunning(idir)
- go r.startState.restartAppIfNecessary(idir)
+ go r.startState.restartAppIfNecessary(r.ctx, idir)
delete(trackedPids, idir)
case nil, syscall.EPERM:
vlog.VI(2).Infof("processStatusPolling saw live pid: %d", pid)
diff --git a/services/device/internal/impl/profile.go b/services/device/internal/impl/profile.go
index ae181d8..74f9ad1 100644
--- a/services/device/internal/impl/profile.go
+++ b/services/device/internal/impl/profile.go
@@ -170,7 +170,13 @@
Os: build.OperatingSystemLinux,
Format: build.FormatElf,
},
- // TODO(caprita): Add other profiles for Mac, Pi, etc.
+ {
+ Label: "darwin-amd64",
+ Description: "",
+ Arch: build.ArchitectureAmd64,
+ Os: build.OperatingSystemDarwin,
+ Format: build.FormatMach,
+ },
}, nil
// TODO(jsimsa): This function assumes the existence of a profile
diff --git a/services/device/internal/impl/utiltest/app.go b/services/device/internal/impl/utiltest/app.go
index 1f959b8..093796d 100644
--- a/services/device/internal/impl/utiltest/app.go
+++ b/services/device/internal/impl/utiltest/app.go
@@ -11,7 +11,6 @@
"io/ioutil"
"os"
"path/filepath"
- "reflect"
"strings"
"testing"
"time"
@@ -22,6 +21,8 @@
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/x/lib/vlog"
+ "v.io/x/ref/lib/exec"
+ "v.io/x/ref/lib/mgmt"
"v.io/x/ref/lib/signals"
"v.io/x/ref/lib/xrpc"
"v.io/x/ref/services/device/internal/suid"
@@ -63,8 +64,9 @@
}
type PingArgs struct {
- Username, FlagValue, EnvValue string
- Pid int
+ Username, FlagValue, EnvValue,
+ DefaultPeerBlessings, PubBlessingPrefixes, InstanceName string
+ Pid int
}
// ping makes a RPC from the App back to the invoking device manager
@@ -82,11 +84,20 @@
args := &PingArgs{
// TODO(rjkroege): Consider validating additional parameters
// from helper.
- Username: savedArgs.Uname,
- FlagValue: flagValue,
- EnvValue: os.Getenv(TestEnvVarName),
- Pid: os.Getpid(),
+ Username: savedArgs.Uname,
+ FlagValue: flagValue,
+ EnvValue: os.Getenv(TestEnvVarName),
+ Pid: os.Getpid(),
+ DefaultPeerBlessings: v23.GetPrincipal(ctx).BlessingStore().ForPeer("nonexistent").String(),
}
+
+ if handle, err := exec.GetChildHandle(); err != nil {
+ vlog.Fatalf("Couldn't get Child Handle: %v", err)
+ } else {
+ args.PubBlessingPrefixes, _ = handle.Config.Get(mgmt.PublisherBlessingPrefixesKey)
+ args.InstanceName, _ = handle.Config.Get(mgmt.InstanceNameKey)
+ }
+
client := v23.GetClient(ctx)
if call, err := client.StartCall(ctx, "pingserver", "Ping", []interface{}{args}); err != nil {
vlog.Fatalf("StartCall failed: %v", err)
@@ -179,17 +190,12 @@
return args
}
-func (p PingServer) VerifyPingArgs(t *testing.T, username, flagValue, envValue string) {
+func (p PingServer) VerifyPingArgs(t *testing.T, username, flagValue, envValue string) PingArgs {
args := p.WaitForPingArgs(t)
- wantArgs := PingArgs{
- Username: username,
- FlagValue: flagValue,
- EnvValue: envValue,
- Pid: args.Pid, // We are not checking for a value of Pid
+ if args.Username != username || args.FlagValue != flagValue || args.EnvValue != envValue {
+ t.Fatalf(testutil.FormatLogLine(2, "got ping args %q, expected [username = %v, flag value = %v, env value = %v]", args, username, flagValue, envValue))
}
- if !reflect.DeepEqual(args, wantArgs) {
- t.Fatalf(testutil.FormatLogLine(2, "got ping args %q, expected %q", args, wantArgs))
- }
+ return args // Useful for tests that want to check other values in the PingArgs result
}
// HangingApp is the same as App, except that it does not exit properly after
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
index 61d2ada..bed7179 100644
--- a/services/device/mgmt_v23_test.go
+++ b/services/device/mgmt_v23_test.go
@@ -120,21 +120,20 @@
binStagingDir = mkSubdir(i, workDir, "bin")
dmInstallDir = filepath.Join(workDir, "dm")
- // All vanadium command-line utitilities will be run by a
- // principal that has "root/alice" as its blessing.
+ // Most vanadium command-line utilities will be run by a
+ // principal that has "root/u/alice" as its blessing.
// (Where "root" comes from i.Principal().BlessingStore().Default()).
// Create those credentials and options to use to setup the
// binaries with them.
- aliceCreds, _ = i.Shell().NewChildCredentials("alice")
+ aliceCreds, _ = i.Shell().NewChildCredentials("u/alice")
aliceOpts = i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(aliceCreds)
// Build all the command-line tools and set them up to run as alice.
// applicationd/binaryd servers will be run by alice too.
+ // TODO: applicationd/binaryd should run as a separate "service" role, as
+ // alice is just a user.
namespaceBin = i.BuildV23Pkg("v.io/x/ref/cmd/namespace").WithStartOpts(aliceOpts)
- debugBin = i.BuildV23Pkg("v.io/x/ref/services/debug/debug").WithStartOpts(aliceOpts)
deviceBin = i.BuildV23Pkg("v.io/x/ref/services/device/device").WithStartOpts(aliceOpts)
- binaryBin = i.BuildV23Pkg("v.io/x/ref/services/binary/binary").WithStartOpts(aliceOpts)
- applicationBin = i.BuildV23Pkg("v.io/x/ref/services/application/application").WithStartOpts(aliceOpts)
binarydBin = i.BuildV23Pkg("v.io/x/ref/services/binary/binaryd").WithStartOpts(aliceOpts)
applicationdBin = i.BuildV23Pkg("v.io/x/ref/services/application/applicationd").WithStartOpts(aliceOpts)
@@ -150,6 +149,42 @@
mtName = "devices/" + hostname // Name under which the device manager will publish itself.
)
+ // We also need some tools running with different sets of credentials...
+
+ // Administration tasks will be performed with a blessing that represents a corporate
+ // adminstrator (which is usually a role account)
+ adminCreds, err := i.Shell().NewChildCredentials("r/admin")
+ if err != nil {
+ i.Fatalf("generating admin creds: %v", err)
+ }
+ adminOpts := i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(adminCreds)
+ adminDeviceBin := deviceBin.WithStartOpts(adminOpts)
+ debugBin := i.BuildV23Pkg("v.io/x/ref/services/debug/debug").WithStartOpts(adminOpts)
+
+ // A special set of credentials will be used to give two blessings to the device manager
+ // when claiming it -- one blessing will be from the corporate administrator role who owns
+ // the machine, and the other will be a manufacturer blessing. (This is a hack until
+ // there's a way to separately supply a manufacturer blessing. Eventually, the claim
+ // would really be done by the administator, and the adminstrator's blessing would get
+ // added to the manufacturer's blessing, which would already be present.)
+ claimCreds, err := i.Shell().AddToChildCredentials(adminCreds, "m/orange/zphone5/ime-i007")
+ if err != nil {
+ i.Fatalf("adding the mfr blessing to admin creds: %v", err)
+ }
+ claimOpts := i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(claimCreds)
+ claimDeviceBin := deviceBin.WithStartOpts(claimOpts)
+
+ // Another set of credentials be used to represent the application publisher, who
+ // signs and pushes binaries
+ pubCreds, err := i.Shell().NewChildCredentials("a/rovio")
+ if err != nil {
+ i.Fatalf("generating publisher creds: %v", err)
+ }
+ pubOpts := i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(pubCreds)
+ pubDeviceBin := deviceBin.WithStartOpts(pubOpts)
+ applicationBin := i.BuildV23Pkg("v.io/x/ref/services/application/application").WithStartOpts(pubOpts)
+ binaryBin := i.BuildV23Pkg("v.io/x/ref/services/binary/binary").WithStartOpts(pubOpts)
+
if withSuid {
// In multiuser mode, deviceUserFlag needs execute access to
// tempDir.
@@ -167,7 +202,7 @@
"v.io/x/ref/services/device/suidhelper",
"v.io/x/ref/services/device/inithelper")
- appDName := "applicationd"
+ appDName := "applications"
devicedAppName := filepath.Join(appDName, "deviced", "test")
deviceScriptArguments := []string{
@@ -218,8 +253,8 @@
claimableEP = string(matches[1])
break
}
- // Claim the device as "root/alice/myworkstation".
- deviceBin.Start("claim", claimableEP, "myworkstation")
+ // Claim the device as "root/u/alice/myworkstation".
+ claimDeviceBin.Start("claim", claimableEP, "myworkstation")
resolve := func(name string) string {
resolver := func() (interface{}, error) {
@@ -239,22 +274,25 @@
// Wait for the device manager to publish its mount table entry.
mtEP := resolve(mtName)
+ adminDeviceBin.Run("acl", "set", mtName+"/devmgr/device", "root/u/alice", "Read,Resolve,Write")
if withSuid {
- deviceBin.Start("associate", "add", mtName+"/devmgr/device", appUser, "root/alice")
+ adminDeviceBin.Start("associate", "add", mtName+"/devmgr/device", appUser, "root/u/alice")
- aai := deviceBin.Start("associate", "list", mtName+"/devmgr/device")
- if got, expected := strings.Trim(aai.Output(), "\n "), "root/alice "+appUser; got != expected {
+ aai := adminDeviceBin.Start("associate", "list", mtName+"/devmgr/device")
+ if got, expected := strings.Trim(aai.Output(), "\n "), "root/u/alice "+appUser; got != expected {
i.Fatalf("association test, got %v, expected %v", got, expected)
}
}
// Verify the device's default blessing is as expected.
+ mfrBlessing := "root/m/orange/zphone5/ime-i007/myworkstation"
+ ownerBlessing := "root/r/admin/myworkstation"
inv := debugBin.Start("stats", "read", mtName+"/devmgr/__debug/stats/security/principal/*/blessingstore")
- inv.ExpectSetEventuallyRE(".*Default Blessings[ ]+root/alice/myworkstation$")
+ inv.ExpectSetEventuallyRE(".*Default Blessings[ ]+" + mfrBlessing + "," + ownerBlessing)
// Get the device's profile, which should be set to non-empty string
- inv = deviceBin.Start("describe", mtName+"/devmgr/device")
+ inv = adminDeviceBin.Start("describe", mtName+"/devmgr/device")
parts := inv.ExpectRE(`{Profiles:map\[(.*):{}\]}`, 1)
expectOneMatch := func(parts [][]string) string {
@@ -271,17 +309,19 @@
// Start a binaryd server that will serve the binary for the test
// application to be installed on the device.
- binarydName := "binaryd"
+ binarydName := "binaries"
binarydBin.Start(
"--name="+binarydName,
"--root-dir="+filepath.Join(workDir, "binstore"),
"--v23.tcp.address=127.0.0.1:0",
"--http=127.0.0.1:0")
- sampleAppBinName := binarydName + "/testapp"
- binaryBin.Run("upload", sampleAppBinName, binarydBin.Path())
- if got := namespaceBin.Run("glob", sampleAppBinName); len(got) == 0 {
- i.Fatalf("glob failed for %q", sampleAppBinName)
- }
+ // Allow publishers to update binaries
+ deviceBin.Run("acl", "set", binarydName, "root/a", "Write")
+
+ // We are also going to use the binaryd binary as our test app binary. Once our test app
+ // binary is published to the binaryd server started above, this (augmented with a
+ // timestamp) is the name the test app binary will have.
+ sampleAppBinName := binarydName + "/binaryd"
// Start an applicationd server that will serve the application
// envelope for the test application to be installed on the device.
@@ -290,7 +330,10 @@
"--store="+mkSubdir(i, workDir, "appstore"),
"--v23.tcp.address=127.0.0.1:0",
)
- sampleAppName := appDName + "/testapp/v0"
+ // Allow publishers to create and update envelopes
+ deviceBin.Run("acl", "set", appDName, "root/a", "Read,Write,Resolve")
+
+ sampleAppName := appDName + "/testapp/0"
appPubName := "testbinaryd"
appEnvelopeFilename := filepath.Join(workDir, "app.envelope")
appEnvelope := fmt.Sprintf("{\"Title\":\"BINARYD\", \"Args\":[\"--name=%s\", \"--root-dir=./binstore\", \"--v23.tcp.address=127.0.0.1:0\", \"--http=127.0.0.1:0\"], \"Binary\":{\"File\":%q}, \"Env\":[]}", appPubName, sampleAppBinName)
@@ -314,6 +357,12 @@
}
}
+ // Publish the app (This uses the binarydBin binary and the testapp envelope from above)
+ pubDeviceBin.Start("publish", "-from", filepath.Dir(binarydBin.Path()), "-readers", "root/r/admin", filepath.Base(binarydBin.Path())+":testapp").WaitOrDie(os.Stdout, os.Stderr)
+ if got := namespaceBin.Run("glob", sampleAppBinName); len(got) == 0 {
+ i.Fatalf("glob failed for %q", sampleAppBinName)
+ }
+
// Install the app on the device.
inv = deviceBin.Start("install", mtName+"/devmgr/apps", sampleAppName)
installationName := inv.ReadLine()
@@ -353,9 +402,13 @@
i.Errorf("app expected to be running as %v but is running as %v", appUser, uname)
}
- // Verify the app's default blessing.
+ // Verify the app's blessings. We check the default blessing, as well as the
+ // "..." blessing, which should be the default blessing plus a publisher blessing.
+ userBlessing := "root/u/alice/myapp"
+ pubBlessing := "root/a/rovio/apps/published/binaryd"
+ appBlessing := mfrBlessing + "/a/" + pubBlessing + "," + ownerBlessing + "/a/" + pubBlessing
inv = debugBin.Start("stats", "read", instanceName+"/stats/security/principal/*/blessingstore")
- inv.ExpectSetEventuallyRE(".*Default Blessings[ ]+root/alice/myapp$")
+ inv.ExpectSetEventuallyRE(".*Default Blessings[ ]+"+userBlessing+"$", "[.][.][.][ ]+"+userBlessing+","+appBlessing)
// Kill and delete the instance.
deviceBin.Run("kill", instanceName)
@@ -370,9 +423,15 @@
i.Fatalf("output expected for glob %s/logs/..., but got none", instanceName)
}
+ // TODO: The deviced binary should probably be published by someone other than rovio :-)
+ // Maybe publishing the deviced binary should eventually use "device publish" too?
+ // For now, it uses the "application" and "binary" tools directly to ensure that those work
+
// Upload a deviced binary
devicedAppBinName := binarydName + "/deviced"
binaryBin.Run("upload", devicedAppBinName, i.BuildGoPkg("v.io/x/ref/services/device/deviced").Path())
+ // Allow root/r/admin and its devices to read the binary
+ deviceBin.Run("acl", "set", devicedAppBinName, "root/r/admin", "Read")
// Upload a device manager envelope.
devicedEnvelopeFilename := filepath.Join(workDir, "deviced.envelope")
@@ -380,9 +439,11 @@
ioutil.WriteFile(devicedEnvelopeFilename, []byte(devicedEnvelope), 0666)
defer os.Remove(devicedEnvelopeFilename)
applicationBin.Run("put", devicedAppName, deviceProfile, devicedEnvelopeFilename)
+ // Allow root/r/admin and its devices to read the envelope
+ deviceBin.Run("acl", "set", devicedAppName, "root/r/admin", "Read")
// Update the device manager.
- deviceBin.Run("update", mtName+"/devmgr/device")
+ adminDeviceBin.Run("update", mtName+"/devmgr/device")
resolveChange := func(name, old string) string {
resolver := func() (interface{}, error) {
inv := namespaceBin.Start("resolve", name)
@@ -403,7 +464,7 @@
}
// Revert the device manager
- deviceBin.Run("revert", mtName+"/devmgr/device")
+ adminDeviceBin.Run("revert", mtName+"/devmgr/device")
mtEP = resolveChange(mtName, mtEP)
// Verify that device manager's mounttable is still published under the
diff --git a/services/identity/internal/auditor/sql_database.go b/services/identity/internal/auditor/sql_database.go
index 222c2b3..9dac3a8 100644
--- a/services/identity/internal/auditor/sql_database.go
+++ b/services/identity/internal/auditor/sql_database.go
@@ -74,6 +74,7 @@
dst <- databaseEntry{decodeErr: fmt.Errorf("Failed to query for all audits: %v", err)}
return
}
+ defer rows.Close()
for rows.Next() {
var dbentry databaseEntry
if err = rows.Scan(&dbentry.email, &dbentry.caveats, &dbentry.timestamp, &dbentry.blessings); err != nil {
diff --git a/services/identity/internal/blesser/oauth.go b/services/identity/internal/blesser/oauth.go
index 16c0866..4403a95 100644
--- a/services/identity/internal/blesser/oauth.go
+++ b/services/identity/internal/blesser/oauth.go
@@ -76,16 +76,7 @@
var caveat security.Caveat
var err error
if b.revocationManager != nil {
- // TODO(ashankar,suharshs): Remove: Added for debugging
- start := time.Now()
caveat, err = b.revocationManager.NewCaveat(self.PublicKey(), b.dischargerLocation)
- var id string
- if caveat.ThirdPartyDetails() != nil {
- id = caveat.ThirdPartyDetails().ID()
- }
- if d := time.Since(start); d > time.Second || err != nil {
- ctx.Infof("NewCaveat took %v and returned error %v (caveat id: %v): (%v <-> %v)", d, err, id, call.RemoteEndpoint(), call.LocalEndpoint())
- }
} else {
caveat, err = security.NewExpiryCaveat(time.Now().Add(b.duration))
}
diff --git a/services/identity/internal/revocation/revocation_manager.go b/services/identity/internal/revocation/revocation_manager.go
index e9f6649..9a274ee 100644
--- a/services/identity/internal/revocation/revocation_manager.go
+++ b/services/identity/internal/revocation/revocation_manager.go
@@ -62,10 +62,7 @@
if err != nil {
return empty, err
}
- r.ctx.Infof("revocationDB.InsertCaveat(%s,%v) called", cav.ThirdPartyDetails().ID(), revocation)
if err = revocationDB.InsertCaveat(cav.ThirdPartyDetails().ID(), revocation[:]); err != nil {
- // TODO(suharshs): Remove this log.
- r.ctx.Infof("revocationDB.InsertCaveat(%s,%v) failed with %v", cav.ThirdPartyDetails().ID(), revocation, err)
return empty, err
}
return cav, nil
@@ -87,7 +84,6 @@
}
func isRevoked(ctx *context.T, call security.Call, key []byte) error {
- start := time.Now()
revocationLock.RLock()
if revocationDB == nil {
revocationLock.RUnlock()
@@ -96,11 +92,6 @@
revocationLock.RUnlock()
revoked, err := revocationDB.IsRevoked(key)
if err != nil {
- // TODO(ashankar): Remove. Added for debugging.
- ctx.Infof("IsRevoked(%v) returned %v (%v <-> %v)", key, err, call.RemoteEndpoint(), call.LocalEndpoint())
- }
- if d := time.Since(start); d > time.Second {
- ctx.Infof("IsRevoked(%v) took %v (%v <-> %v)", key, d, call.RemoteEndpoint(), call.LocalEndpoint())
}
if revoked {
return fmt.Errorf("revoked")
diff --git a/services/identity/internal/revocation/sql_database.go b/services/identity/internal/revocation/sql_database.go
index 0e88eda..b50beb5 100644
--- a/services/identity/internal/revocation/sql_database.go
+++ b/services/identity/internal/revocation/sql_database.go
@@ -8,8 +8,6 @@
"database/sql"
"encoding/hex"
"fmt"
- "os"
- "runtime/pprof"
"time"
)
@@ -29,53 +27,22 @@
}
func (s *sqlDatabase) InsertCaveat(thirdPartyCaveatID string, revocationCaveatID []byte) error {
- return dumpStackTraceIfTakingTooLong(fmt.Sprintf("InsertCaveat(%v, %v)", thirdPartyCaveatID, revocationCaveatID), func() error {
- _, err := s.insertCaveatStmt.Exec(thirdPartyCaveatID, hex.EncodeToString(revocationCaveatID))
- return err
- })
-}
-
-func dumpStackTraceIfTakingTooLong(tag string, f func() error) error {
- ch := make(chan error)
- go dumpStack(ch, tag, time.Now())
- err := f()
- ch <- err
- close(ch)
+ _, err := s.insertCaveatStmt.Exec(thirdPartyCaveatID, hex.EncodeToString(revocationCaveatID))
return err
}
-func dumpStack(ch chan error, tag string, start time.Time) {
- timeout := 5 * time.Minute
- select {
- case <-time.After(timeout):
- fmt.Fprintf(os.Stderr, "%v hasn't completed for %v\n", tag, time.Since(start))
- fmt.Fprintf(os.Stderr, "Stack trace:\n")
- pprof.Lookup("goroutine").WriteTo(os.Stderr, 2)
- fmt.Fprintln(os.Stderr)
- dumpStack(ch, tag, start)
- case err := <-ch:
- fmt.Fprintf(os.Stderr, "%v completed with %v\n", tag, err)
- }
-}
-
func (s *sqlDatabase) Revoke(thirdPartyCaveatID string) error {
_, err := s.revokeStmt.Exec(time.Now(), thirdPartyCaveatID)
return err
}
func (s *sqlDatabase) IsRevoked(revocationCaveatID []byte) (bool, error) {
- var revoked bool
- err := dumpStackTraceIfTakingTooLong(fmt.Sprintf("IsRevoked(%v)", revocationCaveatID), func() error {
- rows, err := s.isRevokedStmt.Query(hex.EncodeToString(revocationCaveatID))
- defer rows.Close()
- if err != nil {
- revoked = false
- return err
- }
- revoked = rows.Next()
- return nil
- })
- return revoked, err
+ rows, err := s.isRevokedStmt.Query(hex.EncodeToString(revocationCaveatID))
+ defer rows.Close()
+ if err != nil {
+ return false, err
+ }
+ return rows.Next(), err
}
func (s *sqlDatabase) RevocationTime(thirdPartyCaveatID string) (*time.Time, error) {
diff --git a/services/internal/servicetest/modules.go b/services/internal/servicetest/modules.go
index ee13fa5..56b4264 100644
--- a/services/internal/servicetest/modules.go
+++ b/services/internal/servicetest/modules.go
@@ -109,6 +109,35 @@
return sh, fn
}
+// CreateShell builds a new modules shell.
+func CreateShell(t *testing.T, ctx *context.T, p security.Principal) (*modules.Shell, func()) {
+ sh, err := modules.NewShell(ctx, p, testing.Verbose(), t)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ opts := sh.DefaultStartOpts()
+ opts.ExpectTimeout = ExpectTimeout
+ sh.SetDefaultStartOpts(opts)
+ // The shell, will, by default share credentials with its children.
+ sh.ClearVar(ref.EnvCredentials)
+
+ fn := func() {
+ ctx.VI(1).Info("------------ CLEANUP ------------")
+ ctx.VI(1).Info("---------------------------------")
+ ctx.VI(1).Info("--(cleaning up shell)------------")
+ if err := sh.Cleanup(os.Stdout, os.Stderr); err != nil {
+ t.Errorf(testutil.FormatLogLine(2, "sh.Cleanup failed with %v", err))
+ }
+ ctx.VI(1).Info("--(done cleaning up shell)-------")
+ }
+ nsRoots := v23.GetNamespace(ctx).Roots()
+ if len(nsRoots) == 0 {
+ t.Fatalf("shell context has no namespace roots")
+ }
+ sh.SetVar(ref.EnvNamespacePrefix, nsRoots[0])
+ return sh, fn
+}
+
// RunCommand runs a modules command.
func RunCommand(t *testing.T, sh *modules.Shell, env []string, prog modules.Program, args ...string) modules.Handle {
h, err := sh.StartWithOpts(sh.DefaultStartOpts(), env, prog, args...)
diff --git a/test/modules/shell.go b/test/modules/shell.go
index cb0b564..0f4106a 100644
--- a/test/modules/shell.go
+++ b/test/modules/shell.go
@@ -323,11 +323,15 @@
// Since the Shell type is intended for tests, it is not required to provide
// caveats. In production scenarios though, one must think long and hard
// before blessing anothing principal without any caveats.
-func (sh *Shell) NewChildCredentials(extension string, caveats ...security.Caveat) (c *CustomCredentials, err error) {
+func (sh *Shell) NewChildCredentials(extension string, caveats ...security.Caveat) (*CustomCredentials, error) {
creds, err := sh.NewCustomCredentials()
if creds == nil {
return nil, err
}
+ return sh.AddToChildCredentials(creds, extension, caveats...)
+}
+
+func (sh *Shell) AddToChildCredentials(creds *CustomCredentials, extension string, caveats ...security.Caveat) (*CustomCredentials, error) {
parent := sh.principal
child := creds.p
if len(caveats) == 0 {
@@ -340,10 +344,16 @@
if err != nil {
return nil, err
}
- if err := child.BlessingStore().SetDefault(blessings); err != nil {
+
+ union, err := security.UnionOfBlessings(child.BlessingStore().Default(), blessings)
+ if err != nil {
return nil, err
}
- if _, err := child.BlessingStore().Set(blessings, security.AllPrincipals); err != nil {
+
+ if err := child.BlessingStore().SetDefault(union); err != nil {
+ return nil, err
+ }
+ if _, err := child.BlessingStore().Set(union, security.AllPrincipals); err != nil {
return nil, err
}
if err := child.AddToRoots(blessings); err != nil {
diff --git a/test/testutil/security.go b/test/testutil/security.go
index a59c5f8..2c673dd 100644
--- a/test/testutil/security.go
+++ b/test/testutil/security.go
@@ -78,6 +78,12 @@
return &IDProvider{p, b}
}
+// IDProviderFromPrincipal creates and IDProvider for the given principal. It
+// will bless other principals with extensions of its default blessing.
+func IDProviderFromPrincipal(p security.Principal) *IDProvider {
+ return &IDProvider{p, p.BlessingStore().Default()}
+}
+
// Bless sets up the provided principal to use blessings from idp as its
// default. It is shorthand for:
// b, _ := idp.NewBlessings(who, extension, caveats...)
@@ -89,16 +95,7 @@
if err != nil {
return err
}
- if err := who.BlessingStore().SetDefault(b); err != nil {
- return err
- }
- if _, err := who.BlessingStore().Set(b, security.AllPrincipals); err != nil {
- return err
- }
- if err := who.AddToRoots(b); err != nil {
- return err
- }
- return nil
+ return vsecurity.SetDefaultBlessings(who, b)
}
// NewBlessings returns Blessings that extend the identity provider's blessing