Add the Hammerhead control plane
Dashboard and API, accounts (organizations, roles, invitations, 2FA, OIDC single sign-on, API keys, revocable sessions), an encrypted SQLite database, resource logins, alerts and /metrics, mail egress management with DKIM keys, and the .env loader. Split out of the original single repository; depends on Reef for the shared types. The end-to-end tests that run Hammerhead, Blacktip and Bamboo together live in e2e/. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3daa368233
commit
f521fa8668
35 files changed
+10392
-1
No files matched your search
@@ -0,0 +1,70 @@
|
||||
package hammerhead
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"git.veillax.com/Veillax/reef/model"
|
||||
)
|
||||
|
||||
type AuditEntry struct {
|
||||
ID int64 `json:"id"`
|
||||
Time time.Time `json:"time"`
|
||||
OrgID string `json:"orgId,omitempty"`
|
||||
Actor string `json:"actor"`
|
||||
Action string `json:"action"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
}
|
||||
|
||||
// logStore is a thin wrapper so the handlers don't care where logs live.
|
||||
type logStore struct{ db *DB }
|
||||
|
||||
func (l *logStore) addAccess(es []model.LogEntry) { l.db.addAccess(es) }
|
||||
func (l *logStore) addAudit(e AuditEntry) { l.db.addAudit(e) }
|
||||
|
||||
func (l *logStore) queryAccess(f accessFilter) []model.LogEntry { return l.db.queryAccess(f) }
|
||||
|
||||
// queryAudit returns newest-first entries; org "" selects server-level events.
|
||||
func (l *logStore) queryAudit(org string, limit int, before int64) []AuditEntry {
|
||||
return l.db.queryAudit(org, limit, before)
|
||||
}
|
||||
|
||||
func (s *Server) audit(orgID, actor, action, detail string) {
|
||||
s.logs.addAudit(AuditEntry{Time: time.Now(), OrgID: orgID, Actor: actor, Action: action, Detail: detail})
|
||||
}
|
||||
|
||||
// importLegacyLogs moves logs from the old JSONL files into the database, once.
|
||||
func (s *Server) importLegacyLogs() {
|
||||
dir := filepath.Join(s.dir, "logs")
|
||||
read := func(name string, each func([]byte)) {
|
||||
path := filepath.Join(dir, name)
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sc := bufio.NewScanner(f)
|
||||
sc.Buffer(make([]byte, 1<<20), 1<<20)
|
||||
for sc.Scan() {
|
||||
each(append([]byte(nil), sc.Bytes()...))
|
||||
}
|
||||
f.Close()
|
||||
os.Rename(path, path+".migrated")
|
||||
}
|
||||
var batch []model.LogEntry
|
||||
read("access.jsonl", func(b []byte) {
|
||||
var e model.LogEntry
|
||||
if json.Unmarshal(b, &e) == nil {
|
||||
batch = append(batch, e)
|
||||
}
|
||||
})
|
||||
s.db.addAccess(batch)
|
||||
read("audit.jsonl", func(b []byte) {
|
||||
var e AuditEntry
|
||||
if json.Unmarshal(b, &e) == nil {
|
||||
s.db.addAudit(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user