blob: 766383a6660931a8604bfaf028ace63abaa38af3 [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 agentlib provides ways to create Principals that are backed by the
// security agent. It implements a client for communicating with an agent
// process holding the private key for a Principal. It also provides a way to
// start an agent for a Principal serialized to disk.
package agentlib
import (
"fmt"
"io"
"sync"
"time"
"v.io/v23/security"
"v.io/v23/verror"
"v.io/x/ref/internal/logger"
"v.io/x/ref/services/agent"
"v.io/x/ref/services/agent/internal/cache"
"v.io/x/ref/services/agent/internal/ipc"
)
const (
pkgPath = "v.io/x/ref/services/agent/agentlib"
defaultConnectTimeout = 10 * time.Second
)
// Errors
var (
errInvalidProtocol = verror.Register(pkgPath+".errInvalidProtocol",
verror.NoRetry, "{1:}{2:} invalid agent protocol {3}")
)
type client struct {
caller caller
key security.PublicKey
// TODO(mattr): At some point we should remove this backward
// compatibility mechanism, once all users are updated.
noCacheTimes bool
}
type caller interface {
call(name string, results []interface{}, args ...interface{}) error
io.Closer
}
type ipcCaller struct {
conn *ipc.IPCConn
flush func()
mu sync.Mutex
}
func (i *ipcCaller) call(name string, results []interface{}, args ...interface{}) error {
return i.conn.Call(name, args, results...)
}
func (i *ipcCaller) Close() error {
i.conn.Close()
return nil
}
func (i *ipcCaller) FlushAllCaches() error {
var flush func()
i.mu.Lock()
flush = i.flush
i.mu.Unlock()
if flush != nil {
flush()
}
return nil
}
func results(inputs ...interface{}) []interface{} {
return inputs
}
func newUncachedPrincipalX(path string, timeout time.Duration) (*client, error) {
caller := new(ipcCaller)
i := ipc.NewIPC()
i.Serve(caller)
conn, err := i.Connect(path, timeout)
if err != nil {
return nil, err
}
caller.conn = conn
agent := &client{caller: caller}
if err := agent.fetchPublicKey(); err != nil {
return nil, err
}
var dis security.Discharge
var cacheTime time.Time
if err := caller.call("BlessingStoreDischarge2", results(&dis, &cacheTime), security.Caveat{}, security.DischargeImpetus{}); err != nil {
// If we can't fetch a discharge with two results, then we should fall back
// to the old one result version.
agent.noCacheTimes = true
}
return agent, nil
}
// NewAgentPrincipal returns a security.Pricipal using the PrivateKey held in a
// remote agent process.
//
// 'path' is the path to the agent socket, typically obtained from
// os.GetEnv(envvar.AgentAddress).
//
// 'timeout' specifies how long to retry connecting to the socket if it's not
// ready.
//
// The caller should call Close on the returned Principal once it's no longer
// used, in order to free up resources.
func NewAgentPrincipal(path string, timeout time.Duration) (agent.Principal, error) {
p, err := newUncachedPrincipalX(path, timeout)
if err != nil {
return nil, err
}
cached, flush, err := cache.NewCachedPrincipalX(p)
if err != nil {
return nil, err
}
caller := p.caller.(*ipcCaller)
caller.mu.Lock()
caller.flush = flush
caller.mu.Unlock()
return cached, nil
}
// TODO(caprita): Deprecate.
// NewAgentPrincipalX returns a security.Pricipal using the PrivateKey held in a
// remote agent process.
//
// 'path' is the path to the agent socket, typically obtained from
// os.GetEnv(envvar.AgentAddress). If the socket is not ready,
// NewAgentPrincipalX retries for a minute before giving up.
//
// The caller should call Close on the returned Principal once it's no longer
// used, in order to free up resources.
func NewAgentPrincipalX(path string) (agent.Principal, error) {
return NewAgentPrincipal(path, defaultConnectTimeout)
}
func (c *client) Close() error {
return c.caller.Close()
}
func (c *client) fetchPublicKey() (err error) {
var b []byte
if err = c.caller.call("PublicKey", results(&b)); err != nil {
return
}
c.key, err = security.UnmarshalPublicKey(b)
return
}
func (c *client) Bless(key security.PublicKey, with security.Blessings, extension string, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Blessings, error) {
var blessings security.Blessings
marshalledKey, err := key.MarshalBinary()
if err != nil {
return security.Blessings{}, err
}
err = c.caller.call("Bless", results(&blessings), marshalledKey, with, extension, caveat, additionalCaveats)
return blessings, err
}
func (c *client) BlessSelf(name string, caveats ...security.Caveat) (security.Blessings, error) {
var blessings security.Blessings
err := c.caller.call("BlessSelf", results(&blessings), name, caveats)
return blessings, err
}
func (c *client) Sign(message []byte) (sig security.Signature, err error) {
err = c.caller.call("Sign", results(&sig), message)
return
}
func (c *client) MintDischarge(forCaveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge ...security.Caveat) (security.Discharge, error) {
var discharge security.Discharge
if err := c.caller.call("MintDischarge", results(&discharge), forCaveat, caveatOnDischarge, additionalCaveatsOnDischarge); err != nil {
return security.Discharge{}, err
}
return discharge, nil
}
func (c *client) PublicKey() security.PublicKey {
return c.key
}
func (c *client) BlessingStore() security.BlessingStore {
closedCh := make(chan struct{})
close(closedCh)
return &blessingStore{caller: c.caller, key: c.key, noCacheTimes: c.noCacheTimes, closedCh: closedCh}
}
func (c *client) Roots() security.BlessingRoots {
return &blessingRoots{c.caller}
}
type blessingStore struct {
caller caller
key security.PublicKey
noCacheTimes bool
closedCh chan struct{}
}
func (b *blessingStore) Set(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
var previous security.Blessings
err := b.caller.call("BlessingStoreSet", results(&previous), blessings, forPeers)
return previous, err
}
func (b *blessingStore) ForPeer(peerBlessings ...string) security.Blessings {
var blessings security.Blessings
if err := b.caller.call("BlessingStoreForPeer", results(&blessings), peerBlessings); err != nil {
logger.Global().Infof("error calling BlessingStorePeerBlessings: %v", err)
}
return blessings
}
func (b *blessingStore) SetDefault(blessings security.Blessings) error {
return b.caller.call("BlessingStoreSetDefault", results(), blessings)
}
func (b *blessingStore) Default() (security.Blessings, <-chan struct{}) {
var blessings security.Blessings
err := b.caller.call("BlessingStoreDefault", results(&blessings))
if err != nil {
logger.Global().Infof("error calling BlessingStoreDefault: %v", err)
return security.Blessings{}, b.closedCh
}
// In practice, this agent based blessing store is always cached and
// thus this retuned channel isn't used. However, in order to be
// conservative, return closed channel in the hopes that anyone relying
// on this being accurate will find this out more easily (for example,
// constantly refreshing the Default because the returned channel is
// closed) that if we silently hid the notifications by returning a
// channel that would never be closed.
return blessings, b.closedCh
}
func (b *blessingStore) PublicKey() security.PublicKey {
return b.key
}
func (b *blessingStore) PeerBlessings() map[security.BlessingPattern]security.Blessings {
var bmap map[security.BlessingPattern]security.Blessings
err := b.caller.call("BlessingStorePeerBlessings", results(&bmap))
if err != nil {
logger.Global().Infof("error calling BlessingStorePeerBlessings: %v", err)
return nil
}
return bmap
}
func (b *blessingStore) DebugString() (s string) {
err := b.caller.call("BlessingStoreDebugString", results(&s))
if err != nil {
s = fmt.Sprintf("error calling BlessingStoreDebugString: %v", err)
logger.Global().Infof(s)
}
return
}
func (b *blessingStore) CacheDischarge(d security.Discharge, c security.Caveat, i security.DischargeImpetus) {
err := b.caller.call("BlessingStoreCacheDischarge", results(), d, c, i)
if err != nil {
logger.Global().Infof("error calling BlessingStoreCacheDischarge: %v", err)
}
}
func (b *blessingStore) ClearDischarges(discharges ...security.Discharge) {
err := b.caller.call("BlessingStoreClearDischarges", results(), discharges)
if err != nil {
logger.Global().Infof("error calling BlessingStoreClearDischarges: %v", err)
}
}
func (b *blessingStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) (out security.Discharge, cacheTime time.Time) {
res := []interface{}{&out}
method := "BlessingStoreDischarge"
if !b.noCacheTimes {
res = append(res, &cacheTime)
method = "BlessingStoreDischarge2"
}
err := b.caller.call(method, res, caveat, impetus)
if err != nil {
logger.Global().Infof("error calling BlessingStoreDischarge: %v", err)
}
return
}
type blessingRoots struct {
caller caller
}
func (b *blessingRoots) Add(root []byte, pattern security.BlessingPattern) error {
return b.caller.call("BlessingRootsAdd", results(), root, pattern)
}
func (b *blessingRoots) Recognized(root []byte, blessing string) error {
return b.caller.call("BlessingRootsRecognized", results(), root, blessing)
}
func (b *blessingRoots) Dump() map[security.BlessingPattern][]security.PublicKey {
var marshaledRoots map[security.BlessingPattern][][]byte
if err := b.caller.call("BlessingRootsDump", results(&marshaledRoots)); err != nil {
logger.Global().Infof("error calling BlessingRootsDump: %v", err)
return nil
}
ret := make(map[security.BlessingPattern][]security.PublicKey)
for p, marshaledKeys := range marshaledRoots {
for _, marshaledKey := range marshaledKeys {
key, err := security.UnmarshalPublicKey(marshaledKey)
if err != nil {
logger.Global().Infof("security.UnmarshalPublicKey(%v) returned error: %v", marshaledKey, err)
continue
}
ret[p] = append(ret[p], key)
}
}
return ret
}
func (b *blessingRoots) DebugString() (s string) {
err := b.caller.call("BlessingRootsDebugString", results(&s))
if err != nil {
s = fmt.Sprintf("error calling BlessingRootsDebugString: %v", err)
logger.Global().Infof(s)
}
return
}