diff --git a/cmd/auditd-dbtool/auditd-dbtool.go b/cmd/auditd-dbtool/auditd-dbtool.go new file mode 100644 index 0000000000000000000000000000000000000000..6803d840f4b38c727cd066ae1268bc37d2620c37 --- /dev/null +++ b/cmd/auditd-dbtool/auditd-dbtool.go @@ -0,0 +1,37 @@ +package main + +import ( + "flag" + "log" + "os" + + "git.autistici.org/ai/audit/server" +) + +var ( + doDump = flag.Bool("dump", false, "dump all messages in the db") + doRestore = flag.Bool("restore", false, "restore messages from a dump") + dbDir = flag.String("data-dir", "/var/lib/auditd", "Path to the database directory") +) + +func main() { + log.SetFlags(0) + flag.Parse() + + if (!*doDump && !*doRestore) || (*doDump && *doRestore) { + log.Fatal("Must specify either --dump or --restore") + } + + db := server.NewDB(*dbDir, nil) + defer db.Close() + + var err error + if *doDump { + err = db.Dump(os.Stdout) + } else { + err = db.Restore(os.Stdin) + } + if err != nil { + log.Fatal(err) + } +} diff --git a/debian/install-golang b/debian/install-golang index 57e181f082d567942b045fd275abb8647431de9f..f4aa095cfa630abfa05b2325b3b8036e59cac0a0 100755 --- a/debian/install-golang +++ b/debian/install-golang @@ -13,7 +13,7 @@ mangle_arch() { ROOT="$1" ARCH=`mangle_arch $2` VERSION="$3" -DL_URL=https://go.googlecode.com/files/go${VERSION}.linux-${ARCH}.tar.gz +DL_URL=https://storage.googleapis.com/golang/go${VERSION}.linux-${ARCH}.tar.gz mkdir -p ${ROOT} wget -O - ${DL_URL} \ diff --git a/debian/rules b/debian/rules index 78992644b22dee419a76d5022de70ff58f2484aa..de8fd35631347a035f57536d9427e538af1c72dc 100755 --- a/debian/rules +++ b/debian/rules @@ -8,7 +8,7 @@ export DH_OPTIONS GOPKG = git.autistici.org/ai/audit -GO_VERSION = 1.2.1 +GO_VERSION = 1.5.3 DEBDIR = $(CURDIR)/debian BUILDDIR = $(CURDIR)/debian/build @@ -33,6 +33,7 @@ override_dh_install: $(GOROOT)/bin/go get -d -v ./$(GOPKG)/... && \ CGO_CFLAGS=-Dleveldb_free=free $(GOROOT)/bin/go install -v ./$(GOPKG)/... && \ install -m 755 -o root -g root $(BUILDDIR)/bin/auditd $(DEBDIR)/ai-auditd/usr/sbin/auditd && \ + install -m 755 -o root -g root $(BUILDDIR)/bin/auditd-dbtool $(DEBDIR)/ai-auditd/usr/sbin/auditd-dbtool && \ install -m 755 -o root -g root $(BUILDDIR)/bin/localauditd $(DEBDIR)/localauditd/usr/sbin/localauditd && \ install -m 755 -o root -g root $(BUILDDIR)/bin/auditc $(DEBDIR)/auditc/usr/bin/auditc) diff --git a/server/db.go b/server/db.go index 355ccd37018a5c079a91aebc52e1c507a6cec8f2..4601799d4c97a2eca4befaa4aa97384eb82f69a3 100644 --- a/server/db.go +++ b/server/db.go @@ -1,8 +1,10 @@ package server import ( + "bufio" "bytes" "fmt" + "io" "log" "strings" @@ -116,11 +118,7 @@ func makePrefixKeyRange(prefix []byte) ([]byte, []byte) { return startKey, endKey } -// Write a Message to the database. -func (db *DB) Write(msg audit.Message) error { - wb := levigo.NewWriteBatch() - defer wb.Close() - +func (db *DB) writeToBatch(msg audit.Message, wb *levigo.WriteBatch) { // Generate a unique ID for the message. msgid := audit.NewUniqueId(msg.Stamp()) wb.Put(makeKey("messages", msgid), msg.ToJSON()) @@ -138,6 +136,60 @@ func (db *DB) Write(msg audit.Message) error { wb.Put(makeKey("index", []byte(key), []byte(value), msgid), msgid) } +} + +// Write a Message to the database. +func (db *DB) Write(msg audit.Message) error { + wb := levigo.NewWriteBatch() + defer wb.Close() + + db.writeToBatch(msg, wb) + + wo := levigo.NewWriteOptions() + defer wo.Close() + return db.leveldb.Write(wo, wb) +} + +// Dump contents to a writer. +func (db *DB) Dump(w io.Writer) error { + ro := levigo.NewReadOptions() + ro.SetFillCache(false) + defer ro.Close() + + i := db.leveldb.NewIterator(ro) + defer i.Close() + + startKey, endKey := makePrefixKeyRange(makeKey("messages")) + for i.Seek(startKey); i.Valid() && bytes.Compare(i.Key(), endKey) < 0; i.Next() { + fmt.Fprintf(w, "%s\n", i.Value()) + } + if err := i.GetError(); err != nil { + return err + } + return nil +} + +// Restore contents from a dump. +func (db *DB) Restore(r io.Reader) error { + wb := levigo.NewWriteBatch() + defer wb.Close() + + errs := 0 + scanner := bufio.NewScanner(r) + for scanner.Scan() { + msg, err := audit.MessageFromJSON(scanner.Bytes()) + if err != nil { + errs++ + continue + } + db.writeToBatch(msg, wb) + } + if err := scanner.Err(); err != nil { + return err + } + if errs > 0 { + return fmt.Errorf("failed to restore %d records", errs) + } wo := levigo.NewWriteOptions() defer wo.Close() diff --git a/server/db_test.go b/server/db_test.go index a945d35c327c551c2916a1ea94d534b3f6b78845..7c197c9f1d64c9927a45af49329549cdef74548c 100644 --- a/server/db_test.go +++ b/server/db_test.go @@ -1,6 +1,7 @@ package server import ( + "bytes" "fmt" "io/ioutil" "os" @@ -33,6 +34,44 @@ func testInsertRecords(db *DB, n int) error { return nil } +func TestDB_QueryRealData(t *testing.T) { + dbdir, _ := ioutil.TempDir("", "auditdb-") + defer os.RemoveAll(dbdir) + + db := NewDB(dbdir, nil) + defer db.Close() + + testData := []string{ + `{"host":"opposizione","message":"created email solemnis@privacyrequired.com","request_type":"email","source":"accountserver","stamp":1.437922949e+09,"user":"solemnis@privacyrequired.com","who":"ai-services"}`, + `{"alias":"info1@grrlz.net","host":"opposizione","message":"deleted alias info1@grrlz.net","source":"accountserver","stamp":1.434558486e+09,"user":"elitee@hacari.com","who":"elitee@hacari.com"}`, + `{"host":"opposizione","message":"password reset","source":"accountserver","stamp":1.439408672e+09,"user":"blackwolf@autistici.org","who":"accountserver"}`, + `{"host":"opposizione","message":"password reset","source":"accountserver","stamp":1.432290377e+09,"user":"blackwolf@autistici.org","who":"accountserver"}`, + `{"alias":"skankindaddy@autistici.org","host":"opposizione","message":"created alias skankindaddy@autistici.org","source":"accountserver","stamp":1.444286071e+09,"user":"ilnonno@autistici.org","who":"ilnonno@autistici.org"}`, + `{"alias":"sknkindaddy@autistici.org","host":"opposizione","message":"deleted alias sknkindaddy@autistici.org","source":"accountserver","stamp":1.44428608e+09,"user":"ilnonno@autistici.org","who":"ilnonno@autistici.org"}`, + `{"host":"opposizione","message":"password reset","source":"accountserver","stamp":1.426104104e+09,"user":"blackwolf@autistici.org","who":"accountserver"}`, + `{"host":"opposizione","message":"password reset","source":"accountserver","stamp":1.423795607e+09,"user":"blackwolf@autistici.org","who":"accountserver"}`, + `{"host":"opposizione","message":"password reset","source":"accountserver","stamp":1.445412207e+09,"user":"blackwolf@autistici.org","who":"accountserver"}`, + } + for _, data := range testData { + msg, _ := audit.MessageFromJSON([]byte(data)) + if err := db.Write(msg); err != nil { + t.Fatal(err) + } + } + + // Test simple query. + result, err := db.Query(map[string]string{"user": "blackwolf@autistici.org"}) + if err != nil { + t.Fatal(err) + } + if len(result) != 5 { + t.Fatalf("bad # of results: %+v", result) + } + if result[0]["user"].(string) != "blackwolf@autistici.org" { + t.Fatalf("bad result: %+v", result[0]) + } +} + func TestDB_Query(t *testing.T) { dbdir, _ := ioutil.TempDir("", "auditdb-") defer os.RemoveAll(dbdir) @@ -86,6 +125,39 @@ func TestDB_Query(t *testing.T) { } } +func TestDB_DumpAndRestore(t *testing.T) { + dbdir, _ := ioutil.TempDir("", "auditdb-") + defer os.RemoveAll(dbdir) + + db := NewDB(dbdir, nil) + defer db.Close() + + testData := []string{ + `{"user": "a", "host": "one", "message": "wow", "stamp": 123456789}`, + `{"user": "b", "host": "one", "message": "wow", "stamp": 123456790}`, + `{"user": "c", "host": "two", "message": "wow", "stamp": 123456790}`, + } + for _, data := range testData { + msg, _ := audit.MessageFromJSON([]byte(data)) + if err := db.Write(msg); err != nil { + t.Fatal(err) + } + } + + var buf bytes.Buffer + if err := db.Dump(&buf); err != nil { + t.Fatalf("Dump: %v", err) + } + + dbdir2, _ := ioutil.TempDir("", "auditdb-") + defer os.RemoveAll(dbdir2) + db2 := NewDB(dbdir2, nil) + defer db2.Close() + if err := db2.Restore(&buf); err != nil { + t.Fatalf("Restore: %v", err) + } +} + // Benchmark straight insertion performance. func BenchmarkDB_Insert(b *testing.B) { dbdir, _ := ioutil.TempDir("", "auditdb-") diff --git a/unique_id.go b/unique_id.go index e5a8f7994afc86595f3aea47a09b7213bdd21585..686e17c0d3f0e49007f3e324ebeb9b360ff39b77 100644 --- a/unique_id.go +++ b/unique_id.go @@ -7,6 +7,12 @@ import ( "time" ) +func init() { + // Just need to differentiate between runs, not aiming for + // cryptographic safety here. + rand.Seed(time.Now().Unix()) +} + // Generate a new time-based unique ID for use as primary key. The // resulting IDs can be sorted lexicographically preserving the time // ordering. @@ -15,7 +21,7 @@ func NewUniqueId(t time.Time) []byte { // The key consists of serialized time + sufficiently large // random number. This should allow a high insertion rate. - binary.Write(&b, binary.BigEndian, t) + binary.Write(&b, binary.BigEndian, t.UnixNano()) binary.Write(&b, binary.BigEndian, rand.Int63()) return b.Bytes()