The Suffix - Part II

Wherein you add fine-grained security to control access to your multiple services. This is an advanced tutorial.

Prerequisites:
All tutorials require Vanadium installation, and must run in a bash shell.
Further, please download this script then source it:

source ~/Downloads/scenario-e-setup.sh

This command wipes tutorial files and defines shell environment variables. If you start a new terminal, and just want to re-establish the environment, see instructions here.

If you would like to generate files from this tutorial without the copy/paste steps, download and source this script. Files will be created in the $V_TUT directory.

Introduction

In the suffix Part I, you built a fortune server with multiple services. For simplicity, this server used the default authorizer and only allowed calls from the same principal.

In Part II, you will learn how to implement a fine-grained access control policy for your services.

Let's add some requirements to our Part I example.

Requirements

Your company, Prophecies Inc, needs a server to support its team of prophetic consultants - Cassandra, Nostradamus, etc. Each consultant needs its own data and customers.

  1. Many services: To support hiring new consultants, the server must be able to create fortune services on the fly, each with its own set of fortunes. [done]

  2. Discovery: Customers must be able to discover all the available services. [done]

  3. Consultants: Those blessed by the company. Only they can create a service or add a fortune to it. [new]

  4. Customers: Those blessed by a particular consultant. Only they can get a fortune from that consultant. [new]

  5. Internships: A consultant can delegate their ability to an apprentice fortune teller. [new]

To meet these requirements, we'll use a custom authorizer.

We won't need a new dispatcher, or a new service implementation.

New authorizer

In Part I and other tutorials we encountered the so-called default authorizer and the Permissions authorizer. The requirements here are out of scope for the default authorizer. The Permissions authorizer at the time of writing is configured at server start time, before we know the names of the services that the server will be running, so it falls short as well.

The following implementation of the authorizer interface attempts to make a naming policy (both for services and blessings) that makes the authorization scheme easy to understand. One look at a blessing name should make the access of the blessing obvious.

 cat - <<EOF >$V_TUT/src/fortune/server/util/authorizer.go
package util

import (
  "strings"
  "fortune/ifc"
  "errors"
  "v.io/v23/context"
  "v.io/v23/security"
  "v.io/v23/security/access"
)

type myAuthorizer struct {}

// The method being called - is it tagged?
func isTagged(call security.Call, tag string) (bool) {
  for _, mTag := range call.MethodTags() {
    if tag == mTag.RawString() {
      return true
    }
  }
  return false
}

func join(args ...string) security.BlessingPattern {
  return security.BlessingPattern(strings.Join(args, security.ChainSeparator))
}

func (my myAuthorizer) Authorize(ctx *context.T, call security.Call) error {
  if call.Suffix() == "" {
    return nil
  }
  var (
    serverBlessings    = security.LocalBlessingNames(ctx, call)
    clientBlessings, _ = security.RemoteBlessingNames(ctx, call)
    consultant         = join(serverBlessings[0], "consultant", call.Suffix())
    intern             = join(string(consultant), "intern")
    customer           = join(string(consultant), "cust")
  )
  if consultant.MakeNonExtendable().MatchedBy(clientBlessings...) {
    return nil
  }
  if intern.MatchedBy(clientBlessings...) {
    return nil
  }
  if (isTagged(call, string(ifc.Reader)) ||
      isTagged(call, string(access.Resolve))) && customer.MatchedBy(clientBlessings...) {
    return nil
  }

  return errors.New("access denied.")
}

func MakeAuthorizer() security.Authorizer {
  return &myAuthorizer {}
}
EOF

Code walk

In the body of Authorize, the first if grants full access to the root service to everyone. Remember that the root service in our dispatcher is the ChildrenGlobber that publishes the name of the fortune services in the server's namespace.

The second if grants full access to a given service if the client has a blessing of the form {server}:consultant:{service}.

The third if allows interns the same sort of service-specific access if their blessing has a particular pattern.

The fourth if allows customers to get read-only access, exploiting the Reader annotation in the fortune VDL file.

Try it

New principals

First, initialize some principals.

$V_BIN/principal create --with-passphrase=false --overwrite \
    $V_TUT/cred/prophInc prophInc
$V_BIN/principal create --with-passphrase=false --overwrite \
    $V_TUT/cred/cassandra cassandra
$V_BIN/principal create --with-passphrase=false --overwrite \
    $V_TUT/cred/nostradamus nostradamus
$V_BIN/principal create --with-passphrase=false --overwrite \
    $V_TUT/cred/alice alice
$V_BIN/principal create --with-passphrase=false --overwrite \
    $V_TUT/cred/bob bob

Start the server

Fire up the server with the new authorizer code, and mount it in a mount table:

go install fortune/server

Start the fortune server.

kill_tut_process TUT_PID_SERVER
$V_TUT/bin/server \
    --v23.credentials $V_TUT/cred/prophInc \
    --endpoint-file-name $V_TUT/server.txt &
TUT_PID_SERVER=$!

Start a mount table.

PORT_MT=23000  # Pick an unused port.
kill_tut_process TUT_PID_MT
$V_BIN/mounttabled \
    --v23.credentials $V_TUT/cred/prophInc \
    --v23.tcp.address :$PORT_MT &
TUT_PID_MT=$!
export V23_NAMESPACE=/localhost:$PORT_MT

Add the fortune server to the mount table at the name prophInc:

$V_BIN/namespace \
    --v23.credentials $V_TUT/cred/prophInc \
    mount prophInc `cat $V_TUT/server.txt` 2h

Hire Cassandra

Try to run a client as Cassandra:

Expect it to fail.

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/cassandra \
    --server prophInc/cassandra

The error message will be something like: Client doesn't trust the server. That's a runtime error - the code you wrote above hasn't even been run yet. Cassandra has no knowledege of Prophecies Inc.

You've learned how to fix this; Cassandra needs a blessing from the server's principal:

$V_BIN/principal bless \
    --v23.credentials $V_TUT/cred/prophInc \
    --for=24h $V_TUT/cred/cassandra consultant:cassandra | \
        $V_BIN/principal set \
            --v23.credentials $V_TUT/cred/cassandra \
            forpeer - prophInc

Try again.

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/cassandra \
    --server prophInc/cassandra

That works.

Likewise, the client should be able to write to that service:

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/cassandra \
    --server prophInc/cassandra \
    --add 'Do not visit Sparta!'

More use cases

Hire Nostradamus

What can Nostradamus do at this point? Nothing, since the company hasn't blessed him. Let's do that:

$V_BIN/principal bless \
  --v23.credentials $V_TUT/cred/prophInc \
  --for=24h $V_TUT/cred/nostradamus consultant:nostradamus |\
        $V_BIN/principal set \
            --v23.credentials $V_TUT/cred/nostradamus \
            forpeer - prophInc

Now verify that Nostradamus can read/write his own service:

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/nostradamus \
    --server prophInc/nostradamus

But Nostradamus should not be able to read his colleague Cassandra's service:

This should fail.

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/nostradamus \
    --server prophInc/cassandra

Also, notice that Nostradadus doesn't see cassandra in the namespace.

$V_BIN/namespace \
    --v23.credentials $V_TUT/cred/nostradamus \
    glob "prophInc/*"

Interns

Suppose Cassandra needs some vacation, and wants Alice to take over. Alice currently has no blessings relevant to the running service, so can do nothing.

Cassandra can change this by hiring Alice as an intern. Cassandra can do so without access to the credentials belonging to Prophesies Inc.

$V_BIN/principal get \
   --v23.credentials $V_TUT/cred/cassandra \
   forpeer prophInc | \
       $V_BIN/principal bless \
         --v23.credentials $V_TUT/cred/cassandra \
         --with=- --for=24h $V_TUT/cred/alice intern:alice |\
               $V_BIN/principal set \
                  --v23.credentials $V_TUT/cred/alice \
                  forpeer - prophInc

Now, like Cassandra, Alice can read and write Cassandra's service:

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/alice \
    --server prophInc/cassandra

But Alice cannot access Nostradamus' service:

This should fail.

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/alice \
    --server prophInc/nostradamus

And Alice can only see cassandra in the namespace:

$V_BIN/namespace \
    --v23.credentials $V_TUT/cred/alice \
    glob "prophInc/*"

Customers

Of the principals created for this tutorial, only Bob is untouched.

Make him a customer to Nostradamus:

$V_BIN/principal get \
   --v23.credentials $V_TUT/cred/nostradamus \
   forpeer prophInc | \
       $V_BIN/principal bless \
         --v23.credentials $V_TUT/cred/nostradamus \
         --with=- --for=24h $V_TUT/cred/bob cust:bob | \
               $V_BIN/principal set \
                  --v23.credentials $V_TUT/cred/bob \
                  forpeer - prophInc

Verify that Bob can get fortunes from Nostradamus:

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/bob \
    --server prophInc/nostradamus

But that he cannot write to Nostradamus' service:

This should fail.

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/bob \
    --server prophInc/nostradamus \
    --add 'Bob is so Cool!'

Verify on your own that Bob is unable to access Cassandra's service.

This should fail.

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/bob \
    --server prophInc/cassandra

As a customer, Bob can only see nostradamus:

$V_BIN/namespace \
    --v23.credentials $V_TUT/cred/bob \
    glob "prophInc/*"

Exercises

Cassandra peeks at Nostradamus

Alice is Cassandra's intern.

What would it take to make her also a customer of Nostradamus? That is, what needs to happen to make the following command work?

$V_TUT/bin/client \
    --v23.credentials $V_TUT/cred/alice \
    --server prophInc/nostradamus

Nostradamus cheats

Can Nostradamus bless himself to gain access to Cassandra's data?

What if he got 'hired' with the name consultant:cassandra2?

Internship

Can an intern have an intern? If so, can you prevent it?

Can an intern accept a new customer?

Cleanup

kill_tut_process TUT_PID_SERVER
kill_tut_process TUT_PID_MT
unset V23_NAMESPACE

Summary