checker: add honeypot-path collector and rules
All checks were successful
continuous-integration/drone/push Build is passing

Probes 20 known-bad paths (/.env, /.git/config, /actuator/env, etc.)
that CT-log scanners hit immediately after a new certificate is issued.
Critical credential/source-leak paths raise StatusCrit; other exposed
paths raise StatusWarn; 401/403 responses raise StatusInfo.

Fixes: #1
This commit is contained in:
nemunaire 2026-06-13 16:01:22 +09:00
commit 086d3e151d
5 changed files with 397 additions and 46 deletions

View file

@ -6,6 +6,11 @@ package checker
import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
"net/url"
"time"
)
@ -34,3 +39,54 @@ type Collector interface {
Key() string
Collect(ctx context.Context, t Target) (any, error)
}
// PathProbe is the common result of a single HTTPS path probe. It is
// embedded by collector-specific probe types that may add extra fields
// (e.g. HoneypotProbe adds Critical).
type PathProbe struct {
URL string `json:"url"`
StatusCode int `json:"status_code,omitempty"`
Bytes int `json:"bytes,omitempty"`
Error string `json:"error,omitempty"`
}
// fetchHTTPSPath issues a single GET against the given path using client,
// reads up to limit bytes (just to measure size), and returns a PathProbe.
func fetchHTTPSPath(ctx context.Context, client *http.Client, host, path, ua string, limit int64) PathProbe {
u := (&url.URL{Scheme: "https", Host: host, Path: path}).String()
probe := PathProbe{URL: u}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
probe.Error = err.Error()
return probe
}
req.Header.Set("User-Agent", ua)
resp, err := client.Do(req)
if err != nil {
probe.Error = err.Error()
return probe
}
defer resp.Body.Close()
probe.StatusCode = resp.StatusCode
n, _ := io.Copy(io.Discard, io.LimitReader(resp.Body, limit))
probe.Bytes = int(n)
return probe
}
// newPinnedHTTPSTransport returns an http.Transport that dials every request
// to ip:443 and presents host as the TLS ServerName. The caller must defer
// the returned cleanup func to drain idle connections.
func newPinnedHTTPSTransport(ip, host string, timeout time.Duration) (*http.Transport, func()) {
addr := net.JoinHostPort(ip, "443")
dialer := &net.Dialer{Timeout: timeout}
t := &http.Transport{
DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, network, addr)
},
TLSClientConfig: &tls.Config{ServerName: host},
TLSHandshakeTimeout: timeout,
ResponseHeaderTimeout: timeout,
DisableKeepAlives: true,
}
return t, t.CloseIdleConnections
}