| // 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. |
| |
| // +build mojo |
| |
| // The evolving story of identification and security in syncbase |
| // running in mojo: |
| // (Last Updated: November 5, 2015). |
| // |
| // SHORT STORY: |
| // The syncbase mojo service gets a blessing by exchanging an OAuth2 token for |
| // a blessing. This allows syncbases to identify each other when synchronizing |
| // data between each other and enables basic security policies like "all my |
| // devices sync all my data with each other". However, different mojo "clients" |
| // (that communicate with the local syncbase via mojo IPC) are not isolated |
| // from each other - so each mojo app has complete access to all the data |
| // written to the local syncbase service (modulo any filesystem isolation via |
| // the --root-dir flag). This is something that we intend to change. |
| // |
| // LONGER VERSION: |
| // A "mojo app" consists of a set of interrelated components, accessed by a |
| // URL. Communication between these components is via mojo IPC. One of those |
| // components is syncbase (this package), which is a store that synchronizes |
| // between devices via Vanadium RPCs. See: |
| // https://docs.google.com/a/google.com/document/d/1TyxPYIhj9VBCtY7eAXu_MEV9y0dtRx7n7UY4jm76Qq4/edit?usp=sharing |
| // |
| // Any process communicating via Vanadium RPCs needs to have an identity |
| // represented as a Blessing |
| // (https://github.com/vanadium/docs/blob/master/concepts/security.md). |
| // |
| // So, the current story is this: |
| // 1 The mojo syncbase app/service fetches an OAuth2 token from the |
| // mojo authentication service |
| // 2 It exchanges this token for a blessing bound to the public |
| // key of the principal associated with this service |
| // (this is essentially a copy of what the mojo principal service |
| // implementation does) |
| // 3 And all RPCs use the blessing obtained above |
| // 4 Other mojo components communicate with their local syncbase via |
| // Mojo IPC (see v.io/x/ref/services/syncbase/server/mojo_call.go) |
| // and DO NOT AUTHENTICATE with it |
| // 5 The resulting security model is that all mojo apps on the same |
| // device have access to whatever is stored in the local syncbase |
| // |
| // Clearly (5) is not ideal. And as this evolves, the plan is likely to be |
| // something involving each "client" of this syncbase mojo service being |
| // identified using the principal service |
| // (https://github.com/domokit/mojo/blob/master/mojo/services/vanadium/security/interfaces/principal.mojom). |
| // But more on that when we get there. |
| |
| package main |
| |
| import ( |
| "encoding/base64" |
| "fmt" |
| "mojo/public/go/application" |
| "mojo/public/go/bindings" |
| "mojo/public/go/system" |
| "mojo/public/interfaces/network/url_request" |
| "mojo/services/authentication/interfaces/authentication" |
| "mojo/services/network/interfaces/network_service" |
| "mojo/services/network/interfaces/url_loader" |
| "net/url" |
| "runtime" |
| |
| "v.io/v23" |
| "v.io/v23/context" |
| "v.io/v23/security" |
| "v.io/v23/vom" |
| seclib "v.io/x/ref/lib/security" |
| ) |
| |
| type selectAccountFailed struct { |
| error |
| } |
| |
| func setBlessings(v23ctx *context.T, appctx application.Context) error { |
| // Get an OAuth2 token from the mojo authentication service. |
| // At the time of this writing, the mojo authentication |
| // service was implemented only for Android, so in absence |
| // of that abort. Which means that the syncbases cannot |
| // communicate with each other, unless --v23.credentials was |
| // supplied as a flag. |
| if runtime.GOOS != "android" { |
| v23ctx.Infof("Using default blessings for non-Android OS (%v)", runtime.GOOS) |
| return nil |
| } |
| // TODO(ashankar,ataly): This is almost a duplicate of what |
| // is in |
| // https://github.com/domokit/mojo/blob/master/services/vanadium/security/principal_service.go |
| // - at some point this needs to be cleared up and this |
| // syncbase should use the principal service? |
| token, err := oauthToken(appctx) |
| if err != nil { |
| if _, ok := err.(selectAccountFailed); ok { |
| // TODO(sadovsky): This behavior is convenient for development, but |
| // probably ought to be disabled in production. |
| v23ctx.Infof("SelectAccount failed; using default blessings") |
| return nil |
| } |
| return err |
| } |
| v23ctx.Infof("Obtained OAuth2 token, will exchange for blessings") |
| p := v23.GetPrincipal(v23ctx) |
| blessings, err := token2blessings(appctx, token, p.PublicKey()) |
| if err != nil { |
| return err |
| } |
| v23ctx.Infof("Obtained blessings %v", blessings) |
| if err := seclib.SetDefaultBlessings(p, blessings); err != nil { |
| return fmt.Errorf("failed to use blessings: %v", err) |
| } |
| return nil |
| } |
| |
| func oauthToken(ctx application.Context) (string, error) { |
| req, ptr := authentication.CreateMessagePipeForAuthenticationService() |
| ctx.ConnectToApplication("mojo:authentication").ConnectToService(&req) |
| proxy := authentication.NewAuthenticationServiceProxy(ptr, bindings.GetAsyncWaiter()) |
| name, errstr, _ := proxy.SelectAccount(true /*return last selected*/) |
| if name == nil || errstr != nil { |
| return "", selectAccountFailed{error: fmt.Errorf("failed to select an account for user: %v", errstr)} |
| } |
| token, errstr, _ := proxy.GetOAuth2Token(*name, []string{"email"}) |
| if token == nil || errstr != nil { |
| return "", fmt.Errorf("failed to obtain an OAuth2 token for %q: %v", *name, errstr) |
| } |
| return *token, nil |
| } |
| |
| func token2blessings(ctx application.Context, token string, key security.PublicKey) (security.Blessings, error) { |
| var ret security.Blessings |
| netReq, netPtr := network_service.CreateMessagePipeForNetworkService() |
| ctx.ConnectToApplication("mojo:network_service").ConnectToService(&netReq) |
| netProxy := network_service.NewNetworkServiceProxy(netPtr, bindings.GetAsyncWaiter()) |
| |
| loaderReq, loaderPtr := url_loader.CreateMessagePipeForUrlLoader() |
| if err := netProxy.CreateUrlLoader(loaderReq); err != nil { |
| return ret, fmt.Errorf("failed to create URL loader: %v", err) |
| } |
| url, err := token2blessingURL(token, key) |
| if err != nil { |
| return ret, err |
| } |
| response, err := url_loader.NewUrlLoaderProxy(loaderPtr, bindings.GetAsyncWaiter()).Start(url_request.UrlRequest{ |
| Url: url, |
| Method: "GET", |
| }) |
| if err != nil || response.Error != nil { |
| var errrep interface{} |
| if err != nil { |
| errrep = err |
| } else { |
| errrep = response.Error |
| } |
| return ret, fmt.Errorf("HTTP request to exchange OAuth token for blessings failed: %v", errrep) |
| } |
| res, b64bytes := (*response.Body).ReadData(system.MOJO_READ_DATA_FLAG_ALL_OR_NONE) |
| if res != system.MOJO_RESULT_OK { |
| return ret, fmt.Errorf("failed to read blessings from the Vanadium identity provider. Result: %v", res) |
| } |
| vombytes, err := base64.URLEncoding.DecodeString(string(b64bytes)) |
| if err != nil { |
| return ret, fmt.Errorf("invalid base64 encoded blessings: %v", err) |
| } |
| if err := vom.Decode(vombytes, &ret); err != nil { |
| return ret, fmt.Errorf("invalid encoded blessings: %v", err) |
| } |
| return ret, nil |
| } |
| |
| func token2blessingURL(token string, key security.PublicKey) (string, error) { |
| base, err := url.Parse("https://dev.v.io/auth/google/bless") |
| if err != nil { |
| return "", fmt.Errorf("failed to parse blessing URL: %v", err) |
| } |
| pub, err := key.MarshalBinary() |
| if err != nil { |
| return "", fmt.Errorf("invalid public key: %v", err) |
| } |
| |
| params := url.Values{} |
| params.Add("public_key", base64.URLEncoding.EncodeToString(pub)) |
| params.Add("token", token) |
| params.Add("output_format", "base64vom") |
| |
| base.RawQuery = params.Encode() |
| return base.String(), nil |
| } |