"veyron/services/wsprd/wspr": Update HTTP server to new security model
Update the WSPR HTTP server to use the new security model whenever
possible for adding principals/blessings for accounts.
Change-Id: I72f7de46a8a171dca6d593469268d74e7323e252
diff --git a/services/wsprd/wspr/wspr.go b/services/wsprd/wspr/wspr.go
index b75cc09..2850e7b 100644
--- a/services/wsprd/wspr/wspr.go
+++ b/services/wsprd/wspr/wspr.go
@@ -27,13 +27,15 @@
"time"
"veyron.io/veyron/veyron2"
+ "veyron.io/veyron/veyron2/context"
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/rt"
"veyron.io/veyron/veyron2/security"
+ "veyron.io/veyron/veyron2/vdl/vdlutil"
"veyron.io/veyron/veyron2/vlog"
- veyron_identity "veyron.io/veyron/veyron/services/identity"
"veyron.io/wspr/veyron/services/wsprd/identity"
+ "veyron.io/wspr/veyron/services/wsprd/principal"
)
const (
@@ -41,21 +43,51 @@
pongTimeout = pingInterval + 10*time.Second // maximum wait for pong.
)
+type blesserService interface {
+ BlessUsingAccessToken(ctx context.T, token string, opts ...ipc.CallOpt) (blessingObj vdlutil.Any, blessings []string, err error)
+}
+
+type bs struct {
+ client ipc.Client
+ name string
+}
+
+func (s *bs) BlessUsingAccessToken(ctx context.T, token string, opts ...ipc.CallOpt) (blessingObj vdlutil.Any, blessings []string, err error) {
+ var call ipc.Call
+ if call, err = s.client.StartCall(ctx, s.name, "BlessUsingAccessToken", []interface{}{token}, opts...); err != nil {
+ return
+ }
+ var email string
+ if ierr := call.Finish(&blessingObj, &email, &err); ierr != nil {
+ err = ierr
+ }
+ serverBlessings, _ := call.RemoteBlessings()
+ for _, b := range serverBlessings {
+ blessings = append(blessings, b+security.ChainSeparator+email)
+ }
+ return
+}
+
type wsprConfig struct {
MounttableRoot []string
}
type WSPR struct {
- mu sync.Mutex
- tlsCert *tls.Certificate
- rt veyron2.Runtime
- httpPort int // HTTP port for WSPR to serve on. Port rather than address to discourage serving in a way that isn't local.
- logger vlog.Logger
- listenSpec ipc.ListenSpec
- identdEP string
- idManager *identity.IDManager
- blesserService veyron_identity.OAuthBlesser
- pipes map[*http.Request]*pipe
+ mu sync.Mutex
+ tlsCert *tls.Certificate
+ rt veyron2.Runtime
+ httpPort int // HTTP port for WSPR to serve on. Port rather than address to discourage serving in a way that isn't local.
+ logger vlog.Logger
+ listenSpec ipc.ListenSpec
+ identdEP string
+ principalManager *principal.PrincipalManager
+ blesser blesserService
+ pipes map[*http.Request]*pipe
+
+ // TODO(ataly, ashankar): Get rid of the fields below once the old
+ // security model is killed.
+ useOldModel bool
+ idManager *identity.IDManager
}
var logger vlog.Logger
@@ -72,12 +104,8 @@
// Starts the proxy and listens for requests. This method is blocking.
func (ctx WSPR) Run() {
- // Bind to the OAuth Blesser service
- blesserService, err := veyron_identity.BindOAuthBlesser(ctx.identdEP)
- if err != nil {
- log.Fatalf("Failed to bind to identity service at %v: %v", ctx.identdEP, err)
- }
- ctx.blesserService = blesserService
+ // Initialize the Blesser service
+ ctx.blesser = &bs{client: ctx.rt.Client(), name: ctx.identdEP}
// HTTP routes
http.HandleFunc("/debug", ctx.handleDebug)
@@ -120,21 +148,42 @@
log.Fatalf("rt.New failed: %s", err)
}
- // TODO(nlacasse, bjornick) use a serializer that can actually persist.
- idManager, err := identity.NewIDManager(newrt, &identity.InMemorySerializer{})
- if err != nil {
- log.Fatalf("identity.NewIDManager failed: %s", err)
+ wspr := &WSPR{
+ httpPort: httpPort,
+ listenSpec: listenSpec,
+ identdEP: identdEP,
+ rt: newrt,
+ logger: newrt.Logger(),
+ pipes: map[*http.Request]*pipe{},
+ useOldModel: true,
}
- return &WSPR{
- httpPort: httpPort,
- listenSpec: listenSpec,
- identdEP: identdEP,
- rt: newrt,
- logger: newrt.Logger(),
- idManager: idManager,
- pipes: map[*http.Request]*pipe{},
+ for _, o := range opts {
+ if _, ok := o.(veyron2.ForceNewSecurityModel); ok {
+ wspr.useOldModel = false
+ break
+ }
}
+
+ if wspr.useOldModel {
+ if wspr.idManager, err = identity.NewIDManager(newrt, &identity.InMemorySerializer{}); err != nil {
+ log.Fatalf("identity.NewIDManager failed: %s", err)
+ }
+ return wspr
+ }
+
+ // TODO(nlacasse, bjornick) use a serializer that can actually persist.
+ if wspr.principalManager, err = principal.NewPrincipalManager(newrt.Principal(), &principal.InMemorySerializer{}); err != nil {
+ log.Fatalf("principal.NewPrincipalManager failed: %s", err)
+ }
+
+ return wspr
+}
+
+func (ctx WSPR) logAndSendBadReqErr(w http.ResponseWriter, msg string) {
+ ctx.logger.Error(msg)
+ http.Error(w, msg, http.StatusBadRequest)
+ return
}
// HTTP Handlers
@@ -182,12 +231,11 @@
Names []string `json:"names"`
}
-// Handler for creating an account in the identity manager.
-// A valid OAuth2 access token must be supplied in the request body. That
-// access token is exchanged for a blessing from the identd server. A new
-// privateID is then derived from WSPR's privateID and the blessing. That
-// privateID is stored in the identity manager. The name of the new privateID
-// is returned to the client.
+// Handler for creating an account in the principal manager.
+// A valid OAuth2 access token must be supplied in the request body,
+// which is exchanged for blessings from the veyron blessing server.
+// An account based on the blessings is then added to WSPR's principal
+// manager, and the set of blessing strings are returned to the client.
func (ctx WSPR) handleCreateAccount(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
@@ -197,51 +245,48 @@
// Parse request body.
var data createAccountInput
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
- msg := fmt.Sprintf("Error parsing body: %v", err)
- ctx.logger.Error(msg)
- http.Error(w, msg, http.StatusBadRequest)
+ ctx.logAndSendBadReqErr(w, fmt.Sprintf("Error parsing body: %v", err))
}
// Get a blessing for the access token from identity server.
rctx, cancel := ctx.rt.NewContext().WithTimeout(time.Minute)
defer cancel()
- blessingAny, _, err := ctx.blesserService.BlessUsingAccessToken(rctx, data.AccessToken)
+ blessingsAny, blessings, err := ctx.blesser.BlessUsingAccessToken(rctx, data.AccessToken)
if err != nil {
- msg := fmt.Sprintf("Error getting blessing for access token: %v", err)
- ctx.logger.Error(msg)
- http.Error(w, msg, http.StatusBadRequest)
- return
- }
- blessing := blessingAny.(security.PublicID)
-
- // Derive a new identity from the runtime's identity and the blessing.
- identity, err := ctx.rt.Identity().Derive(blessing)
- if err != nil {
- msg := fmt.Sprintf("Error deriving identity: %v", err)
- ctx.logger.Error(msg)
- http.Error(w, msg, http.StatusBadRequest)
+ ctx.logAndSendBadReqErr(w, fmt.Sprintf("Error getting blessing for access token: %v", err))
return
}
- for _, name := range blessing.Names() {
- // Store identity in identity manager.
- if err := ctx.idManager.AddAccount(name, identity); err != nil {
- msg := fmt.Sprintf("Error storing identity: %v", err)
- ctx.logger.Error(msg)
- http.Error(w, msg, http.StatusBadRequest)
+ // Shortcut for old security model.
+ if ctx.useOldModel {
+ ctx.handleCreateAccountOldModel(blessingsAny, w)
+ return
+ }
+
+ accountBlessings, err := security.NewBlessings(blessingsAny.(security.WireBlessings))
+ if err != nil {
+ ctx.logAndSendBadReqErr(w, fmt.Sprintf("Error creating blessings from wire data: %v", err))
+ return
+ }
+ // Add accountBlessings to principalManager under each of the
+ // returned blessing strings.
+ // TODO(ataly, ashankar): Adding the same account under different
+ // different names seems a little weird. Figure out a cleaner way
+ // of adding the account.
+ for _, b := range blessings {
+ if err := ctx.principalManager.AddAccount(b, accountBlessings); err != nil {
+ ctx.logAndSendBadReqErr(w, fmt.Sprintf("Error adding account: %v", err))
return
}
}
- // Return the names to the client.
+ // Return blessings to the client.
out := createAccountOutput{
- Names: blessing.Names(),
+ Names: blessings,
}
outJson, err := json.Marshal(out)
if err != nil {
- msg := fmt.Sprintf("Error mashalling names: %v", err)
- ctx.logger.Error(msg)
- http.Error(w, msg, http.StatusInternalServerError)
+ ctx.logAndSendBadReqErr(w, fmt.Sprintf("Error mashalling names: %v", err))
return
}
@@ -256,7 +301,7 @@
Origin string `json:"origin"`
}
-// Handler for associating an existing privateID with an origin.
+// Handler for associating an existing principal with an origin.
func (ctx WSPR) handleAssocAccount(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
@@ -269,8 +314,63 @@
http.Error(w, fmt.Sprintf("Error parsing body: %v", err), http.StatusBadRequest)
}
+ // Shortcup for old security model.
+ if ctx.useOldModel {
+ ctx.handleAssocAccountOldModel(data, w)
+ return
+ }
+
// Store the origin.
// TODO(nlacasse, bjornick): determine what the caveats should be.
+ if err := ctx.principalManager.AddOrigin(data.Origin, data.Name, nil); err != nil {
+ http.Error(w, fmt.Sprintf("Error associating account: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ // Success.
+ fmt.Fprintf(w, "")
+}
+
+// TODO(ataly, ashankar): Remove this method once the old security model is killed.
+func (ctx WSPR) handleCreateAccountOldModel(blessingsAny vdlutil.Any, w http.ResponseWriter) {
+ blessing, ok := blessingsAny.(security.PublicID)
+ if ok {
+ ctx.logAndSendBadReqErr(w, "Error creating PublicID from wire data")
+ return
+ }
+
+ // Derive a new identity from the runtime's identity and the blessing.
+ identity, err := ctx.rt.Identity().Derive(blessing)
+ if err != nil {
+ ctx.logAndSendBadReqErr(w, fmt.Sprintf("Error deriving identity: %v", err))
+ return
+ }
+
+ for _, name := range blessing.Names() {
+ // Store identity in identity manager.
+ if err := ctx.idManager.AddAccount(name, identity); err != nil {
+ ctx.logAndSendBadReqErr(w, fmt.Sprintf("Error storing identity: %v", err))
+ return
+ }
+ }
+
+ // Return the names to the client.
+ out := createAccountOutput{
+ Names: blessing.Names(),
+ }
+ outJson, err := json.Marshal(out)
+ if err != nil {
+ ctx.logAndSendBadReqErr(w, fmt.Sprintf("Error mashalling names: %v", err))
+ return
+ }
+
+ // Success.
+ w.Header().Set("Content-Type", "application/json")
+ fmt.Fprintf(w, string(outJson))
+}
+
+// TODO(ataly, ashankar): Remove this method once the old security model is killed.
+func (ctx WSPR) handleAssocAccountOldModel(data assocAccountInput, w http.ResponseWriter) {
if err := ctx.idManager.AddOrigin(data.Origin, data.Name, nil); err != nil {
http.Error(w, fmt.Sprintf("Error associating account: %v", err), http.StatusBadRequest)
return
diff --git a/services/wsprd/wspr/wspr_test.go b/services/wsprd/wspr/wspr_test.go
index 9993cc1..4572c61 100644
--- a/services/wsprd/wspr/wspr_test.go
+++ b/services/wsprd/wspr/wspr_test.go
@@ -7,8 +7,8 @@
"net/http"
"net/http/httptest"
"testing"
- "time"
+ "veyron.io/veyron/veyron2"
"veyron.io/veyron/veyron2/context"
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/security"
@@ -17,43 +17,30 @@
"veyron.io/veyron/veyron/profiles"
)
+const topLevelName = "mock-blesser"
+
// BEGIN MOCK BLESSER SERVICE
// TODO(nlacasse): Is there a better way to mock this?!
type mockBlesserService struct {
- id security.PrivateID
+ p security.Principal
count int
}
-func newMockBlesserService(id security.PrivateID) *mockBlesserService {
+func newMockBlesserService(p security.Principal) *mockBlesserService {
return &mockBlesserService{
- id: id,
+ p: p,
count: 0,
}
}
-func (m *mockBlesserService) BlessUsingAccessToken(c context.T, accessToken string, co ...ipc.CallOpt) (vdlutil.Any, string, error) {
+func (m *mockBlesserService) BlessUsingAccessToken(c context.T, accessToken string, co ...ipc.CallOpt) (vdlutil.Any, []string, error) {
m.count = m.count + 1
- name := fmt.Sprintf("mock-blessing-%v", m.count)
- blessing, err := m.id.Bless(m.id.PublicID(), name, 5*time.Minute, nil)
+ name := fmt.Sprintf("%s%s%d", topLevelName, security.ChainSeparator, m.count)
+ blessing, err := m.p.BlessSelf(name)
if err != nil {
- return nil, "", err
+ return nil, nil, err
}
- return blessing, name, nil
-}
-
-// This is never used. Only needed for mock.
-func (m *mockBlesserService) GetMethodTags(c context.T, s string, co ...ipc.CallOpt) ([]interface{}, error) {
- return nil, nil
-}
-
-// This is never used. Only needed for mock.
-func (m *mockBlesserService) Signature(c context.T, co ...ipc.CallOpt) (ipc.ServiceSignature, error) {
- return ipc.ServiceSignature{}, nil
-}
-
-// This is never used. Only needed for mock.
-func (m *mockBlesserService) UnresolveStep(c context.T, co ...ipc.CallOpt) ([]string, error) {
- return []string{}, nil
+ return security.MarshalBlessings(blessing), []string{name}, nil
}
// END MOCK BLESSER SERVICE
@@ -61,10 +48,8 @@
func setup(t *testing.T) (*WSPR, func()) {
spec := *profiles.LocalListenSpec
spec.Proxy = "/mock/proxy"
- wspr := NewWSPR(0, spec, "/mock/identd")
- providerId := wspr.rt.Identity()
-
- wspr.blesserService = newMockBlesserService(providerId)
+ wspr := NewWSPR(0, spec, "/mock/identd", veyron2.ForceNewSecurityModel{})
+ wspr.blesser = newMockBlesserService(wspr.rt.Principal())
return wspr, func() {
wspr.Shutdown()
}
@@ -98,10 +83,9 @@
t.Fatalf("Expected handleCreateAccount to return 200 OK, instead got %v", resp1)
}
- // Verify that idManager has the new account
- topLevelName := wspr.rt.Identity().PublicID().Names()[0]
- expectedAccountName := topLevelName + "/mock-blessing-1"
- gotAccounts := wspr.idManager.AccountsMatching(security.BlessingPattern(expectedAccountName))
+ // Verify that principalManager has the new account
+ expectedAccountName := fmt.Sprintf("%s%s%d", topLevelName, security.ChainSeparator, 1)
+ gotAccounts := wspr.principalManager.AccountsMatching(security.BlessingPattern(expectedAccountName))
if len(gotAccounts) != 1 {
t.Fatalf("Expected to have 1 account with name %v, but got %v: %v", expectedAccountName, len(gotAccounts), gotAccounts)
}
@@ -126,8 +110,8 @@
t.Fatalf("Expected handleCreateAccount to return 200 OK, instead got %v", resp2)
}
- // Verify that idManager has both accounts
- gotAccounts = wspr.idManager.AccountsMatching(security.BlessingPattern(fmt.Sprintf("%s%s%v", topLevelName, security.ChainSeparator, security.AllPrincipals)))
+ // Verify that principalManager has both accounts
+ gotAccounts = wspr.principalManager.AccountsMatching(security.BlessingPattern(fmt.Sprintf("%s%s%v", topLevelName, security.ChainSeparator, security.AllPrincipals)))
if len(gotAccounts) != 2 {
t.Fatalf("Expected to have 2 accounts, but got %v: %v", len(gotAccounts), gotAccounts)
}
@@ -137,15 +121,14 @@
wspr, teardown := setup(t)
defer teardown()
- // First create an accounts.
+ // First create an account.
accountName := "mock-account"
- identityName := "mock-id"
- privateID, err := wspr.rt.NewIdentity(identityName)
+ blessing, err := wspr.rt.Principal().BlessSelf(accountName)
if err != nil {
- t.Fatalf("wspr.rt.NewIdentity(%v) failed: %v", identityName, err)
+ t.Fatalf("wspr.rt.Principal.BlessSelf(%v) failed: %v", accountName, err)
}
- if err := wspr.idManager.AddAccount(accountName, privateID); err != nil {
- t.Fatalf("wspr.idManager.AddAccount(%v, %v) failed; %v", accountName, privateID, err)
+ if err := wspr.principalManager.AddAccount(accountName, blessing); err != nil {
+ t.Fatalf("wspr.principalManager.AddAccount(%v, %v) failed; %v", accountName, blessing, err)
}
// Associate with that account
@@ -175,14 +158,14 @@
t.Fatalf("Expected handleAssocAccount to return 200 OK, instead got %v", resp)
}
- // Verify that idManager has the correct identity for the origin
- gotID, err := wspr.idManager.Identity(origin)
+ // Verify that principalManager has the correct principal for the origin
+ got, err := wspr.principalManager.Principal(origin)
if err != nil {
- t.Fatalf("wspr.idManager.Identity(%v) failed: %v", origin, err)
+ t.Fatalf("wspr.principalManager.Principal(%v) failed: %v", origin, err)
}
- if gotID == nil {
- t.Fatalf("Expected wspr.idManager.Identity(%v) to return an valid identity, but got %v", origin, gotID)
+ if got == nil {
+ t.Fatalf("Expected wspr.principalManager.Principal(%v) to return a valid principal, but got %v", origin, got)
}
}
@@ -218,13 +201,13 @@
t.Fatalf("Expected handleAssocAccount to return 400 error, but got %v", resp)
}
- // Verify that idManager has no identities for the origin
- gotID, err := wspr.idManager.Identity(origin)
+ // Verify that principalManager creates no principal for the origin
+ got, err := wspr.principalManager.Principal(origin)
if err == nil {
- t.Fatalf("Expected wspr.idManager.Identity(%v) to fail, but got: %v", origin, gotID)
+ t.Fatalf("Expected wspr.principalManager.Principal(%v) to fail, but got: %v", origin, got)
}
- if gotID != nil {
- t.Fatalf("Expected wspr.idManager.Identity(%v) not to return an identity, but got %v", origin, gotID)
+ if got != nil {
+ t.Fatalf("Expected wspr.principalManager.Principal(%v) not to return a principal, but got %v", origin, got)
}
}