| // 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. |
| |
| package internal |
| |
| import ( |
| "strings" |
| "time" |
| |
| "v.io/v23" |
| "v.io/v23/context" |
| "v.io/v23/glob" |
| "v.io/v23/rpc" |
| "v.io/v23/security" |
| "v.io/v23/verror" |
| |
| "v.io/x/ref/services/role" |
| ) |
| |
| var ( |
| errNoLocalBlessings = verror.Register("v.io/x/ref/services/role/roled/internal/noLocalBlessings", verror.NoRetry, "{1:}{2:} no local blessings") |
| ) |
| |
| type roleService struct { |
| serverConfig *serverConfig |
| role string |
| roleConfig *Config |
| } |
| |
| func (i *roleService) SeekBlessings(ctx *context.T, call rpc.ServerCall) (security.Blessings, error) { |
| remoteBlessingNames, _ := security.RemoteBlessingNames(ctx, call.Security()) |
| ctx.Infof("%q.SeekBlessings() called by %q", i.role, remoteBlessingNames) |
| |
| members := i.filterNonMembers(remoteBlessingNames) |
| if len(members) == 0 { |
| // The Authorizer should already have caught that. |
| return security.Blessings{}, verror.New(verror.ErrNoAccess, ctx) |
| } |
| |
| extensions := extensions(i.roleConfig, i.role, members) |
| caveats, err := caveats(ctx, i.roleConfig) |
| if err != nil { |
| return security.Blessings{}, err |
| } |
| |
| return createBlessings(ctx, call.Security(), i.roleConfig, v23.GetPrincipal(ctx), extensions, caveats, i.serverConfig.dischargerLocation) |
| } |
| |
| func (i *roleService) GlobChildren__(ctx *context.T, call rpc.GlobChildrenServerCall, m *glob.Element) error { |
| return globChildren(ctx, call, i.serverConfig, m) |
| } |
| |
| // filterNonMembers returns only the blessing names that are authorized members |
| // for the role. |
| func (i *roleService) filterNonMembers(blessingNames []string) []string { |
| var results []string |
| for _, name := range blessingNames { |
| // It is not enough to know if the pattern is matched by the |
| // blessings. We need to know exactly which names matched. |
| // These names will be used later to construct the role |
| // blessings. |
| for _, pattern := range i.roleConfig.Members { |
| if pattern.MatchedBy(name) { |
| results = append(results, name) |
| break |
| } |
| } |
| } |
| return results |
| } |
| |
| func extensions(config *Config, roleStr string, blessingNames []string) []string { |
| // roleStr is the suffix of a veyron object name, but extensions are for |
| // blessings, so do the conversion. |
| roleStr = strings.Replace(roleStr, "/", security.ChainSeparator, -1) |
| if !config.Extend { |
| return []string{roleStr} |
| } |
| var extensions []string |
| for _, b := range blessingNames { |
| b = strings.TrimSuffix(b, security.ChainSeparator+role.RoleSuffix) |
| extensions = append(extensions, roleStr+security.ChainSeparator+b) |
| } |
| return extensions |
| } |
| |
| func caveats(ctx *context.T, config *Config) ([]security.Caveat, error) { |
| var caveats []security.Caveat |
| if config.Expiry != "" { |
| d, err := time.ParseDuration(config.Expiry) |
| if err != nil { |
| return nil, verror.Convert(verror.ErrInternal, ctx, err) |
| } |
| expiry, err := security.NewExpiryCaveat(time.Now().Add(d)) |
| if err != nil { |
| return nil, verror.Convert(verror.ErrInternal, ctx, err) |
| } |
| caveats = append(caveats, expiry) |
| } |
| if len(config.Peers) != 0 { |
| peer, err := security.NewCaveat(security.PeerBlessingsCaveat, config.Peers) |
| if err != nil { |
| return nil, verror.Convert(verror.ErrInternal, ctx, err) |
| } |
| caveats = append(caveats, peer) |
| } |
| return caveats, nil |
| } |
| |
| func createBlessings(ctx *context.T, call security.Call, config *Config, principal security.Principal, extensions []string, caveats []security.Caveat, dischargerLocation string) (security.Blessings, error) { |
| blessWith := call.LocalBlessings() |
| blessWithNames := security.LocalBlessingNames(ctx, call) |
| publicKey := call.RemoteBlessings().PublicKey() |
| if len(blessWithNames) == 0 { |
| return security.Blessings{}, verror.New(errNoLocalBlessings, ctx) |
| } |
| |
| var ret security.Blessings |
| for _, ext := range extensions { |
| cav := caveats |
| if config.Audit { |
| // TODO(rthellend): This third-party caveat will only work with a single |
| // discharger service. We need a way to allow multiple instances of this |
| // service to be interchangeable. |
| |
| fullNames := make([]string, len(blessWithNames)) |
| for i, n := range blessWithNames { |
| fullNames[i] = n + security.ChainSeparator + ext |
| } |
| loggingCaveat, err := security.NewCaveat(LoggingCaveat, fullNames) |
| if err != nil { |
| return security.Blessings{}, verror.Convert(verror.ErrInternal, ctx, err) |
| } |
| thirdParty, err := security.NewPublicKeyCaveat(principal.PublicKey(), dischargerLocation, security.ThirdPartyRequirements{ |
| ReportServer: true, |
| ReportMethod: true, |
| ReportArguments: true, |
| }, loggingCaveat) |
| if err != nil { |
| return security.Blessings{}, verror.Convert(verror.ErrInternal, ctx, err) |
| } |
| cav = append(cav, thirdParty) |
| } |
| if len(cav) == 0 { |
| // TODO(rthellend,ashankar): the use of unconstrained |
| // use is concerning. We should figure out how to get |
| // rid of it. |
| // Some options: |
| // - have the seeker specify a set of caveats in the |
| // request (and forcefully insert a restrictive one |
| // or fail if the role server thinks that they are |
| // too loose or something). |
| // - have a set of caveats in the config of the role. |
| cav = []security.Caveat{security.UnconstrainedUse()} |
| } |
| b, err := principal.Bless(publicKey, blessWith, ext, cav[0], cav[1:]...) |
| if err != nil { |
| return security.Blessings{}, verror.Convert(verror.ErrInternal, ctx, err) |
| } |
| if ret, err = security.UnionOfBlessings(ret, b); err != nil { |
| verror.Convert(verror.ErrInternal, ctx, err) |
| } |
| } |
| return ret, nil |
| } |