blob: 62b140f181916e1b79545aa7623a3905b4f7c5b9 [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 auditor
import (
"database/sql"
"fmt"
"strings"
"time"
"v.io/v23/context"
"v.io/v23/security"
"v.io/v23/vom"
"v.io/x/ref/lib/security/audit"
)
// BlessingLogReader provides the Read method to read audit logs.
// Read returns a channel of BlessingEntrys whose extension matches the provided email.
type BlessingLogReader interface {
Read(ctx *context.T, email string) <-chan BlessingEntry
}
// BlessingEntry contains important logged information about a blessed principal.
type BlessingEntry struct {
Email string
Caveats []security.Caveat
Timestamp time.Time // Time when the blesings were created.
RevocationCaveatID string
Blessings security.Blessings
DecodeError error
}
// NewSQLBlessingAuditor returns an auditor for wrapping a principal with, and a BlessingLogReader
// for reading the audits made by that auditor. The config is used to construct the connection
// to the SQL database that the auditor and BlessingLogReader use.
func NewSQLBlessingAuditor(ctx *context.T, sqlDB *sql.DB) (audit.Auditor, BlessingLogReader, error) {
db, err := newSQLDatabase(ctx, sqlDB, "BlessingAudit")
if err != nil {
return nil, nil, fmt.Errorf("failed to create sql db: %v", err)
}
auditor, reader := &blessingAuditor{db}, &blessingLogReader{db}
return auditor, reader, nil
}
type blessingAuditor struct {
db database
}
func (a *blessingAuditor) Audit(ctx *context.T, entry audit.Entry) error {
if entry.Method != "Bless" {
return nil
}
dbentry, err := newDatabaseEntry(entry)
if err != nil {
return err
}
return a.db.Insert(ctx, dbentry)
}
type blessingLogReader struct {
db database
}
func (r *blessingLogReader) Read(ctx *context.T, email string) <-chan BlessingEntry {
c := make(chan BlessingEntry)
go r.sendAuditEvents(ctx, c, email)
return c
}
func (r *blessingLogReader) sendAuditEvents(ctx *context.T, dst chan<- BlessingEntry, email string) {
defer close(dst)
dbch := r.db.Query(ctx, email)
for dbentry := range dbch {
dst <- newBlessingEntry(dbentry)
}
}
func newDatabaseEntry(entry audit.Entry) (databaseEntry, error) {
d := databaseEntry{timestamp: entry.Timestamp}
extension, ok := entry.Arguments[2].(string)
if !ok {
return d, fmt.Errorf("failed to extract extension")
}
// Find the first email component
for _, n := range strings.Split(extension, security.ChainSeparator) {
// HACK ALERT: An email is the first entry to end up with
// a single "@" in it
if strings.Count(n, "@") == 1 {
d.email = n
break
}
}
if len(d.email) == 0 {
return d, fmt.Errorf("failed to extract email address from extension %q", extension)
}
var caveats []security.Caveat
for _, arg := range entry.Arguments[3:] {
if cav, ok := arg.(security.Caveat); !ok {
return d, fmt.Errorf("failed to extract Caveat")
} else {
caveats = append(caveats, cav)
}
}
var blessings security.Blessings
if blessings, ok = entry.Results[0].(security.Blessings); !ok {
return d, fmt.Errorf("failed to extract result blessing")
}
var err error
if d.blessings, err = vom.Encode(blessings); err != nil {
return d, err
}
if d.caveats, err = vom.Encode(caveats); err != nil {
return d, err
}
return d, nil
}
func newBlessingEntry(dbentry databaseEntry) BlessingEntry {
if dbentry.decodeErr != nil {
return BlessingEntry{DecodeError: dbentry.decodeErr}
}
b := BlessingEntry{
Email: dbentry.email,
Timestamp: dbentry.timestamp,
}
if err := vom.Decode(dbentry.blessings, &b.Blessings); err != nil {
return BlessingEntry{DecodeError: fmt.Errorf("failed to decode blessings: %s", err)}
}
if err := vom.Decode(dbentry.caveats, &b.Caveats); err != nil {
return BlessingEntry{DecodeError: fmt.Errorf("failed to decode caveats: %s", err)}
}
b.RevocationCaveatID = revocationCaveatID(b.Caveats)
return b
}
func revocationCaveatID(caveats []security.Caveat) string {
for _, cav := range caveats {
if tp := cav.ThirdPartyDetails(); tp != nil {
return tp.ID()
}
}
return ""
}