blob: d80eea69eb77db681988ab742aaedb8cf578e1ff [file] [log] [blame]
// Copyright 2016 The Upspin 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 server implements the user service upspin.KeyServer
// that runs with the backing of storage.Storage.
package server
import (
"crypto/sha256"
"encoding/json"
"math/big"
"net"
"strings"
"sync"
"upspin.io/cache"
"upspin.io/cloud/storage"
"upspin.io/errors"
"upspin.io/factotum"
"upspin.io/log"
"upspin.io/metric"
"upspin.io/upspin"
"upspin.io/user"
"upspin.io/valid"
)
const cacheSize = 10000
// New initializes an instance of the KeyServer
// that stores its data in the given Storage implementation.
func New(options ...string) (upspin.KeyServer, error) {
const op errors.Op = "key/server.New"
var backend string
var dialOpts []storage.DialOpts
for _, option := range options {
const prefix = "backend="
if strings.HasPrefix(option, prefix) {
backend = option[len(prefix):]
continue
}
// Pass other options to the storage backend.
dialOpts = append(dialOpts, storage.WithOptions(option))
}
if backend == "" {
return nil, errors.E(op, errors.Invalid, `storage "backend" option is missing`)
}
s, err := storage.Dial(backend, dialOpts...)
if err != nil {
return nil, errors.E(op, err)
}
return &server{
storage: s,
refCount: &refCount{count: 1},
lookupTXT: net.LookupTXT,
logger: &loggerImpl{storage: s},
cache: cache.NewLRU(cacheSize),
negCache: cache.NewLRU(cacheSize),
}, nil
}
// server is the implementation of the KeyServer Service.
type server struct {
storage storage.Storage
*refCount
// A text log of all mutations to the key server.
logger
// The name of the user accessing this server, set by Dial.
user upspin.UserName
// lookupTXT is a DNS lookup function that returns all TXT fields for a
// domain. It should alias net.LookupTXT except in tests.
lookupTXT func(domain string) ([]string, error)
// cache caches known users. Key is a UserName. Value is *userEntry.
cache *cache.LRU
// negCache caches the absence of a user. Key is a UserName and value is
// ignored.
negCache *cache.LRU
}
var _ upspin.KeyServer = (*server)(nil)
type refCount struct {
sync.Mutex
count int
}
// userEntry is the on-disk representation of upspin.User, further annotated with
// non-public information, such as whether the user is an admin.
type userEntry struct {
User upspin.User
IsAdmin bool
}
// Lookup implements upspin.KeyServer.
func (s *server) Lookup(name upspin.UserName) (*upspin.User, error) {
const op errors.Op = "key/server.Lookup"
m, span := metric.NewSpan(op)
defer m.Done()
if err := valid.UserName(name); err != nil {
return nil, errors.E(op, name, err)
}
entry, err := s.lookup(op, name, span)
if err != nil {
return nil, err
}
return &entry.User, nil
}
// lookup looks up the internal user record, using caches when available.
func (s *server) lookup(op errors.Op, name upspin.UserName, span *metric.Span) (*userEntry, error) {
// Check positive cache first.
if entry, found := s.cache.Get(name); found {
return entry.(*userEntry), nil
}
// Check negative cache next.
if _, found := s.negCache.Get(name); found {
return nil, errors.E(errors.NotExist, name)
}
// No information. Find it on our storage backend.
sp := span.StartSpan(op + ": fetchUserEntry")
entry, err := s.fetchUserEntry(op, name)
sp.End()
if err != nil {
// Not found: add to negative cache.
if errors.Is(errors.NotExist, err) {
s.negCache.Add(name, true)
}
return nil, err
}
// Found. Add to positive cache.
s.cache.Add(name, entry)
return entry, nil
}
// Put implements upspin.KeyServer.
func (s *server) Put(u *upspin.User) error {
const op errors.Op = "key/server.Put"
m, span := metric.NewSpan(op)
defer m.Done()
if s.user == "" {
return errors.E(op, errors.Internal, "not bound to user")
}
if err := valid.User(u); err != nil {
return errors.E(op, err)
}
// Retrieve info about the user we want to Put.
isAdmin := false
newUser := false
entry, err := s.lookup(op, u.Name, span)
switch {
case errors.Is(errors.NotExist, err):
// OK; adding new user.
newUser = true
case err != nil:
return err
default:
// User exists.
isAdmin = entry.IsAdmin
}
if err := s.canPut(op, u.Name, newUser, span); err != nil {
return err
}
sp := span.StartSpan("logger.PutAttempt")
err = s.logger.PutAttempt(s.user, u)
sp.End()
if err != nil {
return errors.E(op, err)
}
entry = &userEntry{User: *u, IsAdmin: isAdmin}
sp = span.StartSpan("putUserEntry")
err = s.putUserEntry(op, entry)
sp.End()
if err != nil {
// Clear out both negative and positive caches on error since we are not certain
// about the remote storage state.
s.negCache.Remove(u.Name)
s.cache.Remove(u.Name)
return err
}
// Remove this new user from the negative cache and update it on the
// positive cache.
s.negCache.Remove(u.Name)
s.cache.Add(u.Name, entry)
sp = span.StartSpan("logger.PutSuccess")
err = s.logger.PutSuccess(s.user, u)
sp.End()
if err != nil {
return errors.E(op, err)
}
return nil
}
// canPut reports whether the current logged-in user can Put the (new or
// existing) target user.
func (s *server) canPut(op errors.Op, target upspin.UserName, isTargetNew bool, span *metric.Span) error {
sp := span.StartSpan("canPut")
defer sp.End()
name, suffix, domain, err := user.Parse(target)
if err != nil {
return errors.E(op, err)
}
// Do not allow * wildcard in name.
if name == "*" {
return errors.E(op, errors.Invalid, target, "user has wildcard '*' in name")
}
// If the current user is the same as target, it can proceed.
if s.user == target {
return nil
}
// For suffixed users, if the current user is the canonical user for
// target, let it proceed.
if suffix != "" {
uname := name[:len(name)-(len("+")+len(suffix))]
if s.user == upspin.UserName(uname+"@"+domain) {
// Current user is the owner of target.
return nil
}
}
// Check whether the current user is a global admin.
ss := sp.StartSpan("fetchUserEntry")
entry, err := s.fetchUserEntry(op, s.user)
ss.End()
if err != nil {
return err
}
if entry.IsAdmin {
return nil
}
// Finally, for a newly-created user, check whether the logged-in user
// owns the domain (and is thus allowed to create new users).
if !isTargetNew {
// Even domain admins cannot update their users.
return errors.E(op, errors.Exist, s.user)
}
err = s.verifyOwns(s.user, entry.User.PublicKey, domain)
if err == nil {
// User owns the domain for the target user.
return nil
}
return errors.E(op, errors.Permission, s.user, err)
}
// fetchUserEntry reads the user entry for a given user from the storage.
func (s *server) fetchUserEntry(op errors.Op, name upspin.UserName) (*userEntry, error) {
b, err := s.storage.Download(string(name))
if err != nil {
return nil, errors.E(op, name, err)
}
var entry userEntry
if err = json.Unmarshal(b, &entry); err != nil {
return nil, errors.E(op, errors.Invalid, name, err)
}
return &entry, nil
}
// putUserEntry writes the user entry for a user to the storage.
func (s *server) putUserEntry(op errors.Op, entry *userEntry) error {
if entry == nil {
return errors.E(op, errors.Invalid, "nil userEntry")
}
b, err := json.Marshal(entry)
if err != nil {
return errors.E(op, errors.Invalid, err)
}
if err = s.storage.Put(string(entry.User.Name), b); err != nil {
return errors.E(op, errors.IO, err)
}
return nil
}
// verifyOwns verifies whether the named user owns the given domain name, as
// per the domain name's upspin TXT field signature.
func (s *server) verifyOwns(u upspin.UserName, pubKey upspin.PublicKey, domain string) error {
txts, err := s.lookupTXT(domain)
if err != nil {
return errors.E(errors.IO, err)
}
lastErr := errors.Errorf("not an administrator for %s", domain)
const prefix = "upspin:"
for _, txt := range txts {
if len(txt) < len(prefix)+20 {
// not enough data.
continue
}
if !strings.HasPrefix(txt, prefix) {
continue
}
txt = txt[len(prefix):]
// Is there a signature with two segments after the prefix?
sigFields := strings.Split(txt, "-")
if len(sigFields) != 2 {
continue
}
// Parse signature.
var sig upspin.Signature
var rs, ss big.Int
if _, ok := rs.SetString(sigFields[0], 16); !ok {
lastErr = errors.E(errors.Invalid, "invalid signature field0")
continue
}
if _, ok := ss.SetString(sigFields[1], 16); !ok {
lastErr = errors.E(errors.Invalid, "invalid signature field1")
continue
}
sig.R = &rs
sig.S = &ss
hash := sha256.Sum256([]byte("upspin-domain:" + domain + "-" + string(u)))
err := factotum.Verify(hash[:], sig, pubKey)
if err == nil {
// Success!
return nil
}
log.Debug.Printf("key/server: failed to verify that %q owns %q with pubKey %q, sig %q: %v", u, domain, pubKey, txt, err)
lastErr = errors.E(errors.Errorf("%s: verifying ownership of domain %s; re-run cmd/upspin setupdomain?", err, domain))
}
return lastErr
}
// Log implements Logger.
func (s *server) Log() ([]byte, error) {
const op errors.Op = "key/server.Log"
data, err := s.logger.ReadAll()
if err != nil {
return nil, errors.E(op, err)
}
return data, nil
}
// Dial implements upspin.Service.
func (s *server) Dial(cfg upspin.Config, e upspin.Endpoint) (upspin.Service, error) {
s.refCount.Lock()
s.refCount.count++
s.refCount.Unlock()
svc := *s
svc.user = cfg.UserName()
return &svc, nil
}
// Close implements upspin.Service.
func (s *server) Close() {
// This instance is no longer tied to a user.
s.user = ""
s.refCount.Lock()
defer s.refCount.Unlock()
s.refCount.count--
if s.refCount.count == 0 {
s.storage = nil
}
}
// Endpoint implements upspin.Service.
func (s *server) Endpoint() upspin.Endpoint {
return upspin.Endpoint{} // No endpoint.
}