зеркало из https://github.com/getsops/sops.git
171 строка
4.8 KiB
Go
171 строка
4.8 KiB
Go
package audit
|
|
|
|
import (
|
|
"database/sql"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/user"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
// empty import as per https://godoc.org/github.com/lib/pq
|
|
_ "github.com/lib/pq"
|
|
|
|
"github.com/getsops/sops/v3/logging"
|
|
"github.com/sirupsen/logrus"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
var log *logrus.Logger
|
|
|
|
func init() {
|
|
log = logging.NewLogger("AUDIT")
|
|
confBytes, err := os.ReadFile(configFile)
|
|
if err != nil {
|
|
log.WithField("error", err).Debugf("Error reading config")
|
|
return
|
|
}
|
|
var conf config
|
|
err = yaml.Unmarshal(confBytes, &conf)
|
|
if err != nil {
|
|
log.WithField("error", err).Panicf("Error unmarshalling config")
|
|
}
|
|
// If we are running test, then don't create auditors.
|
|
// This is pretty hacky, but doing it The Right Way would require
|
|
// restructuring SOPS to use dependency injection instead of just using
|
|
// globals everywhere.
|
|
if flag.Lookup("test.v") != nil {
|
|
return
|
|
}
|
|
var auditErrors []error
|
|
|
|
for _, pgConf := range conf.Backends.Postgres {
|
|
auditDb, err := NewPostgresAuditor(pgConf.ConnStr)
|
|
if err != nil {
|
|
auditErrors = append(auditErrors, errors.Wrap(err, fmt.Sprintf("connectStr: %s, err", pgConf.ConnStr)))
|
|
}
|
|
auditors = append(auditors, auditDb)
|
|
}
|
|
if len(auditErrors) > 0 {
|
|
log.Errorf("connecting to audit database, defined in %s", configFile)
|
|
for _, err := range auditErrors {
|
|
log.Error(err)
|
|
}
|
|
log.Fatal("one or more audit backends reported errors, exiting")
|
|
}
|
|
}
|
|
|
|
// TODO: Make platform agnostic
|
|
const configFile = "/etc/sops/audit.yaml"
|
|
|
|
type config struct {
|
|
Backends struct {
|
|
Postgres []struct {
|
|
ConnStr string `yaml:"connection_string"`
|
|
} `yaml:"postgres"`
|
|
} `yaml:"backends"`
|
|
}
|
|
|
|
var auditors []Auditor
|
|
|
|
// SubmitEvent handles an event for all auditors
|
|
func SubmitEvent(event interface{}) {
|
|
for _, auditor := range auditors {
|
|
auditor.Handle(event)
|
|
}
|
|
}
|
|
|
|
// Register registers a new Auditor in the global auditor list
|
|
func Register(auditor Auditor) {
|
|
auditors = append(auditors, auditor)
|
|
}
|
|
|
|
// Auditor is notified when noteworthy events happen,
|
|
// for example when a file is encrypted or decrypted.
|
|
type Auditor interface {
|
|
// Handle() takes an audit event and attempts to persists it;
|
|
// how it is persisted and how errors are handled is up to the
|
|
// implementation of this interface.
|
|
Handle(event interface{})
|
|
}
|
|
|
|
// DecryptEvent contains fields relevant to a decryption event
|
|
type DecryptEvent struct {
|
|
File string
|
|
}
|
|
|
|
// EncryptEvent contains fields relevant to an encryption event
|
|
type EncryptEvent struct {
|
|
File string
|
|
}
|
|
|
|
// RotateEvent contains fields relevant to a key rotation event
|
|
type RotateEvent struct {
|
|
File string
|
|
}
|
|
|
|
// PostgresAuditor is a Postgres SQL DB implementation of the Auditor interface.
|
|
// It persists the audit event by writing a row to the 'audit_event' table.
|
|
// Errors with writing to the database will output a log message and the
|
|
// process will exit with status set to 1
|
|
type PostgresAuditor struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
// NewPostgresAuditor is the constructor for a new PostgresAuditor struct
|
|
// initialized with the given db connection string
|
|
func NewPostgresAuditor(connStr string) (*PostgresAuditor, error) {
|
|
db, err := sql.Open("postgres", connStr)
|
|
pg := &PostgresAuditor{DB: db}
|
|
if err != nil {
|
|
return pg, err
|
|
}
|
|
var result int
|
|
err = pg.DB.QueryRow("SELECT 1").Scan(&result)
|
|
if err != nil {
|
|
return pg, fmt.Errorf("Pinging audit database failed: %s", err)
|
|
} else if result != 1 {
|
|
return pg, fmt.Errorf("Database malfunction: SELECT 1 should return 1, but returned %d", result)
|
|
}
|
|
return pg, nil
|
|
}
|
|
|
|
// Handle persists the audit event by writing a row to the
|
|
// 'audit_event' postgres table
|
|
func (p *PostgresAuditor) Handle(event interface{}) {
|
|
u, err := user.Current()
|
|
if err != nil {
|
|
log.Fatalf("Error getting current user for auditing: %s", err)
|
|
}
|
|
switch event := event.(type) {
|
|
case DecryptEvent:
|
|
// Save the event to the database
|
|
log.WithField("file", event.File).
|
|
Debug("Saving decrypt event to database")
|
|
_, err = p.DB.Exec("INSERT INTO audit_event (action, username, file) VALUES ($1, $2, $3)", "decrypt", u.Username, event.File)
|
|
if err != nil {
|
|
log.Fatalf("Failed to insert audit record: %s", err)
|
|
}
|
|
case EncryptEvent:
|
|
// Save the event to the database
|
|
log.WithField("file", event.File).
|
|
Debug("Saving encrypt event to database")
|
|
_, err = p.DB.Exec("INSERT INTO audit_event (action, username, file) VALUES ($1, $2, $3)", "encrypt", u.Username, event.File)
|
|
if err != nil {
|
|
log.Fatalf("Failed to insert audit record: %s", err)
|
|
}
|
|
case RotateEvent:
|
|
// Save the event to the database
|
|
log.WithField("file", event.File).
|
|
Debug("Saving rotate event to database")
|
|
_, err = p.DB.Exec("INSERT INTO audit_event (action, username, file) VALUES ($1, $2, $3)", "rotate", u.Username, event.File)
|
|
if err != nil {
|
|
log.Fatalf("Failed to insert audit record: %s", err)
|
|
}
|
|
default:
|
|
log.WithField("type", fmt.Sprintf("%T", event)).
|
|
Info("Received unknown event")
|
|
}
|
|
}
|