blob: f36ccea81f437c3050c94e4d03b7a9793b4efe69 [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 // import "upspin.io/key/server"
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"sync"
"time"
"upspin.io/cloud/storage"
"upspin.io/errors"
"upspin.io/upspin"
)
// Logger returns KeyServer logs for auditing purposes.
// It is implemented by the KeyServer in this package,
// but is not part of the upspin.KeyServer interface.
type Logger interface {
Log() ([]byte, error)
}
type logger interface {
PutAttempt(actor upspin.UserName, u *upspin.User) error
PutSuccess(actor upspin.UserName, u *upspin.User) error
ReadAll() ([]byte, error)
}
// logRef is the name in Google Cloud Storage under which the log is stored.
const logRef = "keyserver/log"
type loggerImpl struct {
mu sync.Mutex
log []byte
storage storage.Storage
}
// PutAttempt records a KeyServer.Put attempt
// by the given actor for the given user record.
func (l *loggerImpl) PutAttempt(actor upspin.UserName, u *upspin.User) error {
const op errors.Op = errors.Op("key/gcp.Logger.PutAttempt")
if err := l.put(time.Now(), "put attempt", actor, u); err != nil {
return errors.E(op, err)
}
return nil
}
// PutSuccess records a successful KeyServer.Put
// by the given actor for the given user record.
func (l *loggerImpl) PutSuccess(actor upspin.UserName, u *upspin.User) error {
const op errors.Op = errors.Op("key/gcp.Logger.PutSuccess")
if err := l.put(time.Now(), "put success", actor, u); err != nil {
return errors.E(op, err)
}
return nil
}
var hashPrefix = []byte("SHA256:")
func (l *loggerImpl) put(now time.Time, kind string, actor upspin.UserName, u *upspin.User) error {
content, err := json.Marshal(u)
if err != nil {
return err
}
record := []byte(fmt.Sprintf("%v: %s by %q: %s\n", now.In(time.UTC), kind, actor, content))
h := sha256.New()
h.Write([]byte(record))
l.mu.Lock()
defer l.mu.Unlock()
if err := l.populate(); err != nil {
return err
}
// Include the previous record's hash in the new hash,
// but only if there is a previous record.
i := bytes.LastIndex(l.log, hashPrefix)
if i == -1 {
if len(l.log) > 0 {
return errors.Str("key log corrupt: non-empty but lacks previous record hash")
}
// No previous record; that's ok.
} else {
// Grab the hash hex, stripping the prefix and trailing newline.
prevHash := l.log[i+len(hashPrefix) : len(l.log)-1]
h.Write(prevHash)
}
l.log = append(l.log, record...)
l.log = append(l.log, fmt.Sprintf("%s%x\n", hashPrefix, h.Sum(nil))...)
return l.storage.Put(logRef, l.log)
}
// ReadAll returns the log bytes.
func (l *loggerImpl) ReadAll() ([]byte, error) {
const op errors.Op = "key/gcp.Logger.ReadAll"
l.mu.Lock()
defer l.mu.Unlock()
if err := l.populate(); err != nil {
return nil, errors.E(op, err)
}
// Make a copy of the log data to prevent tampering,
// however unlikely that may be.
data := append([]byte(nil), l.log...)
return data, nil
}
// populate reads log data from storage and populates l.log.
// If l.log is already populated nothing happens.
// l.mu must be held.
func (l *loggerImpl) populate() error {
if l.log != nil {
return nil
}
data, err := l.storage.Download(logRef)
if errors.Is(errors.NotExist, err) {
// The log doesn't exist yet.
// Make l.log non-nil so that we don't keep trying.
l.log = []byte{}
return nil
}
if err != nil {
return err
}
l.log = data
return nil
}