40 Commits

Author SHA1 Message Date
a43a722e79
feat: expose refresh outcomes 2026-09-19 02:27:01 -06:00
c6da1e7e4a
feat(iplist): accept "source" as header name in addition to "network"
Some deployments used "source,comment" as the CSV header for IP list
files, but the parser only recognized "network" as a header row.
Accept both "network" and "source" (case-insensitive) to avoid
treating the header as a (non-existent) domain entry.
2026-09-18 22:59:45 -06:00
04aaadff75
fix(ippolicy): clear to empty evaluator 2026-09-10 02:35:57 -06:00
4b8425c1ac
fix(ippolicy): return empty evaluator on load failure 2026-09-10 02:23:14 -06:00
9a7a0f523c
chore: refresh downstream release checksums 2026-09-10 02:14:23 -06:00
ef11c70896
chore: use ippolicy v0.10.0 2026-09-10 02:13:21 -06:00
177c862169
chore(net/ippolicy): use released iplist tag 2026-09-10 02:12:16 -06:00
db2dbbc29c
chore: update stacked module releases 2026-09-10 02:09:48 -06:00
61d068c95c
refactor(ippolicy): separate policy loading from evaluation 2026-09-10 02:09:17 -06:00
db1d4c55a9
refactor(sync): rename cacheable package 2026-09-10 02:08:36 -06:00
5221e0e1c3
chore(net/formmailer): update stacked release dependencies 2026-09-10 00:46:23 -06:00
0e6faecf6c
refactor(net/formmailer): rebase release onto main 2026-09-10 00:45:56 -06:00
be816d8da0
chore(net/ippolicy): use released iplist tag 2026-09-10 00:45:12 -06:00
27ba97de6d
refactor(net/ippolicy): publish shared cache policy 2026-09-10 00:41:40 -06:00
48a4f2a145
refactor(net/iplist): use released cache APIs 2026-09-10 00:21:39 -06:00
87ab75126a
refactor(net/geoip): use released cache APIs 2026-09-09 23:35:39 -06:00
fac7ffad84
refactor(net/httpcache): finalize release API 2026-09-09 23:34:52 -06:00
21cb0bc677
refactor(net/gitshallow): finalize release API 2026-09-09 23:34:39 -06:00
9d2f2cb402
refactor(net/dnsresolver): finalize release API 2026-09-09 22:29:00 -06:00
b67ea17776 chore(net/ipcohort): whitespace 2026-09-09 03:20:28 -06:00
07d4a2652c
feat!(ippolicy): share refreshed IP policy behavior
Rename net/ipgate to net/ippolicy and compose independently refreshed
whitelist and blacklist sources into one IP decision policy.

- rename slog group and error prefixes from ipgate to ippolicy
- delete unused ParseDomainSet and RecordReader from domainsetcsv.go
- rewrite README to match the actual Config API
- document nil-whitelist behavior on New()
- bump iplist dependency to v0.8.5
- go mod tidy: remove stale ipgate and iplist entries
2026-09-09 02:55:09 -06:00
869086ce0c
feat(iplist): add refreshable Source 2026-09-09 02:54:34 -06:00
1cb0235d9d
feat(ipgate): configure matcher refresh intervals 2026-09-09 02:29:23 -06:00
03722a2934
feat(iplist): allow custom HTTP clients 2026-09-09 01:14:05 -06:00
c41ac3732d
feat(iplist): load and validate IP policy entries from TSV/CSV sources
Add net/iplist for shared IP-policy source loading.

- Accept local TSV/CSV file paths or HTTP(S) URLs (including Google Sheets).
- Auto-detect TSV vs CSV format from file content.
- Send Accept: text/tab-separated-values header for content negotiation.
- Parse the first column and ignore comments (#) and optional "network" header.
- Expand nested list URLs up to MaxNestedSources (2) levels.
- Support Basic Auth from URL userinfo without sending credentials in the URL.
- Persist cached URL responses across restarts via httpcache.
- Refresh periodically and preserve the known-good cache on fetch or parse failure.
- Validate entries as IP address, CIDR range, or domain name (Validate()).
- Use golib/https.NewInternalClient() through httpcache.NewWith.
- Add README with usage examples and entry format documentation.
2026-09-08 22:46:17 -06:00
bb488fe230 fix(net/ipgate): prevent nil cohort by construction, extract cachedCohortValid
Ensure cohort is never nil:
- NewDomainSet stores an empty Cohort before rebuildCohort, so a parse
  failure leaves an empty (non-nil) cohort instead of nil
- Contains uses CompareAndSwap to lazily self-initialise a zero-value
  struct, making nil impossible after first access — no explicit nil
  check needed

Extract the cache-validity check into a testable cachedCohortValid()
helper and add filesPresent() to detect when data files exist on disk.

Use strings.Builder in commaify() for efficiency.

Tests:
- TestPrefixSetContainsZeroValue: zero-value self-heals via CAS,
  cohort is non-nil after first Contains call
- TestCachedCohortValidRequiresFiles: cache invalidation logic
2026-09-08 20:36:43 -06:00
70d95be546 test(net/gitshallow): support modern git orphan branches 2026-09-08 20:16:37 -06:00
4260287467 chore(net/gitshallow): add .goreleaser.yaml 2026-09-08 20:16:37 -06:00
f7ca1d22a5
feat(geoip-update): align CLI flags with conventions 2026-09-08 15:19:38 -06:00
e82e3979d2
fix(geoip): update httpcache dependency 2026-09-08 14:47:28 -06:00
05505c3554
fix(geoip-update): back off failed downloads 2026-09-08 14:44:48 -06:00
71f7752015
fix(httpcache): back off after failed fetches 2026-09-08 14:44:48 -06:00
584061744b
feat(geoip-update): CLI for downloading GeoLite2 databases via conditional HTTP GET
- Downloads all editions listed in a GeoIP.conf
  - Flags: -config, -dir, -fresh-days, -base-url (override
    DownloadBase for custom mirror/proxy), -V/--version, -help
  - Version output shows name, version, commit, date, copyright and
    license (populated via ldflags at build time)
  - Uses Basic auth with split credential encoding for readability
  - ETag/Last-Modified caching via httpcache
2026-05-26 22:04:22 -06:00
2f33b5e430
feat(net/geoip): MaxMind GeoLite2 reader with auto-update
Add a Go package for reading MaxMind GeoLite2 City and ASN databases.

  - ParseConf: parses geoipupdate-style config files (AccountID,
    LicenseKey, EditionIDs, DatabaseDirectory)
  - Databases: holds open City + ASN readers; Open() loads both
    editions from a directory, extracting .mmdb from tar.gz in memory
    without writing to disk
  - FindTarGz: resolves cache paths, preferring *_LATEST.tar.gz
  - Lookup: returns city, region, country, ASN, and AS org for an IP
  - DownloadBase constant, TarGzName helper, DefaultConfPaths and
    DefaultCacheDir conveniences

Integration tests verify download and conditional-get freshness.
2026-05-26 22:04:17 -06:00
04b8bb406d
feat(net/httpcache): ETag/Last-Modified HTTP cache
Conditional GET cache for periodic data sources. Persists ETag and
Last-Modified to a .meta sidecar so 304s survive process restarts.
Two independent rate-limit gates (MaxAge file-mtime, MinInterval
in-memory). Body cap via MaxBytes defends against fill-disk from a
hostile upstream or redirect target. Authorization (and any
caller-supplied) headers stripped on every redirect hop. Userinfo
in URL is redacted from error messages.
2026-05-26 01:27:20 -06:00
dddfdd392c
feat(net/ipgate): IP allowlist/blocklist via PrefixSet and DomainSet
PrefixSet: git-backed CIDR files via gitshallow, refreshed every 47 min.
DomainSet: takes pre-parsed (staticPrefixes, domains []string); resolves
hostnames every 5 min, retaining stale IPs on failure. Both use
atomic-swapped ipcohort.Cohort for lock-free reads.

ParseDomainSet(RecordReader) parses rows into prefixes and hostnames;
caller constructs the record reader (e.g. csv.Reader with delimiter,
comment char). RecordReader interface is satisfied by *csv.Reader.

commaify uses plain string concat (integers max ~7 groups).
2026-05-22 12:23:35 -04:00
d472c00ef1
feat(net/dnsresolver): parallel DNS resolver with fallback servers
Resolves A, CNAME chain, and SRV records via miekg/dns. Queries all
configured servers in parallel and returns the first success. Falls
back to OpenDNS/Cloudflare/Quad9 when /etc/resolv.conf is absent or empty.
2026-05-22 11:16:22 -04:00
1db21be047
fix(gitshallow): depth-3 windowed fetch with low-memory GC
- Default depth changed from 1 to 3: enough overlap for delta
  compression even if the remote pushes 2 commits between fetches
- Keep --depth on fetch (not just clone) so shallow stays windowed
- GCInterval default changed: 0 now means every fetch (was disabled)
- GC sequence: reflog expire → prune → gc --prune=now
  Expire makes old shallow entries' objects unreachable, prune removes
  them cheaply (5MB RSS), then gc delta-compresses what remains (~10MB)
- Only GC when HEAD actually changed (skip no-op fetches)
- Dropped GCAggressive (no benefit in shallow clones)
2026-05-11 23:45:10 -06:00
1dacbdd8f7
feat(net/gitshallow): incremental shallow git-repo mirroring
Periodically-updated shallow clone for repos used as a data source.
One Fetch verb (clone-or-pull-or-skip) drives both the Fetcher
interface and per-File handles. MaxAge gates on FETCH_HEAD mtime so
short-lived CLI invocations don't hammer the remote across process
restarts; an in-memory 1s debounce coalesces concurrent Fetches.
Hardens git invocations against argument injection: validates
branch/ref shape, rejects URLs starting with '-', and uses '--'
separators before positional args. Userinfo in URL is redacted from
error output (both args echo and git's own messages).
2026-05-02 16:15:54 -06:00
bea4c3de0a
feat(net/ipcohort): IP block/allow cohort filter
IPv4 host + CIDR membership testing backed by sorted /32 binary search
and a linear scan of CIDR ranges. CSV/text loader with bounded reads
(maxBytes is required so callers pick a limit appropriate to their
input source — 1 KB allowlists through 100+ MB threat feeds). Returns
errors for unparseable IPs so callers can decide fail-open vs
fail-closed; partial cohorts are returned alongside parse errors.
2026-05-02 15:41:38 -06:00