Merge "veyron2: Panic if the user tries to use the runtime when it is not set."
diff --git a/runtimes/google/ipc/blessings_cache.go b/runtimes/google/ipc/blessings_cache.go
index effa8e8..aadbbfc 100644
--- a/runtimes/google/ipc/blessings_cache.go
+++ b/runtimes/google/ipc/blessings_cache.go
@@ -1,6 +1,7 @@
package ipc
import (
+ "crypto/sha256"
"fmt"
"reflect"
"sync"
@@ -12,7 +13,7 @@
// clientEncodeBlessings gets or inserts the blessings into the cache.
func clientEncodeBlessings(cache stream.VCDataCache, blessings security.Blessings) ipc.BlessingsRequest {
- blessingsCacheAny := cache.GetOrInsert(clientBlessingsKey{}, newClientBlessingsCache)
+ blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache)
blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
return blessingsCache.getOrInsert(blessings)
}
@@ -20,7 +21,7 @@
// clientAckBlessings verifies that the server has updated its cache to include blessings.
// This means that subsequent rpcs from the client with blessings can send only a cache key.
func clientAckBlessings(cache stream.VCDataCache, blessings security.Blessings) {
- blessingsCacheAny := cache.GetOrInsert(clientBlessingsKey{}, newClientBlessingsCache)
+ blessingsCacheAny := cache.GetOrInsert(clientBlessingsCacheKey{}, newClientBlessingsCache)
blessingsCache := blessingsCacheAny.(*clientBlessingsCache)
blessingsCache.acknowledge(blessings)
}
@@ -28,77 +29,94 @@
// serverDecodeBlessings insert the key and blessings into the cache or get the blessings if only
// key is provided in req.
func serverDecodeBlessings(cache stream.VCDataCache, req ipc.BlessingsRequest, stats *ipcStats) (security.Blessings, error) {
- blessingsCacheAny := cache.GetOrInsert(serverBlessingsKey{}, newServerBlessingsCache)
+ blessingsCacheAny := cache.GetOrInsert(serverBlessingsCacheKey{}, newServerBlessingsCache)
blessingsCache := blessingsCacheAny.(*serverBlessingsCache)
return blessingsCache.getOrInsert(req, stats)
}
// IMPLEMENTATION DETAILS BELOW
-// clientBlessingsCache is a thread-safe map from blessings to cache key.
+// clientBlessingsCache is a thread-safe map from blessings to cache id.
type clientBlessingsCache struct {
sync.RWMutex
- m map[security.Blessings]clientCacheValue
- key uint64
+ m map[[sha256.Size]byte]clientCacheValue
+ curId uint64
}
type clientCacheValue struct {
- key uint64
- // ack is set to true once the server has confirmed receipt of the cache key.
- // Clients that insert into the cache when ack is false must send both the key
+ id uint64
+ // ack is set to true once the server has confirmed receipt of the cache id.
+ // Clients that insert into the cache when ack is false must send both the id
// and the blessings.
ack bool
}
-// clientBlessingsKey is the key used to retrieve the clientBlessingsCache from the VCDataCache.
-type clientBlessingsKey struct{}
+// clientBlessingsCacheKey is the key used to retrieve the clientBlessingsCache from the VCDataCache.
+type clientBlessingsCacheKey struct{}
func newClientBlessingsCache() interface{} {
- return &clientBlessingsCache{m: make(map[security.Blessings]clientCacheValue)}
+ return &clientBlessingsCache{m: make(map[[sha256.Size]byte]clientCacheValue)}
+}
+
+func getBlessingsHashKey(blessings security.Blessings) (key [sha256.Size]byte) {
+ h := sha256.New()
+ for _, chain := range security.MarshalBlessings(blessings).CertificateChains {
+ if len(chain) == 0 {
+ continue
+ }
+ cert := chain[len(chain)-1]
+ s := sha256.Sum256(cert.Signature.R)
+ h.Write(s[:])
+ s = sha256.Sum256(cert.Signature.S)
+ h.Write(s[:])
+ }
+ copy(key[:], h.Sum(nil))
+ return
}
func (c *clientBlessingsCache) getOrInsert(blessings security.Blessings) ipc.BlessingsRequest {
+ key := getBlessingsHashKey(blessings)
c.RLock()
- val, exists := c.m[blessings]
+ val, exists := c.m[key]
c.RUnlock()
if exists {
return c.makeBlessingsRequest(val, blessings)
}
- // if the val doesn't exist we must create a new key, update the cache, and send the key and blessings.
+ // If the val doesn't exist we must create a new id, update the cache, and send the id and blessings.
c.Lock()
- defer c.Unlock()
- // we must check that the val wasn't inserted in the time we changed locks.
- val, exists = c.m[blessings]
- if exists {
- return c.makeBlessingsRequest(val, blessings)
+ // We must check that the val wasn't inserted in the time we changed locks.
+ val, exists = c.m[key]
+ if !exists {
+ val = clientCacheValue{id: c.nextIdLocked()}
+ c.m[key] = val
}
- newVal := clientCacheValue{key: c.nextKeyLocked()}
- c.m[blessings] = newVal
- return c.makeBlessingsRequest(newVal, blessings)
+ c.Unlock()
+ return c.makeBlessingsRequest(val, blessings)
}
func (c *clientBlessingsCache) acknowledge(blessings security.Blessings) {
+ key := getBlessingsHashKey(blessings)
c.Lock()
- val := c.m[blessings]
+ val := c.m[key]
val.ack = true
- c.m[blessings] = val
+ c.m[key] = val
c.Unlock()
}
func (c *clientBlessingsCache) makeBlessingsRequest(val clientCacheValue, blessings security.Blessings) ipc.BlessingsRequest {
if val.ack {
// when the value is acknowledged, only send the key, since the server has confirmed that it knows the key.
- return ipc.BlessingsRequest{Key: val.key}
+ return ipc.BlessingsRequest{Key: val.id}
}
// otherwise we still need to send both key and blessings, but we must ensure that we send the same key.
wireBlessings := security.MarshalBlessings(blessings)
- return ipc.BlessingsRequest{val.key, &wireBlessings}
+ return ipc.BlessingsRequest{val.id, &wireBlessings}
}
-// nextKeyLocked creates a new key for inserting blessings. It must be called after acquiring a writer lock.
-func (c *clientBlessingsCache) nextKeyLocked() uint64 {
- c.key++
- return c.key
+// nextIdLocked creates a new id for inserting blessings. It must be called after acquiring a writer lock.
+func (c *clientBlessingsCache) nextIdLocked() uint64 {
+ c.curId++
+ return c.curId
}
// serverBlessingsCache is a thread-safe map from cache key to blessings.
@@ -107,8 +125,8 @@
m map[uint64]security.Blessings
}
-// serverBlessingsKey is the key used to retrieve the serverBlessingsCache from the VCDataCache.
-type serverBlessingsKey struct{}
+// serverBlessingsCacheKey is the key used to retrieve the serverBlessingsCache from the VCDataCache.
+type serverBlessingsCacheKey struct{}
func newServerBlessingsCache() interface{} {
return &serverBlessingsCache{m: make(map[uint64]security.Blessings)}
@@ -131,6 +149,8 @@
stats.recordBlessingCache(true)
return cached, nil
}
+ // Always count the slow path as a cache miss since we do not get the benefit of sending only the cache key.
+ stats.recordBlessingCache(false)
// Slowpath, might need to update the cache, or check that the received blessings are
// the same as what's in the cache.
recv, err := security.NewBlessings(*req.Blessings)
@@ -144,10 +164,8 @@
if !reflect.DeepEqual(cached, recv) {
return nil, fmt.Errorf("client sent invalid Blessings")
}
- stats.recordBlessingCache(true)
return cached, nil
}
c.m[req.Key] = recv
- stats.recordBlessingCache(false)
return recv, nil
}
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 6ef039b..a472e68 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -736,8 +736,8 @@
// Encode the Blessings information for the client to authorize the flow.
var blessingsRequest ipc.BlessingsRequest
if fc.flow.LocalPrincipal() != nil {
- localBlessings := fc.flow.LocalPrincipal().BlessingStore().ForPeer(fc.server...)
- blessingsRequest = clientEncodeBlessings(fc.flow.VCDataCache(), localBlessings)
+ fc.blessings = fc.flow.LocalPrincipal().BlessingStore().ForPeer(fc.server...)
+ blessingsRequest = clientEncodeBlessings(fc.flow.VCDataCache(), fc.blessings)
}
// TODO(suharshs, ataly): Make security.Discharge a vdl type.
anyDischarges := make([]vdlutil.Any, len(fc.discharges))
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index 432242e..e9b7066 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -1118,7 +1118,9 @@
fs.server.streamMgr.ShutdownEndpoint(fs.RemoteEndpoint())
return old_verror.BadProtocolf("ipc: blessings cache failed: %v", err)
}
- fs.ackBlessings = true
+ if fs.clientBlessings != nil {
+ fs.ackBlessings = true
+ }
// TODO(suharshs, ataly): Make security.Discharge a vdl type.
for i, d := range req.Discharges {
diff --git a/security/agent/client.go b/security/agent/client.go
index af30ec3..0c2912a 100644
--- a/security/agent/client.go
+++ b/security/agent/client.go
@@ -268,7 +268,7 @@
if err != nil {
return err
}
- return b.caller.call("BlessingRootsAdd", results(), marshalledKey, blessing)
+ return b.caller.call("BlessingRootsRecognized", results(), marshalledKey, blessing)
}
func (b *blessingRoots) DebugString() (s string) {
diff --git a/security/agent/test.sh b/security/agent/test.sh
index 2e574a6..f4aaeba 100755
--- a/security/agent/test.sh
+++ b/security/agent/test.sh
@@ -9,6 +9,7 @@
build() {
AGENTD_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/security/agent/agentd')"
PINGPONG_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/security/agent/pingpong')"
+ TESTPRINCIPAL_BIN="$(shell_test::build_go_binary 'v.io/core/veyron/security/agent/test_principal')"
VRUN="$(shell_test::build_go_binary 'v.io/core/veyron/tools/vrun')"
}
@@ -21,20 +22,33 @@
shell_test::setup_server_test || shell_test::fail "line ${LINENO} failed to setup server test"
export VEYRON_CREDENTIALS="$(shell::tmp_dir)"
+ # Test all methods of the principal interface.
+ # (Errors are printed to STDERR)
+ echo -n "agentd: test_principal..."
+ "${AGENTD_BIN}" "${TESTPRINCIPAL_BIN}" >/dev/null || shell_test::fail "line ${LINENO}"
+ echo "OK"
+
+ # Use a different credentials directory for future tests
+ # since test_principal "pollutes" it.
+ export VEYRON_CREDENTIALS="$(shell::tmp_dir)"
# Test running a single app.
+ echo -n "agentd: single app..."
shell_test::start_server "${PINGPONG_BIN}" --server
"${AGENTD_BIN}" --v=4 "${PINGPONG_BIN}" || shell_test::fail "line ${LINENO}: failed to run pingpong"
local -r CREDENTIALS_UNDER_AGENT=$("${AGENTD_BIN}" bash -c 'echo ${VEYRON_CREDENTIALS}')
if [[ "${CREDENTIALS_UNDER_AGENT}" != "" ]]; then
shell_test::fail "line ${LINENO}: VEYRON_CREDENTIALS should not be set when running under the agent(${CREDENTIALS_UNDER_AGENT})"
fi
+ echo "OK"
# Test running multiple apps connecting to the same agent.
# Make sure the testchild.sh script get the same shell_test_BIN_DIR as the main script.
+ echo -n "agentd: multiple apps..."
export shell_test_BIN_DIR="${shell_test_BIN_DIR}"
RESULT=$(shell::check_result "${AGENTD_BIN}" bash "$(go list -f {{.Dir}} v.io/core/veyron/security/agent)/testchild.sh")
shell_test::assert_eq "${RESULT}" "0" "${LINENO}"
+ echo "OK"
# Verify the restart feature.
local -r COUNTER_FILE="${WORKDIR}/counter"
@@ -49,22 +63,28 @@
EOF
)
# Have the agent restart the child while its exit code is 0.
+ echo -n "agentd: restart child while its exit code is 0..."
echo "0" > "${COUNTER_FILE}"
RESULT=$(shell::check_result "${AGENTD_BIN}" --additional_principals="$(shell::tmp_dir)" --restart_exit_code="0" bash -c "${SCRIPT}")
shell_test::assert_eq "${RESULT}" "1" "${LINENO}"
[[ $(<"${COUNTER_FILE}") -eq 5 ]] || shell_test::fail "Failed"
+ echo "OK"
# Have the agent restart the child while its exit code is !0.
+ echo -n "agentd: restart child while its exit code != 0..."
echo "0" > "${COUNTER_FILE}"
RESULT=$(shell::check_result "${AGENTD_BIN}" --additional_principals="$(shell::tmp_dir)" --restart_exit_code="!0" bash -c "${SCRIPT}")
shell_test::assert_eq "${RESULT}" "0" "${LINENO}"
[[ $(<"${COUNTER_FILE}") -eq 1 ]] || shell_test::fail "Failed"
+ echo "OK"
# Have the agent restart the child while its exit code is != 1.
+ echo -n "agentd: restart child while its exit code != 1..."
echo "0" > "${COUNTER_FILE}"
RESULT=$(shell::check_result "${AGENTD_BIN}" --additional_principals="$(shell::tmp_dir)" --restart_exit_code="!1" bash -c "${SCRIPT}")
shell_test::assert_eq "${RESULT}" "1" "${LINENO}"
[[ $(<"${COUNTER_FILE}") -eq 5 ]] || shell_test::fail "Failed"
+ echo "OK"
shell_test::pass
}
diff --git a/security/agent/test_principal/main.go b/security/agent/test_principal/main.go
new file mode 100644
index 0000000..b8dc549
--- /dev/null
+++ b/security/agent/test_principal/main.go
@@ -0,0 +1,112 @@
+package main
+
+import (
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "fmt"
+ "os"
+ "reflect"
+ "runtime"
+
+ _ "v.io/core/veyron/profiles"
+ "v.io/core/veyron2"
+ "v.io/core/veyron2/security"
+)
+
+func newKey() security.PublicKey {
+ k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+ return security.NewECDSAPublicKey(&k.PublicKey)
+}
+
+func main() {
+ ctx, shutdown := veyron2.Init()
+ defer shutdown()
+ p := veyron2.GetPrincipal(ctx)
+
+ var errors []string
+ defer func() {
+ if len(errors) == 0 {
+ return
+ }
+ // Print out all errors and exit with failure.
+ for _, e := range errors {
+ fmt.Fprintln(os.Stderr, e)
+ }
+ os.Exit(1)
+ }()
+ errorf := func(format string, args ...interface{}) {
+ _, file, line, _ := runtime.Caller(1)
+ errors = append(errors, fmt.Sprintf("%v:%d: %v", file, line, fmt.Sprintf(format, args...)))
+ }
+
+ // BlessSelf
+ b, err := p.BlessSelf("batman")
+ if err != nil {
+ errorf("BlessSelf: %v", err)
+ }
+ // Bless
+ if _, err := p.Bless(newKey(), b, "delegate", security.UnconstrainedUse()); err != nil {
+ errorf("Bless: %v", err)
+ }
+ // Sign & PublicKey
+ signature, err := p.Sign([]byte("bugs bunny"))
+ if err != nil {
+ errorf("Sign: %v", err)
+ }
+ if !signature.Verify(p.PublicKey(), []byte("bugs bunny")) {
+ errorf("signature.Verify: %v", err)
+ }
+ // MintDischarge
+ cav, err := security.MethodCaveat("method")
+ if err != nil {
+ errorf("security.MethodCaveat: %v", err)
+ }
+ tpcav, err := security.NewPublicKeyCaveat(p.PublicKey(), "location", security.ThirdPartyRequirements{}, cav)
+ if err != nil {
+ errorf("security.NewPublicKeyCaveat: %v", err)
+ }
+ if _, err := p.MintDischarge(tpcav, cav); err != nil {
+ errorf("MintDischarge: %v", err)
+ }
+ // BlessingRoots
+ if err := p.Roots().Recognized(p.PublicKey(), "batman"); err == nil {
+ errorf("Roots().Recognized returned nil")
+ }
+ if err := p.AddToRoots(b); err != nil {
+ errorf("AddToRoots: %v", err)
+ }
+ if err := p.Roots().Recognized(p.PublicKey(), "batman"); err != nil {
+ errorf("Roots().Recognized: %v", err)
+ }
+ // BlessingStore: Defaults
+ if err := p.BlessingStore().SetDefault(nil); err != nil {
+ errorf("BlessingStore().SetDefault: %v", err)
+ }
+ if def := p.BlessingStore().Default(); def != nil {
+ errorf("BlessingStore().Default returned %v, want nil", def)
+ }
+ if err := p.BlessingStore().SetDefault(b); err != nil {
+ errorf("BlessingStore().SetDefault: %v", err)
+ }
+ if def := p.BlessingStore().Default(); !reflect.DeepEqual(def, b) {
+ errorf("BlessingStore().Default returned [%v], want [%v]", def, b)
+ }
+ // BlessingStore: Set & ForPeer
+ // First, clear out the self-generated default of the blessing store.
+ if _, err := p.BlessingStore().Set(nil, security.AllPrincipals); err != nil {
+ errorf("BlessingStore().Set(nil, %q): %v", security.AllPrincipals, err)
+ }
+ if forpeer := p.BlessingStore().ForPeer("superman/friend"); forpeer != nil {
+ errorf("BlessingStore().ForPeer unexpectedly returned %v", forpeer)
+ }
+ if old, err := p.BlessingStore().Set(b, "superman/..."); old != nil || err != nil {
+ errorf("BlessingStore().Set returned (%v, %v)", old, err)
+ }
+ if forpeer := p.BlessingStore().ForPeer("superman/friend"); !reflect.DeepEqual(forpeer, b) {
+ errorf("BlessingStore().ForPeer returned %v and not %v", forpeer, b)
+ }
+}
diff --git a/security/blessingstore.go b/security/blessingstore.go
index 46636d4..74ed21c 100644
--- a/security/blessingstore.go
+++ b/security/blessingstore.go
@@ -116,7 +116,7 @@
func (bs *blessingStore) SetDefault(blessings security.Blessings) error {
bs.mu.Lock()
defer bs.mu.Unlock()
- if !reflect.DeepEqual(blessings.PublicKey(), bs.publicKey) {
+ if blessings != nil && !reflect.DeepEqual(blessings.PublicKey(), bs.publicKey) {
return errStoreAddMismatch
}
oldDefault := bs.state.Default
diff --git a/security/blessingstore_test.go b/security/blessingstore_test.go
index a3e59d6..d328da4 100644
--- a/security/blessingstore_test.go
+++ b/security/blessingstore_test.go
@@ -51,6 +51,13 @@
if got := s.Default(); !reflect.DeepEqual(got, currentDefault) {
return fmt.Errorf("Default(): got: %v, want: %v", got, currentDefault)
}
+ // SetDefault(nil)
+ if err := s.SetDefault(nil); err != nil {
+ return fmt.Errorf("SetDefault(nil): %v", err)
+ }
+ if got := s.Default(); got != nil {
+ return fmt.Errorf("Default returned %v, want nil", got)
+ }
if err := s.SetDefault(t.def); err != nil {
return fmt.Errorf("SetDefault(%v): %v", t.def, err)
}