blob: 475beb521fc78a8f5f723cb9783796a12a149ab5 [file] [log] [blame]
// 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 (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strings"
"v.io/v23/context"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/verror"
"v.io/x/ref/services/discharger"
"v.io/x/ref/services/role"
"v.io/x/lib/vlog"
)
const requiredSuffix = security.ChainSeparator + role.RoleSuffix
// NewDispatcher returns a dispatcher object for a role service and its
// associated discharger service.
// The configRoot is the top level directory where the role configuration files
// are stored.
// The dischargerLocation is the object name or address of the discharger
// service for the third-party caveats attached to the role blessings returned
// by the role service.
func NewDispatcher(configRoot, dischargerLocation string) rpc.Dispatcher {
return &dispatcher{&serverConfig{configRoot, dischargerLocation}}
}
type serverConfig struct {
root string
dischargerLocation string
}
type dispatcher struct {
config *serverConfig
}
func (d *dispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
if len(suffix) == 0 {
return discharger.DischargerServer(&dischargerImpl{d.config}), security.AllowEveryone(), nil
}
fileName := filepath.Join(d.config.root, filepath.FromSlash(suffix+".conf"))
if !strings.HasPrefix(fileName, d.config.root) {
// Guard against ".." in the suffix that could be used to read
// files outside of the config root.
return nil, nil, verror.New(verror.ErrNoExistOrNoAccess, nil)
}
roleConfig, err := loadExpandedConfig(fileName, nil)
if err != nil && !os.IsNotExist(err) {
// The config file exists, but we failed to read it for some
// reason. This is likely a server configuration error.
vlog.Errorf("loadConfig(%q, %q): %v", d.config.root, suffix, err)
return nil, nil, verror.Convert(verror.ErrInternal, nil, err)
}
obj := &roleService{serverConfig: d.config, role: suffix, roleConfig: roleConfig}
return role.RoleServer(obj), &authorizer{roleConfig}, nil
}
type authorizer struct {
config *Config
}
func (a *authorizer) Authorize(ctx *context.T, call security.Call) error {
if call.Method() == "__Glob" {
// The Glob implementation only shows objects that the caller
// has access to. So this blanket approval is OK.
return nil
}
if a.config == nil {
return verror.New(verror.ErrNoExistOrNoAccess, ctx)
}
remoteBlessingNames, _ := security.RemoteBlessingNames(ctx, call)
if hasAccess(a.config, remoteBlessingNames) {
return nil
}
return verror.New(verror.ErrNoExistOrNoAccess, ctx)
}
func hasAccess(c *Config, blessingNames []string) bool {
for _, pattern := range c.Members {
if pattern.MatchedBy(blessingNames...) {
return true
}
}
return false
}
func loadExpandedConfig(fileName string, seenFiles map[string]struct{}) (*Config, error) {
if seenFiles == nil {
seenFiles = make(map[string]struct{})
}
if _, seen := seenFiles[fileName]; seen {
return nil, nil
}
seenFiles[fileName] = struct{}{}
c, err := loadConfig(fileName)
if err != nil {
return nil, err
}
parentDir := filepath.Dir(fileName)
for _, imp := range c.ImportMembers {
f := filepath.Join(parentDir, filepath.FromSlash(imp+".conf"))
ic, err := loadExpandedConfig(f, seenFiles)
if err != nil {
vlog.Errorf("loadExpandedConfig(%q) failed: %v", f, err)
continue
}
if ic == nil {
continue
}
c.Members = append(c.Members, ic.Members...)
}
c.ImportMembers = nil
dedupMembers(c)
return c, nil
}
func loadConfig(fileName string) (*Config, error) {
contents, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, err
}
var c Config
if err := json.Unmarshal(contents, &c); err != nil {
return nil, err
}
for i, pattern := range c.Members {
if p := string(pattern); !strings.HasSuffix(p, requiredSuffix) {
c.Members[i] = security.BlessingPattern(p + requiredSuffix)
}
}
return &c, nil
}
func dedupMembers(c *Config) {
members := make(map[security.BlessingPattern]struct{})
for _, m := range c.Members {
members[m] = struct{}{}
}
c.Members = []security.BlessingPattern{}
for m := range members {
c.Members = append(c.Members, m)
}
}