-
Notifications
You must be signed in to change notification settings - Fork 28
/
blessing_auditor.go
145 lines (131 loc) · 4.09 KB
/
blessing_auditor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// 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 ""
}