feat(monitor/collectors/dshield): http client + frozen fixtures (topports, topips, dailysummary)

This commit is contained in:
CarterPerez-dev 2026-05-01 22:31:01 -04:00
parent a80664b32a
commit 808eb1b892
5 changed files with 181 additions and 0 deletions

View File

@ -0,0 +1,85 @@
// ©AngelaMos | 2026
// client.go
package dshield
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"golang.org/x/time/rate"
"github.com/carterperez-dev/monitor-the-situation/backend/internal/httpx"
)
const (
KindTopPorts = "topports"
KindTopIPs = "topips"
KindDailySummary = "dailysummary"
defaultTopN = 10
defaultDShieldRate = 2 * time.Second
defaultDShieldBudget = 5
defaultBreakerWindow = 60 * time.Second
)
type ClientConfig struct {
BaseURL string
TopN int
}
type Client struct {
hx *httpx.Client
topN int
}
func NewClient(cfg ClientConfig) *Client {
if cfg.BaseURL == "" {
cfg.BaseURL = "https://isc.sans.edu"
}
if cfg.TopN <= 0 {
cfg.TopN = defaultTopN
}
return &Client{
hx: httpx.New(httpx.Config{
Name: "dshield",
BaseURL: cfg.BaseURL,
Rate: rate.Every(defaultDShieldRate),
Burst: 1,
ConsecutiveFailureBudget: defaultDShieldBudget,
BreakerTimeout: defaultBreakerWindow,
}),
topN: cfg.TopN,
}
}
type SnapshotPayload struct {
Kind string
Payload json.RawMessage
}
func (c *Client) FetchAll(ctx context.Context) ([]SnapshotPayload, error) {
yesterday := time.Now().UTC().AddDate(0, 0, -1).Format("2006-01-02")
endpoints := []struct {
Kind string
Path string
}{
{KindTopPorts, "/api/topports/records/" + strconv.Itoa(c.topN) + "/" + yesterday + "?json"},
{KindTopIPs, "/api/topips/?json"},
{KindDailySummary, "/api/dailysummary/" + yesterday + "/?json"},
}
out := make([]SnapshotPayload, 0, len(endpoints))
for _, ep := range endpoints {
var raw json.RawMessage
if err := c.hx.GetJSON(ctx, ep.Path, nil, &raw); err != nil {
return nil, fmt.Errorf("dshield %s: %w", ep.Kind, err)
}
out = append(out, SnapshotPayload{Kind: ep.Kind, Payload: raw})
}
return out, nil
}

View File

@ -0,0 +1,93 @@
// ©AngelaMos | 2026
// client_test.go
package dshield_test
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/carterperez-dev/monitor-the-situation/backend/internal/collectors/dshield"
)
func newFixtureServer(t *testing.T) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
mux.HandleFunc("/api/topports/", func(w http.ResponseWriter, r *http.Request) {
require.Contains(t, r.URL.Path, "/api/topports/records/")
body, err := os.ReadFile(filepath.Join("testdata", "topports.json"))
require.NoError(t, err)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(body)
})
mux.HandleFunc("/api/topips/", func(w http.ResponseWriter, _ *http.Request) {
body, err := os.ReadFile(filepath.Join("testdata", "topips.json"))
require.NoError(t, err)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(body)
})
mux.HandleFunc("/api/dailysummary/", func(w http.ResponseWriter, r *http.Request) {
require.True(t, strings.HasPrefix(r.URL.Path, "/api/dailysummary/"))
body, err := os.ReadFile(filepath.Join("testdata", "dailysummary.json"))
require.NoError(t, err)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(body)
})
return httptest.NewServer(mux)
}
func TestClient_FetchAllReturnsThreeKinds(t *testing.T) {
srv := newFixtureServer(t)
defer srv.Close()
c := dshield.NewClient(dshield.ClientConfig{BaseURL: srv.URL, TopN: 10})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
snaps, err := c.FetchAll(ctx)
require.NoError(t, err)
require.Len(t, snaps, 3)
kinds := map[string]bool{}
for _, s := range snaps {
kinds[s.Kind] = true
require.NotEmpty(t, s.Payload, "kind %s payload empty", s.Kind)
}
require.True(t, kinds[dshield.KindTopPorts])
require.True(t, kinds[dshield.KindTopIPs])
require.True(t, kinds[dshield.KindDailySummary])
}
func TestClient_FetchAllUsesYesterdayDate(t *testing.T) {
var topPortsPath, dailyPath string
mux := http.NewServeMux()
mux.HandleFunc("/api/topports/", func(w http.ResponseWriter, r *http.Request) {
topPortsPath = r.URL.Path
_, _ = w.Write([]byte(`{}`))
})
mux.HandleFunc("/api/topips/", func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(`[]`))
})
mux.HandleFunc("/api/dailysummary/", func(w http.ResponseWriter, r *http.Request) {
dailyPath = r.URL.Path
_, _ = w.Write([]byte(`[]`))
})
srv := httptest.NewServer(mux)
defer srv.Close()
c := dshield.NewClient(dshield.ClientConfig{BaseURL: srv.URL, TopN: 5})
_, _ = c.FetchAll(context.Background())
yesterday := time.Now().UTC().AddDate(0, 0, -1).Format("2006-01-02")
require.Contains(t, topPortsPath, "/records/5/"+yesterday)
require.Contains(t, dailyPath, "/dailysummary/"+yesterday)
}

View File

@ -0,0 +1 @@
[{"date":"2026-05-01","records":17196921,"sources":146555,"targets":871}]

View File

@ -0,0 +1 @@
[{"rank":1,"source":"13.94.254.200","reports":319739,"targets":1},{"rank":2,"source":"194.224.249.214","reports":309508,"targets":1},{"rank":3,"source":"52.157.207.201","reports":194780,"targets":1},{"rank":4,"source":"89.248.163.109","reports":96279,"targets":150},{"rank":5,"source":"143.244.186.35","reports":92007,"targets":8},{"rank":6,"source":"18.116.198.191","reports":59907,"targets":1},{"rank":7,"source":"152.89.198.163","reports":41378,"targets":478},{"rank":8,"source":"152.89.198.103","reports":41346,"targets":479},{"rank":9,"source":"152.89.198.187","reports":41332,"targets":477},{"rank":10,"source":"185.122.204.71","reports":41221,"targets":479}]

View File

@ -0,0 +1 @@
{"0":{"rank":1,"targetport":23,"records":5756817,"targets":316,"sources":52950},"1":{"rank":2,"targetport":80,"records":1151422,"targets":316,"sources":9421},"2":{"rank":3,"targetport":5555,"records":1106744,"targets":298,"sources":3668},"3":{"rank":4,"targetport":8080,"records":978049,"targets":309,"sources":7062},"4":{"rank":5,"targetport":22,"records":514078,"targets":326,"sources":9113},"5":{"rank":6,"targetport":2222,"records":371960,"targets":318,"sources":7388},"6":{"rank":7,"targetport":51223,"records":313702,"targets":15,"sources":1293},"7":{"rank":8,"targetport":8000,"records":241520,"targets":323,"sources":6078},"8":{"rank":9,"targetport":443,"records":231949,"targets":432,"sources":8308},"9":{"rank":10,"targetport":68,"records":215704,"targets":185,"sources":209},"date":"2026-05-01","limit":10}