feat(monitor/collectors/kev): kev_entries repository with diff lookup
This commit is contained in:
parent
5ad1ff8e14
commit
701bc8e790
|
|
@ -0,0 +1,77 @@
|
|||
// ©AngelaMos | 2026
|
||||
// repo.go
|
||||
|
||||
package kev
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type Row struct {
|
||||
CveID string `db:"cve_id"`
|
||||
Vendor string `db:"vendor"`
|
||||
Product string `db:"product"`
|
||||
VulnerabilityName string `db:"vulnerability_name"`
|
||||
DateAdded time.Time `db:"date_added"`
|
||||
DueDate *time.Time `db:"due_date"`
|
||||
RansomwareUse string `db:"ransomware_use"`
|
||||
Payload json.RawMessage `db:"payload"`
|
||||
}
|
||||
|
||||
type Repo struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewRepo(db *sqlx.DB) *Repo { return &Repo{db: db} }
|
||||
|
||||
func (r *Repo) Insert(ctx context.Context, row Row) error {
|
||||
_, err := r.db.ExecContext(ctx, `
|
||||
INSERT INTO kev_entries
|
||||
(cve_id, vendor, product, vulnerability_name, date_added, due_date, ransomware_use, payload)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (cve_id) DO UPDATE SET
|
||||
vendor = EXCLUDED.vendor,
|
||||
product = EXCLUDED.product,
|
||||
vulnerability_name = EXCLUDED.vulnerability_name,
|
||||
date_added = EXCLUDED.date_added,
|
||||
due_date = EXCLUDED.due_date,
|
||||
ransomware_use = EXCLUDED.ransomware_use,
|
||||
payload = EXCLUDED.payload`,
|
||||
row.CveID, row.Vendor, row.Product, row.VulnerabilityName,
|
||||
row.DateAdded, row.DueDate, row.RansomwareUse, []byte(row.Payload),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert kev %s: %w", row.CveID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repo) KnownIDs(ctx context.Context, ids []string) (map[string]bool, error) {
|
||||
if len(ids) == 0 {
|
||||
return map[string]bool{}, nil
|
||||
}
|
||||
var found []string
|
||||
if err := r.db.SelectContext(ctx, &found,
|
||||
`SELECT cve_id FROM kev_entries WHERE cve_id = ANY($1::text[])`, pq.Array(ids)); err != nil {
|
||||
return nil, fmt.Errorf("kev known ids: %w", err)
|
||||
}
|
||||
out := make(map[string]bool, len(found))
|
||||
for _, id := range found {
|
||||
out[id] = true
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *Repo) Count(ctx context.Context) (int64, error) {
|
||||
var n int64
|
||||
if err := r.db.GetContext(ctx, &n, `SELECT count(*) FROM kev_entries`); err != nil {
|
||||
return 0, fmt.Errorf("kev count: %w", err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
// ©AngelaMos | 2026
|
||||
// repo_test.go
|
||||
|
||||
package kev_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||
|
||||
"github.com/carterperez-dev/monitor-the-situation/backend/internal/collectors/kev"
|
||||
)
|
||||
|
||||
func setupDB(t *testing.T) *sqlx.DB {
|
||||
t.Helper()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pg, err := postgres.Run(ctx, "postgres:17-alpine",
|
||||
postgres.WithDatabase("monitor"),
|
||||
postgres.WithUsername("monitor"),
|
||||
postgres.WithPassword("monitor"),
|
||||
postgres.BasicWaitStrategies(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = pg.Terminate(context.Background()) })
|
||||
|
||||
dsn, err := pg.ConnectionString(ctx, "sslmode=disable")
|
||||
require.NoError(t, err)
|
||||
|
||||
db, err := sqlx.ConnectContext(ctx, "pgx", dsn)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = db.Close() })
|
||||
|
||||
_, err = db.ExecContext(ctx, `
|
||||
CREATE TABLE kev_entries (
|
||||
cve_id text PRIMARY KEY,
|
||||
vendor text NOT NULL DEFAULT '',
|
||||
product text NOT NULL DEFAULT '',
|
||||
vulnerability_name text NOT NULL DEFAULT '',
|
||||
date_added date NOT NULL,
|
||||
due_date date,
|
||||
ransomware_use text NOT NULL DEFAULT '',
|
||||
payload jsonb NOT NULL
|
||||
)`)
|
||||
require.NoError(t, err)
|
||||
return db
|
||||
}
|
||||
|
||||
func TestRepo_InsertAndKnownIDs(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
repo := kev.NewRepo(db)
|
||||
ctx := context.Background()
|
||||
added := time.Date(2026, 4, 30, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
require.NoError(t, repo.Insert(ctx, kev.Row{
|
||||
CveID: "CVE-2024-3094",
|
||||
Vendor: "JiaT75",
|
||||
Product: "xz",
|
||||
VulnerabilityName: "Backdoor",
|
||||
DateAdded: added,
|
||||
Payload: json.RawMessage(`{"cveID":"CVE-2024-3094"}`),
|
||||
}))
|
||||
|
||||
known, err := repo.KnownIDs(ctx, []string{"CVE-2024-3094", "CVE-2024-MISSING"})
|
||||
require.NoError(t, err)
|
||||
require.True(t, known["CVE-2024-3094"])
|
||||
require.False(t, known["CVE-2024-MISSING"])
|
||||
}
|
||||
|
||||
func TestRepo_InsertIsIdempotent(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
repo := kev.NewRepo(db)
|
||||
ctx := context.Background()
|
||||
|
||||
row := kev.Row{
|
||||
CveID: "CVE-2024-X",
|
||||
Vendor: "v1",
|
||||
DateAdded: time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC),
|
||||
Payload: json.RawMessage(`{}`),
|
||||
}
|
||||
require.NoError(t, repo.Insert(ctx, row))
|
||||
|
||||
updated := row
|
||||
updated.Vendor = "v2"
|
||||
require.NoError(t, repo.Insert(ctx, updated))
|
||||
|
||||
count, err := repo.Count(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, count)
|
||||
}
|
||||
|
||||
func TestRepo_CountReflectsTotal(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
repo := kev.NewRepo(db)
|
||||
ctx := context.Background()
|
||||
added := time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
for _, id := range []string{"CVE-A", "CVE-B", "CVE-C"} {
|
||||
require.NoError(t, repo.Insert(ctx, kev.Row{
|
||||
CveID: id, DateAdded: added, Payload: json.RawMessage(`{}`),
|
||||
}))
|
||||
}
|
||||
|
||||
count, err := repo.Count(ctx)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 3, count)
|
||||
}
|
||||
Loading…
Reference in New Issue