security: UUID-based caveat validator registration (Step 1/3).
The current Caveat scheme uses the package path of the CaveatValidator
implementation to associate a caveat with its validation function.
Relying on such type information is brittle because any renaming breaks
caveat validation.
Instead, we (toddw@, bprosnitz@, ataly@ and ashankar@) decided to use
explicit unique ids to associate caveats with their validation
functions.
This commit implements the registration mechanism for associating caveat
validation functions with caveats.
As a bonus, this scheme also improves caveat validation performance
by a factor of 3x, as the VOM-decoding happens into concrete
types (instead of into an interface). Benchmarks added in this CL
on my laptop show:
BenchmarkValidateCaveat 200000 13337 ns/op
while a comparable benchmark on the older style caveat parsing and
validation showed ~45K ns/op.
This is the first (& biggest) step of a 3 step process to be
completed in the next week:
(1) This change: Introducing a registry that associates
caveats identified by UUIDs with validation functions.
(2) Once all binaries have been rebuilt & restarted with this
change, stop populating the ValidatorVOM field and
rebuild & restart services again
(3) Once all blessings have been refreshed (so that the
ValidatorVOM field is not populated), drop the field
altogether.
MultiPart: 1/2
Change-Id: I590414110d71cb4c70d9069d01161df15b7ebd49
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 3e85647..da0d385 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -69,16 +69,6 @@
c.Unlock()
}
-type fakeTimeCaveat int
-
-func (c fakeTimeCaveat) Validate(security.Context) error {
- now := clock.Now()
- if now > int(c) {
- return fmt.Errorf("fakeTimeCaveat expired: now=%d > then=%d", now, c)
- }
- return nil
-}
-
// We need a special way to create contexts for tests. We
// can't create a real runtime in the runtime implementation
// so we use a fake one that panics if used. The runtime
@@ -195,15 +185,26 @@
type dischargeServer struct{}
func (*dischargeServer) Discharge(ctx ipc.ServerCall, cav vdl.AnyRep, _ security.DischargeImpetus) (vdl.AnyRep, error) {
- c, ok := cav.(security.ThirdPartyCaveat)
- if !ok {
- return nil, fmt.Errorf("discharger: unknown caveat(%T)", cav)
+ // TODO(ashankar): During refactoring, this "if" statement must remain.
+ // After security.Caveat.ValidatorVOM is removed, the "else" part can go away.
+ var tpc security.ThirdPartyCaveat
+ if c, ok := cav.(security.Caveat); ok {
+ tpc = c.ThirdPartyDetails()
+ } else {
+ tpc, _ = cav.(security.ThirdPartyCaveat)
}
- if err := c.Dischargeable(ctx); err != nil {
- return nil, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", c, err)
+ if tpc == nil {
+ return nil, fmt.Errorf("discharger: %T does not represent a third-party caveat", cav)
+ }
+ if err := tpc.Dischargeable(ctx); err != nil {
+ return nil, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", cav, err)
}
// Add a fakeTimeCaveat to be able to control discharge expiration via 'clock'.
- return ctx.LocalPrincipal().MintDischarge(c, newCaveat(fakeTimeCaveat(clock.Now())))
+ expiry, err := security.NewCaveat(fakeTimeCaveat, clock.Now())
+ if err != nil {
+ return nil, fmt.Errorf("failed to create an expiration on the discharge: %v", err)
+ }
+ return ctx.LocalPrincipal().MintDischarge(cav, expiry)
}
func startServer(t *testing.T, principal security.Principal, sm stream.Manager, ns naming.Namespace, name string, disp ipc.Dispatcher, opts ...ipc.ServerOpt) (naming.Endpoint, ipc.Server) {
@@ -751,7 +752,7 @@
if err != nil {
panic(err)
}
- return newCaveat(tpc)
+ return tpc
}
// dischargeTestServer implements the discharge service. Always fails to
@@ -784,14 +785,10 @@
mkClient = func(req security.ThirdPartyRequirements) vc.LocalPrincipal {
// Setup the client so that it shares a blessing with a third-party caveat with the server.
- tpc, err := security.NewPublicKeyCaveat(pdischarger.PublicKey(), "mountpoint/discharger", req, security.UnconstrainedUse())
+ cav, err := security.NewPublicKeyCaveat(pdischarger.PublicKey(), "mountpoint/discharger", req, security.UnconstrainedUse())
if err != nil {
t.Fatalf("Failed to create ThirdPartyCaveat(%+v): %v", req, err)
}
- cav, err := security.NewCaveat(tpc)
- if err != nil {
- t.Fatal(err)
- }
b, err := pclient.BlessSelf("client_for_server", cav)
if err != nil {
t.Fatalf("BlessSelf failed: %v", err)
@@ -1632,7 +1629,7 @@
if err != nil {
t.Error(err)
}
- dc.PrepareDischarges(testContext(), []security.ThirdPartyCaveat{tpcav2}, security.DischargeImpetus{})
+ dc.PrepareDischarges(testContext(), []security.ThirdPartyCaveat{tpcav2.ThirdPartyDetails()}, security.DischargeImpetus{})
// Ensure that discharger1 was not called and discharger2 was called.
if discharger1.called {
@@ -1745,11 +1742,18 @@
clientB.Close()
}
-func init() {
- vdl.Register(fakeTimeCaveat(0))
+var fakeTimeCaveat = security.CaveatDescriptor{
+ Id: uniqueid.Id{0x18, 0xba, 0x6f, 0x84, 0xd5, 0xec, 0xdb, 0x9b, 0xf2, 0x32, 0x19, 0x5b, 0x53, 0x92, 0x80, 0x0},
+ ParamType: vdl.TypeOf(int64(0)),
}
func TestMain(m *testing.M) {
testutil.Init()
+ security.RegisterCaveatValidator(fakeTimeCaveat, func(_ security.Context, t int64) error {
+ if now := clock.Now(); now > int(t) {
+ return fmt.Errorf("fakeTimeCaveat expired: now=%d > then=%d", now, t)
+ }
+ return nil
+ })
os.Exit(m.Run())
}
diff --git a/runtimes/google/ipc/stream/vc/vc_test.go b/runtimes/google/ipc/stream/vc/vc_test.go
index 05f4edd..81c1ece 100644
--- a/runtimes/google/ipc/stream/vc/vc_test.go
+++ b/runtimes/google/ipc/stream/vc/vc_test.go
@@ -180,13 +180,6 @@
var _ vc.DischargeClient = (mockDischargeClient)(nil)
func TestHandshakeWithDischargesTLS(t *testing.T) {
- newCaveat := func(validator security.CaveatValidator) security.Caveat {
- cav, err := security.NewCaveat(validator)
- if err != nil {
- t.Fatal(err)
- }
- return cav
- }
var (
discharger = tsecurity.NewPrincipal("discharger")
client = tsecurity.NewPrincipal()
@@ -204,10 +197,10 @@
// Setup 'client' and 'server' so that they use a blessing from 'root' with a third-party caveat
// during VC handshake.
- if err := root.Bless(client, "client", newCaveat(tpcav)); err != nil {
+ if err := root.Bless(client, "client", tpcav); err != nil {
t.Fatal(err)
}
- if err := root.Bless(server, "server", newCaveat(tpcav)); err != nil {
+ if err := root.Bless(server, "server", tpcav); err != nil {
t.Fatal(err)
}
diff --git a/runtimes/google/ipc/testutil_test.go b/runtimes/google/ipc/testutil_test.go
index 01f9444..7eb79eb 100644
--- a/runtimes/google/ipc/testutil_test.go
+++ b/runtimes/google/ipc/testutil_test.go
@@ -50,14 +50,6 @@
}
}
-func newCaveat(v security.CaveatValidator) security.Caveat {
- cav, err := security.NewCaveat(v)
- if err != nil {
- panic(err)
- }
- return cav
-}
-
func mkCaveat(cav security.Caveat, err error) security.Caveat {
if err != nil {
panic(err)
diff --git a/runtimes/google/rt/ipc_test.go b/runtimes/google/rt/ipc_test.go
index 3d72538..95a95c1 100644
--- a/runtimes/google/rt/ipc_test.go
+++ b/runtimes/google/rt/ipc_test.go
@@ -59,14 +59,6 @@
return ret
}
-func newCaveat(v security.CaveatValidator) security.Caveat {
- cav, err := security.NewCaveat(v)
- if err != nil {
- panic(err)
- }
- return cav
-}
-
func mkCaveat(cav security.Caveat, err error) security.Caveat {
if err != nil {
panic(err)
@@ -89,7 +81,7 @@
if err != nil {
panic(err)
}
- return newCaveat(tpc)
+ return tpc
}
func startServer(ctx *context.T, s interface{}) (ipc.Server, string, error) {
diff --git a/security/agent/agent_test.go b/security/agent/agent_test.go
index a6593f6..0c9005e 100644
--- a/security/agent/agent_test.go
+++ b/security/agent/agent_test.go
@@ -167,7 +167,7 @@
return
}
-func (p *mockPrincipal) MintDischarge(security.ThirdPartyCaveat, security.Caveat, ...security.Caveat) (security.Discharge, error) {
+func (p *mockPrincipal) MintDischarge(interface{}, security.Caveat, ...security.Caveat) (security.Discharge, error) {
defer p.reset()
d, _ := p.NextResult.(security.Discharge)
return d, p.NextError
@@ -311,7 +311,7 @@
return b
}
-func newThirdPartyCaveatAndDischarge(t *testing.T) (security.ThirdPartyCaveat, security.Discharge) {
+func newThirdPartyCaveatAndDischarge(t *testing.T) (security.Caveat, security.Discharge) {
p := newPrincipal(t)
c, err := security.NewPublicKeyCaveat(p.PublicKey(), "location", security.ThirdPartyRequirements{}, newCaveat(security.MethodCaveat("method")))
if err != nil {
diff --git a/security/agent/client.go b/security/agent/client.go
index 77c6dde..99854c5 100644
--- a/security/agent/client.go
+++ b/security/agent/client.go
@@ -120,10 +120,9 @@
return
}
-func (c *client) MintDischarge(tp security.ThirdPartyCaveat, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Discharge, error) {
+func (c *client) MintDischarge(tp interface{}, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Discharge, error) {
var discharge security.Discharge
- err := c.caller.call("MintDischarge", results(&discharge), vdl.AnyRep(tp), caveat, additionalCaveats)
- if err != nil {
+ if err := c.caller.call("MintDischarge", results(&discharge), vdl.AnyRep(tp), caveat, additionalCaveats); err != nil {
return nil, err
}
return discharge, nil
diff --git a/security/agent/server/server.go b/security/agent/server/server.go
index 7175a57..a274c53 100644
--- a/security/agent/server/server.go
+++ b/security/agent/server/server.go
@@ -7,7 +7,6 @@
"crypto/sha512"
"crypto/x509"
"encoding/base64"
- "fmt"
"io"
"net"
"os"
@@ -254,11 +253,7 @@
}
func (a agentd) MintDischarge(_ ipc.ServerContext, tp vdl.AnyRep, caveat security.Caveat, additionalCaveats []security.Caveat) (vdl.AnyRep, error) {
- tpCaveat, ok := tp.(security.ThirdPartyCaveat)
- if !ok {
- return nil, fmt.Errorf("provided caveat of type %T does not implement security.ThirdPartyCaveat", tp)
- }
- return a.principal.MintDischarge(tpCaveat, caveat, additionalCaveats...)
+ return a.principal.MintDischarge(tp, caveat, additionalCaveats...)
}
func (a keymgr) newKey(in_memory bool) (id []byte, p security.Principal, err error) {
diff --git a/security/audit/principal.go b/security/audit/principal.go
index b7f9347..879d8f6 100644
--- a/security/audit/principal.go
+++ b/security/audit/principal.go
@@ -46,7 +46,7 @@
return sig, nil
}
-func (p *auditingPrincipal) MintDischarge(tp security.ThirdPartyCaveat, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Discharge, error) {
+func (p *auditingPrincipal) MintDischarge(tp interface{}, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Discharge, error) {
d, err := p.principal.MintDischarge(tp, caveat, additionalCaveats...)
// No need to log the discharge
if err = p.audit(err, "MintDischarge", addCaveats(args{tp, caveat}, additionalCaveats...), nil); err != nil {
diff --git a/security/audit/principal_test.go b/security/audit/principal_test.go
index 4b0d977..f080af7 100644
--- a/security/audit/principal_test.go
+++ b/security/audit/principal_test.go
@@ -156,7 +156,7 @@
return
}
-func (p *mockPrincipal) MintDischarge(security.ThirdPartyCaveat, security.Caveat, ...security.Caveat) (security.Discharge, error) {
+func (p *mockPrincipal) MintDischarge(interface{}, security.Caveat, ...security.Caveat) (security.Discharge, error) {
defer p.reset()
d, _ := p.NextResult.(security.Discharge)
return d, p.NextError
@@ -249,7 +249,7 @@
return b
}
-func newThirdPartyCaveatAndDischarge(t *testing.T) (security.ThirdPartyCaveat, security.Discharge) {
+func newThirdPartyCaveatAndDischarge(t *testing.T) (security.Caveat, security.Discharge) {
p := newPrincipal(t)
c, err := security.NewPublicKeyCaveat(p.PublicKey(), "location", security.ThirdPartyRequirements{}, newCaveat(security.MethodCaveat("method")))
if err != nil {
diff --git a/services/identity/auditor/blessing_auditor_test.go b/services/identity/auditor/blessing_auditor_test.go
index 5b1f50b..ab0dadc 100644
--- a/services/identity/auditor/blessing_auditor_test.go
+++ b/services/identity/auditor/blessing_auditor_test.go
@@ -43,8 +43,8 @@
{
Extension: "special/guests/foo@bar.com/caveatAndRevocation",
Email: "foo@bar.com",
- Caveats: []security.Caveat{expiryCaveat, newCaveat(security.NewCaveat(revocationCaveat))},
- RevocationCaveatID: revocationCaveat.ID(),
+ Caveats: []security.Caveat{expiryCaveat, revocationCaveat},
+ RevocationCaveatID: revocationCaveat.ThirdPartyDetails().ID(),
Blessings: newBlessing(t, p, "test/foo@bar.com/caveatAndRevocation"),
},
}
@@ -86,7 +86,7 @@
}
}
-func newThirdPartyCaveat(t *testing.T, p security.Principal) security.ThirdPartyCaveat {
+func newThirdPartyCaveat(t *testing.T, p security.Principal) security.Caveat {
tp, err := security.NewPublicKeyCaveat(p.PublicKey(), "location", security.ThirdPartyRequirements{}, newCaveat(security.MethodCaveat("method")))
if err != nil {
t.Fatal(err)
diff --git a/services/identity/revocation/caveat.vdl b/services/identity/revocation/caveat.vdl
new file mode 100644
index 0000000..673e29f
--- /dev/null
+++ b/services/identity/revocation/caveat.vdl
@@ -0,0 +1,17 @@
+package revocation
+
+import (
+ "v.io/core/veyron2/uniqueid"
+ "v.io/core/veyron2/security"
+)
+
+// NotRevokedCaveat is used to implement revocation.
+// It validates iff the parameter is not included in a list of blacklisted
+// values.
+//
+// The third-party discharging service checks this revocation caveat against a
+// database of blacklisted (revoked) keys before issuing a discharge.
+const NotRevokedCaveat = security.CaveatDescriptor{
+ Id: uniqueid.Id{0x4b, 0x46, 0x5c, 0x56, 0x37, 0x79, 0xd1, 0x3b, 0x7b, 0xa3, 0xa7, 0xd6, 0xa5, 0x34, 0x80, 0x0},
+ ParamType: typeobject([]byte),
+}
diff --git a/services/identity/revocation/caveat.vdl.go b/services/identity/revocation/caveat.vdl.go
new file mode 100644
index 0000000..2990f95
--- /dev/null
+++ b/services/identity/revocation/caveat.vdl.go
@@ -0,0 +1,41 @@
+// This file was auto-generated by the veyron vdl tool.
+// Source: caveat.vdl
+
+package revocation
+
+import (
+ "v.io/core/veyron2/security"
+
+ "v.io/core/veyron2/uniqueid"
+
+ // The non-user imports are prefixed with "__" to prevent collisions.
+ __vdl "v.io/core/veyron2/vdl"
+)
+
+// NotRevokedCaveat is used to implement revocation.
+// It validates iff the parameter is not included in a list of blacklisted
+// values.
+//
+// The third-party discharging service checks this revocation caveat against a
+// database of blacklisted (revoked) keys before issuing a discharge.
+var NotRevokedCaveat = security.CaveatDescriptor{
+ Id: uniqueid.Id{
+ 75,
+ 70,
+ 92,
+ 86,
+ 55,
+ 121,
+ 209,
+ 59,
+ 123,
+ 163,
+ 167,
+ 214,
+ 165,
+ 52,
+ 128,
+ 0,
+ },
+ ParamType: __vdl.TypeOf([]byte("")),
+}
diff --git a/services/identity/revocation/revocation_manager.go b/services/identity/revocation/revocation_manager.go
index 235b88f..ed685ff 100644
--- a/services/identity/revocation/revocation_manager.go
+++ b/services/identity/revocation/revocation_manager.go
@@ -10,6 +10,7 @@
"v.io/core/veyron2/security"
"v.io/core/veyron2/vdl"
+ "v.io/core/veyron2/vom"
)
// RevocationManager persists information for revocation caveats to provided discharges and allow for future revocations.
@@ -50,18 +51,22 @@
if _, err := rand.Read(revocation[:]); err != nil {
return empty, err
}
- restriction, err := security.NewCaveat(revocationCaveat(revocation))
+ restriction, err := security.NewCaveat(NotRevokedCaveat, revocation[:])
if err != nil {
return empty, err
}
+ // TODO(ashankar): Remove when removing ValidatorVOM
+ if restriction.ValidatorVOM, err = vom.Encode(revocationCaveat(revocation)); err != nil {
+ return empty, err
+ }
cav, err := security.NewPublicKeyCaveat(discharger, dischargerLocation, security.ThirdPartyRequirements{}, restriction)
if err != nil {
return empty, err
}
- if err = revocationDB.InsertCaveat(cav.ID(), revocation[:]); err != nil {
+ if err = revocationDB.InsertCaveat(cav.ThirdPartyDetails().ID(), revocation[:]); err != nil {
return empty, err
}
- return security.NewCaveat(cav)
+ return cav, nil
}
// Revoke disables discharges from being issued for the provided third-party caveat.
@@ -79,22 +84,29 @@
return timestamp
}
-type revocationCaveat [16]byte
-
-func (cav revocationCaveat) Validate(security.Context) error {
+func isRevoked(_ security.Context, key []byte) error {
revocationLock.RLock()
if revocationDB == nil {
revocationLock.RUnlock()
return fmt.Errorf("missing call to NewRevocationManager")
}
revocationLock.RUnlock()
- revoked, err := revocationDB.IsRevoked(cav[:])
+ revoked, err := revocationDB.IsRevoked(key)
if revoked {
return fmt.Errorf("revoked")
}
return err
}
+// TODO(ashankar): Remove when removing security.Caveat.ValidatorVOM.
+type revocationCaveat [16]byte
+
+func (cav revocationCaveat) Validate(ctx security.Context) error {
+ return isRevoked(ctx, cav[:])
+}
+
func init() {
+ // TODO(ashankar): Remove when removing security.Caveat.ValidatorVOM
vdl.Register(revocationCaveat{})
+ security.RegisterCaveatValidator(NotRevokedCaveat, isRevoked)
}
diff --git a/services/security/discharger.vdl b/services/security/discharger.vdl
index d43ed74..c1747cc 100644
--- a/services/security/discharger.vdl
+++ b/services/security/discharger.vdl
@@ -10,7 +10,7 @@
//
// Caveat and Discharge are of type ThirdPartyCaveat and Discharge
// respectively. (not enforced here because vdl does not know these types)
- // TODO(ataly,ashankar): Figure out a VDL representation for ThirdPartyCaveat
- // and Discharge and use those here?
+ // TODO(ataly,ashankar): The type of Caveat should become security.Caveat and
+ // we have to figure out an alternative to any for the return Discharge.
Discharge(Caveat any, Impetus security.DischargeImpetus) (Discharge any | error)
}
diff --git a/services/security/discharger.vdl.go b/services/security/discharger.vdl.go
index 771c5f1..f086448 100644
--- a/services/security/discharger.vdl.go
+++ b/services/security/discharger.vdl.go
@@ -24,8 +24,8 @@
//
// Caveat and Discharge are of type ThirdPartyCaveat and Discharge
// respectively. (not enforced here because vdl does not know these types)
- // TODO(ataly,ashankar): Figure out a VDL representation for ThirdPartyCaveat
- // and Discharge and use those here?
+ // TODO(ataly,ashankar): The type of Caveat should become security.Caveat and
+ // we have to figure out an alternative to any for the return Discharge.
Discharge(ctx *__context.T, Caveat __vdl.AnyRep, Impetus security.DischargeImpetus, opts ...__ipc.CallOpt) (Discharge __vdl.AnyRep, err error)
}
@@ -80,8 +80,8 @@
//
// Caveat and Discharge are of type ThirdPartyCaveat and Discharge
// respectively. (not enforced here because vdl does not know these types)
- // TODO(ataly,ashankar): Figure out a VDL representation for ThirdPartyCaveat
- // and Discharge and use those here?
+ // TODO(ataly,ashankar): The type of Caveat should become security.Caveat and
+ // we have to figure out an alternative to any for the return Discharge.
Discharge(ctx __ipc.ServerContext, Caveat __vdl.AnyRep, Impetus security.DischargeImpetus) (Discharge __vdl.AnyRep, err error)
}
@@ -143,7 +143,7 @@
Methods: []__ipc.MethodDesc{
{
Name: "Discharge",
- Doc: "// Discharge is called by a principal that holds a blessing with a third\n// party caveat and seeks to get a discharge that proves the fulfillment of\n// this caveat.\n//\n// Caveat and Discharge are of type ThirdPartyCaveat and Discharge\n// respectively. (not enforced here because vdl does not know these types)\n// TODO(ataly,ashankar): Figure out a VDL representation for ThirdPartyCaveat\n// and Discharge and use those here?",
+ Doc: "// Discharge is called by a principal that holds a blessing with a third\n// party caveat and seeks to get a discharge that proves the fulfillment of\n// this caveat.\n//\n// Caveat and Discharge are of type ThirdPartyCaveat and Discharge\n// respectively. (not enforced here because vdl does not know these types)\n// TODO(ataly,ashankar): The type of Caveat should become security.Caveat and\n// we have to figure out an alternative to any for the return Discharge.",
InArgs: []__ipc.ArgDesc{
{"Caveat", ``}, // __vdl.AnyRep
{"Impetus", ``}, // security.DischargeImpetus