Release v2.1.x: Penguin-DAL migration, centralized libs, and test suite overhaul#46
Release v2.1.x: Penguin-DAL migration, centralized libs, and test suite overhaul#46PenguinzTech wants to merge 112 commits into
Conversation
Version files added (all at v1.0.0):
- dns-client-go/.version - Go DNS client
- dns-server/.version - DNS server
- dns-client/.version - Python DNS client (docker-client)
- dns-server/flask_app/.version - Web console (webui)
Workflow updates:
- Go client workflow now uses dns-client-go/.version
- Build triggers only when component's .version changes
- Added Windows ARM64 to build targets (6 platforms total)
- Release tags now use format: v{VERSION}-go-client
Release notes reorganized with sections for each package:
- go-client, dns-server, docker-client, webui
- Each with independent versioning and changelogs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add .version file monitoring to all build workflows - Implement epoch64 timestamp-based naming (alpha/beta-<epoch64>) - Add version-based release naming (vX.X.X-alpha/beta) - Add auto pre-release creation on .version changes - Add security scanning (gosec for Go, bandit for Python, npm audit for Node.js) - Create comprehensive docs/WORKFLOWS.md - Update docs/STANDARDS.md with CI/CD section - Update CLAUDE.md with CI/CD section and pre-commit checklist 🤖 Generated with Claude Code Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Backend: - Add DHCP models (pools, leases, reservations, servers) with PyDAL - Add Time models (servers, sync logs, clients, config) with PTP/NTP support - Add full REST API for DHCP pool management and lease tracking - Add full REST API for time server management and sync operations - Fix RBAC middleware: add check_team_access function - Fix team model: remove unsupported PyDAL unique constraint Frontend: - Add TypeScript types for DHCP and Time services - Add DHCPPools management component with utilization visualization - Add TimeServers management component with PTP/NTP protocol selection - Integrate new tabs into Management page Go Client: - Add NTP client package with automatic server failover - Add NTP forwarder to intercept OS time requests (port 123) - Add time configuration with environment variable support - Add CLI commands: time query, time forward, time status - Add comprehensive unit and integration tests Documentation: - Update API.md with DHCP and Time API endpoints - Update ARCHITECTURE.md with network services diagrams - Update STANDARDS.md with service-specific guidelines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Restructure from server-side templates to a three-container architecture: - Flask backend serves JSON-only REST API with JWT + Flask-Login auth - React WebUI (Vite + TypeScript + Tailwind) as separate nginx container - DNS server unchanged Flask changes: add Flask-JWT-Extended, Flask-CORS, Flask-Limiter; convert all dashboard routes to /api/v1/ JSON endpoints; add safe PyDAL row serialization (exact type checks to exclude RecordUpdater/LazySet); enforce auth on all API endpoints. Docker: add Dockerfile.api for Flask, dns-webui service with nginx reverse proxy, update docker-compose.yml with new services. Tests: add comprehensive smoke test suite (130 tests) covering container health, API endpoints, JWT auth flows, pagination, and response formats. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes Dependabot alerts #33 and #34: - CVE: Next.js HTTP request deserialization DoS (high) - CVE: Next.js Image Optimizer DoS (medium) Also fixes js-yaml prototype pollution via npm audit fix. Remaining 2 moderate alerts (eslint stack overflow, Next.js PPR memory) require breaking major version upgrades and have no stable patches. quic-go alerts (#28-30) are already resolved on this branch (v0.59.0 includes all patches). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The root Dockerfile's dns-server and dns-client stages both COPY docs/ into the image. The .dockerignore was excluding the docs directory, which would cause the CI docker-multi-build job to fail. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit addresses all identified security vulnerabilities across Python, Node.js, and Go components, and adds extensive smoke test coverage for both alpha (local) and beta (K8s) environments to ensure production readiness. Security Fixes: - Python: Update cryptography (41→44), PyMySQL (1.1.0→1.1.1), Pillow (10→11), aiohttp (3.9→3.11.11), add defusedxml for XXE prevention - Node.js: Update react-router-dom (6→7), eslint (8→9), vite (5→7), next (15→16) to fix XSS, DoS, and stack overflow vulnerabilities - Go: Fix integer overflow in NTP time handling, replace math/rand with crypto/rand, enforce TLS 1.2+ minimum, update quic-go to v0.59.0 New Features: - Add version management script (scripts/version/update-version.sh) supporting major/minor/patch/build version increments - Add 150 mock tests for isolated component testing (DNS client/server, Manager API, database operations) - Add 79 comprehensive smoke tests covering security, edge cases, integration, and user experience for alpha and beta environments Test Coverage: - Security: 32 tests (auth, authorization, input validation, injection prevention, CORS, TLS, data isolation) - Edge Cases: 37 tests (boundaries, special chars, concurrency, large datasets, error recovery) - Integration: 39 tests (component communication, data flow, K8s integration, monitoring) - User Experience: 24 tests (page loads, forms, navigation, error messages) - Mock Tests: 150 tests (fast, isolated, no network/database dependencies) Version: Bump to v2.1.1.1770072428 (patch increment for security fixes) Resolves: Critical PyMySQL SQL injection, XSS in react-router, integer overflow in Go NTP client, weak RNG in HTTP backoff, TLS misconfiguration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Install shared/react_libs dependencies before webui build to resolve zod module import error during vite build. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update dal2-beta cluster deployment to use the pre-existing penguintech-wildcard-tls certificate from cert-manager instead of generating a new certificate. Changes: - Create comprehensive K8s manifests in k8s/manifests/ - Configure ingress for squawk.penguintech.io with proper TLS - Use pre-existing Cloudflare wildcard certificate (penguintech-wildcard-tls) - Deploy DNS server, Flask API, WebUI, and Valkey services - Add health checks, resource limits, and HA configuration (2 replicas) - Configure proper routing: / → webui, /api/v1 → flask-api, /dns-query → dns-server Ingress Configuration: - Host: squawk.penguintech.io - TLS Secret: penguintech-wildcard-tls (Cloudflare wildcard cert) - SSL redirect enabled - NGINX ingress class Deployed to: dal2-beta cluster, squawk namespace Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove restrictive security contexts and add emptyDir volumes for directories that need write access (database logs, nginx cache). Fixes: - flask-api: Health check path /api/v1/health → /health - flask-api: Add volume mount for /app/flask_app/databases - webui: Add volume mounts for nginx cache and run directories - All: Remove runAsUser/runAsNonRoot to allow containers to start properly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change @penguin/react_libs reference from local file path to GitHub repository to ensure consistent versioning across all PenguinTech projects. - Update package.json to reference github:penguintechinc/penguin-libs#main - Install @penguintechinc/react-libs from main branch - Remove dependency on local shared/react_libs copy Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace local copies of shared libraries with git-referenced dependencies from github.com/penguintechinc/penguin-libs. This ensures consistent versioning across all PenguinTech projects. - Remove entire shared/ directory (react_libs, node_libs, go_libs, py_libs, licensing, database) including duplicate shared/shared/ nested copy - Update Dockerfile to remove COPY shared/react_libs build steps - Update tailwind.config.js content path to node_modules dist - Update all imports from @penguin/react_libs to @penguintechinc/react-libs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: upgrade GitHub Actions + add missing e2e/smoke test coverage Actions upgrades: - actions/checkout v5→v6, actions/cache v4→v5, actions/setup-go v5→v6 - docker/setup-qemu-action v3→v4, docker/setup-buildx-action v3→v4 - docker/build-push-action v6→v7, docker/login-action v3→v4 - docker/metadata-action → v6, linear-b/gitstream-github-action → v2 Test coverage gaps closed (per standards audit): - /ready readiness endpoint tests in DNS server and web console smoke suites - API filter/pagination param tests: /api/v1/queries (page, limit, empty page), /api/v1/domains (active filter), /api/v1/logs, /api/v1/ioc/feeds invalid-ID 404 - Playwright: one-time auth fixture + protected-route page-load tests for all 7 web console routes (index, tokens, domains, permissions, blacklist, certs, logs) - Playwright: form modal validation-error tests (new token, new domain) - CI cleanup step (if: always()) for /tmp/playwright-squawk artifacts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: migrate ingress to Cilium Gateway API HTTPRoute Replace nginx Ingress with Gateway API HTTPRoute targeting the shared Gateway in the gateway namespace. Part of dal2-beta nginx-ingress EOL. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: pin gosec to v2.22.11 (last Go 1.24-compatible version) gosec@latest resolves to v2.25.0 which requires Go >= 1.25.0. Pin to v2.22.11 (go 1.24.0 minimum) to match our Go 1.24.2 toolchain. Also fixes the @latest ban from dependency pinning standards. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add k8s/kustomize/base/ with DNS server, DNS client (Go), Valkey - Add k8s/kustomize/overlays/beta/ patching CI images and beta resource limits - DNS client bridges traditional UDP/TCP port 53 → squawk DoH (port 5300 internal) - CoreDNS forward patch ready at k8s/coredns-external-patch.yaml (not applied yet) - Extend push.yml: add build-dns-client + merge-dns-client jobs so squawk-dns-client gets a beta image on every branch push alongside the main squawk image Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… overlays
- Rename all kustomize base/overlay files from .yaml to .yml
- Add alpha overlay with local registry (localhost:32000)
- Create complete Helm chart: Chart.yaml, values-{base,beta,prod}.yml
- Add 7 Helm templates: namespace, dns-server, dns-client, valkey deployments/services
- Rename coredns-external-patch.yaml to .yml
- Support alpha (Kustomize), beta/prod (Helm) deployments
Adds a new Go service (squawk-k8s-dns) that watches the Kubernetes API and serves cluster.local DNS queries directly from a local zone cache, enabling eventual full CoreDNS replacement. Key changes: - pkg/zonecache: thread-safe in-memory zone cache (sync.RWMutex) - pkg/k8swatcher: SharedIndexInformer watches Services + Endpoints; synthesizes A, AAAA, SRV, and pod records; headless vs ClusterIP aware - pkg/resolver: dns.Handler routing cluster.local/in-addr.arpa to cache, all other queries forwarded to DoH upstream - pkg/metrics: Prometheus metrics registry (OTel-bridge ready) covering query duration, cache hit/miss, upstream latency, watcher events - pkg/forwarder: instrumented with Prometheus metrics - cmd/squawk-k8s-dns: Cobra CLI serving UDP+TCP DNS on :5300 with /healthz, /readyz, /metrics on :8081 - Dockerfile.k8s-dns: multi-stage bookworm build, rootless UID 1000 - CI: build-k8s-dns + merge-k8s-dns jobs in push.yml - K8s: Deployment, Service (53->5300), RBAC in Helm + Kustomize - Helm: fix Helm 4.1.4 .helmignore negation bug (Chart.yaml missing error) - Helm: rewrite NOTES.txt + nil-safe _helpers.tpl for Helm 4 strict mode Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(security): close auth bypass, remove default creds & backdoor tokens
Enterprise-readiness security hardening (Wave 2):
- CRITICAL: dns-server app/ verified JWT signatures on zone-authorization
paths (selective_router, resilience) instead of verify_signature=False;
fail-closed when JWT_SECRET_KEY unset. Adds regression tests.
- manager: no default SECRET_KEY/JWT_SECRET_KEY; ProductionConfig fails
fast if unset; default config -> ProductionConfig; CORS allowlist
(drop wildcard+credentials); admin seed requires SQUAWK_ADMIN_PASSWORD.
- bins (live server): strict Bearer parsing; import-time admin123 seed
removed from flask_app.
- compose: all secret/password defaults now required (${VAR:?}).
- scripts/init-postgres.sql: backdoor seed tokens/wildcard grant removed
(moved to unmounted scripts/dev-seed.sql).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: remove dead py4web/legacy code; add app/ to CI coverage
- Delete unreferenced stacks confirmed dead by parity audit: web/ py4web
console, bins/py4web_extended_app.py (also removes an unauthenticated
/metrics endpoint), bins/server_optimized.py, start_console.sh, and the
web-console service in dns-server/docker-compose.yml.
- CI: add --cov=dns-server/app to build/release workflows; add
non-blocking acceptance-future job for tests_full_future/ (migration
parity gate); add dns-server/app codecov flag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: untrack committed virtualenvs and sqlite .table files
Remove vendored venv/ trees and *.table SQLite artifacts from version
control (kept on disk) and add .gitignore rules so they stay out.
These should never have been tracked; isolated in its own commit to
keep the security diff reviewable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(manager): add IOC ingestion schema (ioc_entry, ioc_override) + migration
Wave 3 / IOC migration Chunk A — database layer:
- Extend ioc_feed with format, enabled, parser_config, authentication,
entry_count, last_success columns.
- Add ioc_entry (indicators + threat metadata + MISP refs, indexed) and
ioc_override (token-scoped allow/block, expiry) tables.
- Alembic 002_ioc_entries chained off 001_initial (reversible).
- Reconcile the duplicate pydal ioc_feed def in models/config.py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(manager): IOCIngestionService with feed parsing, overrides, matching
Wave 3 / IOC migration Chunk B — the ingestion service (parity gate green:
27/27 tests_full_future/test_ioc_manager.py pass).
- New app/services/ioc_ingestion_service.py (IOCManager): txt/csv/json/xml
feed parsing, per-row typing for "mixed" feeds, domain wildcard + CIDR IP
matching, per-token allow/block overrides with expiry, feed scheduling
(update_frequency_hours), stats, cleanup_old_indicators, default feeds.
Runtime DB via penguin-dal (reflects Chunk A schema); @DataClass(slots=True).
- Cache rebuilt after every mutation (ingest/update-all/override/cleanup).
- Failed feed fetches no longer counted as success; malformed content
returns success=False.
- conftest: wire manager services path + temp DB schema (SQLAlchemy) for the
acceptance suite. Add aiohttp dependency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(ioc): wire ingestion into config-sync + resolver (end-to-end blocking)
Wave 3 / IOC migration Chunk C+D — make IOC blocking actually work:
manager:
- config_service.get_active_ioc_feeds() now returns feeds with normalized
indicator `entries` under config key `ioc_feeds`; get_config_version()
changes when ioc_entry data changes (triggers dns-server resync).
- blueprints/ioc_feeds.py POST /<id>/sync now ingests via IOCManager
(was a no-op stub). IOCManager wired into app factory as app.ioc_manager.
dns-server (app/):
- Fix dead-path key bug: iocFeeds -> ioc_feeds (3 sites in main.py).
- ioc_checker: wildcard-domain + CIDR/IP matching; loads the feed contract.
- main.py: block resolved A/AAAA answers whose IP is in an IOC feed.
Tests: dns-server test_ioc_checker 21, manager test_config_ioc_sync 5,
acceptance test_ioc_manager 27 — all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(security): SSRF guard on IOC feed fetching (both fetch sites)
Addresses automated security review finding (HIGH SSRF) on feed fetching:
- Add _assert_feed_url_safe(): rejects non-http(s) schemes and hosts that
resolve to private/loopback/link-local/reserved/multicast/unspecified
addresses (incl. cloud metadata 169.254.169.254). IOC_ALLOW_PRIVATE_FEEDS
env override for on-prem internal feeds.
- Call the guard before fetching in BOTH sites: blueprints/ioc_feeds.py
/sync endpoint and ioc_ingestion_service._update_single_feed
(scheduled update_all_feeds).
- allow_redirects=False on both fetches (an allowed host could 302 to an
internal target).
- Dedicated tests (test_ioc_ssrf.py, 10) + hermetic autouse fixture so the
acceptance suite doesn't depend on live DNS. Acceptance gate stays 27/27.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(ioc): scheduled feed refresh via supercronic
Wave 3 / IOC migration Chunk E — auto-refresh (feeds previously updated
only on manual /sync):
- app/jobs/ioc_refresh.py: CLI (`python3 -m app.jobs.ioc_refresh`) running
IOCManager.update_all_feeds(); structured stdout summary; non-zero exit on
failure; PostHog gating hook stubbed. Tests: test_ioc_refresh_job (5).
- manager/backend/crontab + Dockerfile: supercronic (org standard, non-root)
so the same image runs gunicorn (default) or supercronic (scheduler) via
CMD override, every 30 min.
- K8s: manager Deployment + manager-scheduler Deployment (Helm + Kustomize
base; overlays validate). securityContext hardened; DB_URL via Secret;
scheduler single-replica to avoid concurrent runs.
NOTE: supercronic image digest not pinned (sandbox lacks ghcr.io access);
documented TODO with fetch command in Dockerfile — tracked under Wave 6
external-image digest pinning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(ioc): PostHog feature-flag + license gating
Wave 3 / IOC migration Chunk F — gate the feature per org rules:
- New app/services/posthog_client.py: feature_enabled() with graceful
degradation (last-known cache, never raises), wired as app.posthog.
New flags default OFF.
- IOC ingestion gated behind PostHog flag 'squawkdns.ioc-ingestion':
refresh job skips when disabled; blueprint write/sync endpoints 403.
- Enterprise feed formats (taxii/misp/stix/openioc) gated behind license
feature 'ioc_advanced_feeds' (402 unlicensed); community formats
(txt/csv/json/xml) always allowed. Order: flag first, license second.
- POSTHOG_* config (optional; unset -> flags fall back to default).
Ships DARK: IOC stays disabled until 'squawkdns.ioc-ingestion' is enabled.
Tests: test_ioc_gating (16) + regressions; 63 IOC tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(manager): add WHOIS cache schema + Alembic 003
Wave 3 / WHOIS migration W-A: whois_cache, whois_search_index,
whois_query_log SQLAlchemy tables + Alembic 003_whois_cache chained off
002_ioc_entries. Foundation for the WHOIS service.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(manager): WHOISManager service (domain/IP lookup, cache, RDAP fallback)
Wave 3 / WHOIS migration W-B — service to the acceptance spec (19/19
test_whois_manager.py green):
- app/services/whois_service.py: async lookup_domain/lookup_ip (flat result
shape), penguin-dal cache in whois_cache, RDAP -> legacy whois fallback,
search_whois, get_stats, cleanup_old_data(retention_days=), _is_valid_domain/
_is_valid_ip, asyncio rate limiting + same-domain dedup. Runtime DB via
penguin-dal (reflects W-A tables; no self-migration).
- whois_manager.py alias for the acceptance import path.
- conftest temp_db creates whois tables. Pin python-whois==0.9.6, ipwhois==1.3.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(whois): API blueprint, PostHog gating, scheduled cleanup
Wave 3 / WHOIS migration W-C — complete the feature:
- app/blueprints/whois.py: GET /api/v1/whois/{domain/<d>,ip/<ip>,search,stats},
token-required, gated behind PostHog flag 'squawkdns.whois-lookup' (403 when
off); sanitized errors.
- app/jobs/whois_cleanup.py: daily cleanup CLI, flag-gated (skips when off).
- app factory wires app.whois_manager + registers whois_bp.
- crontab: daily whois_cleanup alongside ioc_refresh (supercronic).
WHOIS ships DARK (flag default OFF); community-entitled (flag-gated, not
enterprise-locked). Full IOC+WHOIS suite: 91 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(manager): migrate client-config generation + fix cross-tenant IDOR
Wave 3 / client-config migration (23/23 acceptance green):
- schema: deployment_domain, client_config, config_role, config_user_role,
client_instance, config_history (+ Alembic 004). Service
app/services/client_config_service.py on penguin-dal; blueprint
app/blueprints/client_config.py gated by PostHog flag squawkdns.client-config.
- SECURITY (cross-tenant IDOR, from security review): register_client now
scopes client_instance lookup by domain_id and rejects a client_id already
registered under another domain; assign_config_to_client refuses a config
whose domain_id differs from the client's. Regression test
test_client_config_tenant.py (2).
- Test maintenance for intended changes: test_config default->ProductionConfig,
test_schema expected table set incl. ioc/whois/client_config tables,
test_app limiter check tolerant of mocked penguin_limiter. conftest temp_db
creates config tables + resilient teardown. .gitignore: *.db artifacts.
Full manager+acceptance suite: 144 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(dns-server): migrate Prometheus metrics into app/services (33/33)
Wave 3 / prometheus-metrics migration:
- Real prometheus_client-based metrics (counters/histograms/labels, 0.0.4
exposition) now live in the SURVIVING tree: dns-server/app/services/
prometheus_metrics.py. app/main.py + grpc_server.py repointed off the
hand-rolled MetricsReporter to init_prometheus_metrics()/record_query();
/metrics serves the real exposition. Pinned prometheus-client==0.21.1.
- Import correctness: app/main.py imports from app.services.prometheus_metrics
(works under `python -m app.main` with bins/ absent), NOT the legacy bins
copy. tests_full_future/conftest adds app/services to sys.path so the
acceptance test resolves to the surviving module (not bins, which is slated
for deletion). Left bins/prometheus_metrics.py unchanged (removed in Wave 5).
Acceptance: 33/33 (dns-server venv). penguin_dal group unaffected: 71/71.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(k8s): add default-deny NetworkPolicies (Helm + Kustomize)
Wave 6 hardening — the audit found ZERO NetworkPolicies. Adds a default-deny
(ingress+egress) baseline for the squawk namespace plus scoped allow rules,
wired into BOTH deploy methods (Kustomize base -> all overlays; Helm template
gated by networkPolicy.enabled=true):
- default-deny-all + allow-same-namespace + allow-egress-kube-dns.
- dns-server/dns-client: public ingress 53 udp/tcp + DoH 8080; upstream DNS egress.
- manager + manager-scheduler: ingress 5000/50051 same-namespace only; egress
443 (+53) for IOC feed fetch, license.penguintech.io, PostHog; egress 5432 to
external Postgres.
- valkey(6379)/postgres(5432): ingress from same-namespace app pods only.
Selectors use app.kubernetes.io/name+component (consistent across both deploy
methods). Renders: kustomize alpha/beta = 11 NetworkPolicy each; helm = 12
(adds allow-ingress-k8s-dns). helm lint clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(dns-server): migrate selective DNS routing to penguin-dal (25/25)
Wave 3 / selective-dns-routing migration (final acceptance-tested feature):
- Reimplemented dns-server/app/services/selective_dns_routing.py on penguin-dal
(was raw pydal — standards violation): SelectiveDNSRouter with group-based
zone visibility (public/internal/restricted/private), wildcard + domain-
hierarchy matching, strict token->user resolution (unknown token -> None).
- Tables added to manager schema (dns_group, user_group_assignment,
dns_routing_zone, group_zone_access) + Alembic 005; dedicated dns_routing_zone
name avoids clobbering existing dns_zone. conftest temp_db creates them.
- Removed an unacceptable hack the migration introduced (hardcoded
"test-token-123" seeded from production assign_user_to_group). The canonical
test principal is now seeded in the test's dns_router fixture instead.
- sample_token_data now returns the schema `token` id (what services read),
making token_id assertions order-robust. test_schema expected set updated.
- Existing selective_router.py (JWT-verify fix) untouched; no verify_signature
False on any authz path.
Acceptance: selective 25/25; penguin_dal group 96; prometheus 33; manager unit 73.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(dns-server): mTLS certificate lifecycle (penguin-dal, 39 tests)
Wave 3 / build-with-authored-tests (user-requested):
- New dns-server/app/services/cert_manager.py — CertManager mTLS lifecycle:
self-signed CA create/load, server cert issuance (SAN), client cert
issue/sign with CN identity, verification against CA (chain + validity),
expiry detection, rotation, and revocation (CRL-ish). penguin-dal for all
runtime persistence (SQLAlchemy is schema-only). Gated by PostHog flag
squawkdns.mtls (default OFF, graceful degradation). cryptography==44.0.0.
- Schema: mtls_certificate + mtls_revocation tables + Alembic 006 (chained
after 005). conftest temp_db + test_schema expected set updated.
- Authored suite dns-server/tests_full_future/test_cert_manager.py: 39 tests
(CA, server/client issuance, verify valid/wrong-CA/expired/tampered,
revocation, rotation, expiry, flag-off) — run under manager venv (penguin-dal).
Regression: penguin_dal group 96; prometheus 33; manager unit green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(dns-server): HTTP/3 (QUIC) serving via hypercorn (20 tests)
Wave 3 / build-with-authored-tests (user-requested):
- New dns-server/app/services/http3_serving.py — build_serving_config() builds a
hypercorn Config that adds quic_bind (HTTP/3) alongside the TCP bind when
enabled, wiring TLS cert/key (HTTP/3 mandates TLS). Integrates CertManager to
obtain a server cert when explicit TLS_CERT_FILE/KEY are absent. Fails safe:
no TLS material -> HTTP/3 disabled, resolver still serves DoH over HTTP/2
(never QUIC without TLS). ALPN h3,h2,http/1.1 when on; h2,http/1.1 when off.
- Config: HTTP3_ENABLED (default False), QUIC_BIND (0.0.0.0:8443),
TLS_CERT_FILE/KEY. Gated by PostHog flag squawkdns.http3 (default OFF) with
graceful degradation (flag server unreachable -> TCP-only). hypercorn[h3]==0.18.0.
- app/main.py __main__ now launches via hypercorn serve() with this config
(hypercorn ASGI, not gunicorn/uvicorn).
- Authored suite test_http3_serving.py: 20 tests (disabled default, enabled+cert,
fail-safe without cert, flag precedence, CertManager integration, degradation).
Regression: prometheus 33; cert+selective 64.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* build(dns-server): repoint container run to app.main (hypercorn) + native healthcheck
Wave 4 — repoint build from the legacy bins/ stack to the canonical Quart app:
- Root Dockerfile (dns-server + production stages) and dns-server/Dockerfile now
run `python -m app.main` (PYTHONPATH=/app/dns-server) — the Quart resolver
served by hypercorn (app.main __main__ builds the hypercorn/HTTP-3 config).
No more bins/server.py / bins/server_premium_integrated.py.
- Replaced curl HEALTHCHECKs with native Python urllib.request checks against
/health (no curl/wget — org standard). Venv installs full app requirements
(quart, hypercorn[h3], penguin-dal, prometheus-client, cryptography).
- Production image builds clean (docker build --target production OK).
- Note: testing stage still runs dns-server/tests (tests_full_future acceptance
suite runs separately — needs manager schema + penguin-dal).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: repoint dns-server workflows from bins/flask_app to app/
Wave 4 — align CI with the consolidated Quart stack ahead of legacy deletion:
- build.yml + server-release.yml: drop dns-server/flask_app/tests; coverage
now --cov=dns-server/app (was bins/flask_app). In-image bandit/flake8 target
app/ (was bins/). tests_full_future acceptance stays in its non-blocking job.
- version-monitor.yml: DNS server presence probe repointed from the deleted
bins/server_optimized.py to app/main.py.
- All workflows remain SHA-pinned; YAML valid; no dangling bins/flask_app refs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: delete legacy dns-server stacks (bins/flask_app/web) — Wave 5
The Quart app (dns-server/app, run via `python -m app.main`) is the sole shipped
dns-server stack (Wave 4 repointed build/CI). Remove the now-dormant legacy trees:
- Deleted dns-server/bins/, dns-server/flask_app/ (+ its alembic/templates/tests),
dns-server/web/ (py4web console), and dns-server/Dockerfile.api (flask image).
- Deleted obsolete tests that only exercised the removed stacks: dns-server/tests
{test_dns_core, test_health_check, test_server_premium}, tests_full_future
{test_server, test_api_integration, test_authentication}, tests/test_blacklist.
- Reconciled harnesses: dns-server/tests/conftest.py schema import repointed from
flask_app to the manager schema (flask_app path removed); tests_full_future
conftest dropped dead bins/web sys.path inserts; tests/unit/conftest dropped the
flask_app path + unused mock_flask_app, and mock_admin_user no longer mutates the
shared mock_user fixture (fixes a pre-existing cross-test bleed).
- Repointed tooling: Makefile coverage/black/mypy dns-server targets bins->app;
codecov flags; scripts/deploy-beta.sh dropped the flask-api service;
update-version.sh dropped flask_app/.version; install.py + dns-client compose
console URL -> /health.
Surviving suites green: acceptance 135, in-image dns-server/tests 56, prometheus+
http3 53, manager unit 73, root unit 52. dns-client/bins untouched (still ships).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* security(dns-client): default TLS verification on; no global warning suppression
Wave 6 hardening (audit #7): dns-client no longer disables cert validation by
default. _configure_ssl now: uses a CA bundle if configured, else verify=True
(default), else verify=False ONLY under the explicit SQUAWK_VERIFY_SSL=false /
config opt-out — and logs a prominent one-time warning that TLS is disabled.
Removed the global urllib3.disable_warnings() call.
(Note: 5 pre-existing dns-client gRPC-mock/failover test failures are unrelated
to this change — confirmed failing identically on HEAD.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* build(k8s): pin external image digests; drop supercronic :latest
Wave 6 hardening (audit #8, containers):
- manager/backend/Dockerfile: supercronic FROM :latest -> :0.2.47 (mutable tag
removed). Full @sha256 digest pending — ghcr.io is unauthenticated/403 in this
environment; the resolve command is documented inline for CI/dev with ghcr access.
- k8s/manifests/valkey-deployment.yaml: valkey/valkey:8-bookworm pinned by
@sha256 digest.
- Verified other external base images (python/debian/golang/node/nginx) were
already sha256-pinned in the Dockerfiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: lint configs + git hooks + wire make targets; fix 3 bugs lint caught
Wave 6 hardening (audit #11):
- Add .flake8 (critical codes E9/F63/F7/F82 blocking), pyproject.toml (black/
isort/mypy/pytest/coverage), .golangci.yml (staticcheck/gosec/errcheck/...),
.pre-commit-config.yaml (gitleaks, flake8/black/isort, hadolint, shellcheck;
pre-push heavier scans).
- Makefile: `lint` now blocking on critical flake8/hadolint (was `|| true`;
black/isort/golangci/shellcheck advisory to avoid mass-reformatting existing
code); smoke-test -> tests/smoke, test-e2e -> tests/integration+load, test-unit
-> all unit suites; new `install-hooks`.
- Blocking flake8 immediately caught 3 real F821 undefined-name bugs (latent
NameErrors) — fixed:
* dns-server/app/grpc_server.py: response_time -> response_time_sec (prometheus
migration leftover).
* manager/backend/app/services/whois_service.py: db: DAL -> db: DB (dangling
pydal type annotation after the penguin-dal conversion).
* manager/backend/app/utils/decorators.py: handle_db_errors returned undefined
`decorator` and had a misplaced inner return -> returns decorated_function.
make lint passes; affected suites green (whois + manager unit 92).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: track .pre-commit-config.yaml (negate broad *.yaml ignore)
The repo .gitignore has a broad `*.yaml` rule that silently ignored the new
pre-commit hook config (so `make install-hooks` had no config to install).
Add a `!.pre-commit-config.yaml` negation and commit the hook config.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: add lint tooling (flake8/black/isort/mypy) to dev requirements
Ensures `make lint` + pre-commit hooks have their tools available reproducibly:
add isort to dns-server/requirements-dev.txt and create
manager/backend/requirements-dev.txt with flake8/black/isort/mypy (dev-only,
not in the runtime images). Tools installed into both project venvs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: track manager dev requirements (negate broad *.txt ignore)
The .gitignore `*.txt` blanket rule silently dropped new requirements files.
Add `!**/requirements*.txt` negation and commit manager/backend/requirements-dev.txt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: consolidate pytest config into pyproject.toml
Wave 6 (audit #14): the root pytest.ini was stale+broken — it referenced the
deleted dns-server/flask_app/tests + dns-server/bins coverage and forced a
blanket --cov-fail-under=98, and (as pytest.ini) silently overrode the clean
[tool.pytest.ini_options] in pyproject.toml. Deleted root pytest.ini and made
pyproject the canonical root config, migrating the full marker set (20 markers)
+ python_classes/functions. tests/pytest.ini kept as the scoped config for the
tests/ suite. Runs green (manager unit 73).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(dns-client): fix 5 pre-existing gRPC/failover test failures
Pre-existing failures (failed on HEAD, unrelated to the TLS hardening):
- test_query_failover: used hostnames rejected by IP validation + caught bare
Exception -> use loopback IPs + requests.exceptions.RequestException.
- 4 gRPC client tests: protobuf/grpc optional-import left stub/request classes
undefined at module scope. Client now defines safe fallbacks
(DNSQueryServiceStub/QueryRequest/BatchQueryRequest/HealthCheckRequest = None)
so the module attributes always exist; tests patch PROTOBUF/GRPC_AVAILABLE and
the request classes to exercise the gRPC path.
TLS-verify hardening preserved (verify=True default; verify=False only on opt-out).
dns-client suite: 13 passed (was 8/5).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(dns-server): make in-image test conftest schema import defensive
CI "Run unit tests on complete application image" failed: after the legacy-stack
deletion, dns-server/tests/conftest.py imported the SQLAlchemy schema from
manager/backend/app — but the dns-server container image ships dns-server/ only,
so `from schema import metadata` raised ImportError and no in-image test could
collect.
Fix: try the manager schema (repo/canonical); on ImportError fall back to an
empty MetaData (the dns-server unit tests exercise app/services logic and don't
depend on manager-owned tables). clean_db_tables now references the module-level
metadata instead of re-importing. Verified: 56 passed both in-repo (manager
present) and with manager/backend absent (image scenario).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* security(manager): bump aiohttp 3.10.0 -> 3.11.11 (GHSA-6mq8-rvhq-8wgg)
Socket.dev flagged a HIGH CVE (GHSA-6mq8-rvhq-8wgg): aiohttp's HTTP parser
auto_decompress is vulnerable to a zip bomb. manager was pinned to the
vulnerable aiohttp==3.10.0; bump to 3.11.11 (matches dns-server, clears this +
other aiohttp advisories). aiohttp is used by the IOC ingestion service/feed
fetch. Verified: manager unit 73 + IOC/WHOIS acceptance green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ci): install pytest in test image so in-image tests run
Two regressions broke the in-image pytest step in build.yml/server-release.yml:
1. dns-server/Dockerfile lacked `ARG SQUAWK_ENV`, so `--build-arg
SQUAWK_ENV=test` never reached the RUN layer (docker emitted
'UndefinedVar $SQUAWK_ENV'). $SQUAWK_ENV defaulted to production,
the dev-deps branch was skipped, and pytest was never installed ->
'No module named pytest'. Added the ARG and made the dev-install
strict (no silent '|| echo' swallow) so a broken dev requirement
fails the build instead of hiding.
2. dns-server/requirements-dev.txt had a corrupted final line
('responses>=0.23.0isort>=5.12.0') from an earlier append without a
trailing newline, which made the whole `pip install -r
requirements-dev.txt` fail. Split into two lines.
Verified: docker build --build-arg SQUAWK_ENV=test + in-image
'python3.13 -m pytest tests/' -> 56 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(security): bump aiohttp 3.11.11 -> 3.13.3 (GHSA-6mq8-rvhq-8wgg)
Socket.dev flagged a Block-level HIGH CVE on aiohttp 3.11.11: the HTTP
parser auto_decompress feature is vulnerable to a zip bomb
(GHSA-6mq8-rvhq-8wgg), affecting < 3.13.3. The prior bump (433e0bd)
cleared an older advisory but not this one. 3.13.3 is the patched
release; transitive deps are unpinned here so pip resolves them fresh
(verified aiohttp==3.13.3 resolves in python:3.13).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Reconcile the two divergent lines for the v2.1.x -> main release (PR #46). main and v2.1.x had diverged bidirectionally; this merges them into a lose-nothing UNION: Kept from main (data plane + new components): - dhcp-server/, ntp-server/ standalone protocol servers (to be hardened) - Go client pkgs dhcp, ntp (NTS), transport (HTTP/3) - website/ (marketing site) - client dir naming: dns-client -> squawk-client, dns-client-go -> squawk-client-go Kept from v2.1.x (consolidation + management plane, newer everywhere): - app/ Quart stack, manager/ mgmt-plane (DHCP/time blueprints), k8s/ Helm+kustomize - Go client pkgs time (classic NTP), k8swatcher, resolver, zonecache, metrics; cmd squawk-k8s-dns binary - newer Python client (TLS-verify hardening + gRPC test fallbacks) - all dependencies (penguin-dal, exact pins, hypercorn[h3], aiohttp 3.13.3), standards docs, lint configs Resolution: dropped committed venv/build artifacts and main's local shared/ (violates penguin-libs standard); kept bins/flask_app deleted (consolidation). Go client unified under squawk-client-go with all 15 packages (go build/vet/test pass, both binaries build). Repointed all build/CI/Makefile/pyproject/k8s references from dns-client(-go) to squawk-client(-go). Verified on merged tree: manager 73, acceptance 135, http3+prometheus 53, dns-server 56 tests pass; kustomize alpha/beta render; flake8 critical clean. NOTE: dhcp-server/ntp-server are prototype-grade and are hardened in the follow-up commits on this branch (persistence, real JWT auth, full RFC 8915 NTS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the prototype's in-memory leases and any-token auth with a production-grade DHCP-over-HTTPS service: - Persistence via penguin-dal into the manager's dhcp_pool/dhcp_reservation/ dhcp_lease tables (per-service scoped DATABASE_URL). Allocation engine stays in-server: discover honors reservations then picks a free IP, request creates a persistent lease (T1=50%/T2=87.5%), release marks released, background task expires leases. penguin-dal only — no raw SQLAlchemy/pydal at runtime. - Real JWT HS256 auth (JWT_SECRET_KEY shared with manager, fail-closed): client endpoints require scope dhcp:read, admin endpoints dhcp:admin. - Standards: @DataClass(slots=True), full type hints, PostHog flag squawkdns.dhcp-server (default OFF -> 503, graceful degradation), structlog, startup config validation, rootless Dockerfile (sha256-pinned bookworm base, native-python healthcheck). - 32 tests (auth: header/signature/expiry/scope; allocation: offer/request/ release/expiry/stats). Verified: 32 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the prototype's XOR cookie stub and any-token auth with a production-grade, RFC 8915-compliant NTS server: - NTS-KE (RFC 8915 §4): standalone pyOpenSSL TLS server on port 4460 that terminates TLS itself and derives c2s/s2c keys via the RFC 5705 exporter (label "EXPORTER-network-time-security", per-key context [proto|aead|dir]). Formal NTS-KE record encoding (Critical bit, End-of-Message, NEXT_PROTOCOL/ AEAD_ALGORITHM/NEW_COOKIE records). - Cookies: real AES-SIV-CMAC-256 AEAD sealing of key material under a rotating master key (version byte + grace window). No XOR. - Authenticated time (RFC 8915 §5.7): NTPv4 over UDP with NTS extension fields; the NTS authenticator uses AES-SIV (NOT HMAC) — unsupported AEADs and any crypto failure fail closed rather than downgrading. - Auth: JWT HS256 (shared JWT_SECRET_KEY, read at call time, fail closed), scope ntp:client for KE, ntp:admin for status. - UDP port configurable via NTP_UDP_PORT (default 123) so deployment can bind a high port and stay rootless. - Standards: @DataClass(slots=True), type hints, PostHog flag squawkdns.ntp-server (default OFF, graceful degradation), startup config validation, rootless Dockerfile (sha256-pinned bookworm base). - 25 tests: NTS-KE record roundtrip, TLS-exporter key agreement, AES-SIV cookie seal/unseal (tamper/expiry/version), AES-SIV authenticator verify/generate, key confirmation, JWT scope checks. Verified: 25 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Automated security review flagged two plaintext-transport paths in the merged client code: - squawk-client (Python) gRPC used grpc.insecure_channel unconditionally, sending the bearer token in the clear. Now: loopback targets may stay plaintext for local dev; all other targets use grpc.secure_channel with ssl_channel_credentials (+ optional CA bundle) and the token attached as composite call-credentials (gRPC only permits these over TLS). Removed the dead access_token_call_credentials that was never wired to the call. - squawk-client-go transport (http2.go) fell through to insecure.NewCredentials() (plaintext gRPC) when VerifySSL=false. Now keeps the channel encrypted with credentials.NewTLS(InsecureSkipVerify) — parity with the HTTP transports' verify=false — so the token is never on the wire in cleartext. Default remains fully-verified TLS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Deployments/Services/NetworkPolicies for the two hardened servers to both the Helm chart and the Kustomize base + alpha/beta overlays: - dhcp-server: port 8081, /health probes, env JWT_SECRET_KEY (squawk-jwt-secret) + DATABASE_URL (dhcp-server-db, scoped dhcp-server-rw) + POSTHOG_KEY (optional). - ntp-server: 8082 HTTP + 4460 NTS-KE (TLS terminated in-pod; documented that any future Gateway/Ingress fronting 4460 MUST use TLS/SNI passthrough, never terminate) + 123/udp Service -> targetPort 11123 so the pod binds a high UDP port and stays rootless. Mounts ntp-server-tls at /etc/ntp-tls. - Rootless securityContext, resource tiers, feature flags default OFF in-app. - Secrets referenced by name (must be provisioned out-of-band): squawk-jwt-secret, dhcp-server-db, ntp-server-tls, squawk-posthog-secret (optional). kustomize alpha/beta render clean with the new resources. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PyJWT==2.8.1 does not exist and penguin-dal>=1.0.0 exceeds the public release (max 0.3.0); the image build failed at pip install. Pin PyJWT==2.10.1 and penguin-dal==0.3.0 (matching manager/dns-server), posthog==3.6.0. Verified: docker build dhcp-server succeeds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The two new servers had no CI coverage. Add:
- build.yml: run dhcp-server tests (penguin-dal + JWT) and ntp-server tests
(RFC 8915 NTS, with a generated TLS cert) in build-and-test; build + smoke
their images (dep-import check) in docker-multi-build.
- server-release.yml: publish both on release tags via new matrix entries with
per-service context/dockerfile and the org-standard image name
ghcr.io/penguintechinc/squawk/{dhcp-server,ntp-server} (matching the k8s
manifests). Generalized the build step to support separate-Dockerfile services
alongside the existing root-Dockerfile targets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apply black formatting to the new/modified Python (agent-generated server code and the client gRPC-TLS change). Formatting only — no behavior change; suites still pass (dhcp 32, ntp 25, client 13) and blocking flake8 is clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The client union brought two codebases under squawk-client-go's blocking golangci-lint gate, surfacing 89 issues. Fix all (linter now exits 0) without weakening config: - errcheck (29): handle/deferred-ignore Close()/Encode()/AddEventHandler(). - staticcheck (46): WriteString(Sprintf) -> Fprintf; deprecated http3.RoundTripper -> http3.Transport; remove ineffectual ctx assignments; labeled break. - gosec (10): add ReadHeaderTimeout to http.Servers (G112); targeted //nolint:gosec with justification for intentional InsecureSkipVerify (G402, the opt-in no-plaintext TLS path) and safe integer conversions (G115). - ineffassign (1), unused (3): remove dead ctx assignment and unused HTTP2Transport client fields. Verified: golangci-lint run, go build ./..., go vet ./..., go test ./... all exit 0; both binaries build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Caution Review the following alerts detected in dependencies. According to your organization's Security Policy, you must resolve all "Block" alerts before proceeding. It is recommended to resolve "Warn" alerts too. Learn more about Socket for GitHub.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
…bump oauth2 CI environment mismatches surfaced on PR #46 (masked locally by a newer toolchain / the manager venv): - squawk-client-go/go.mod required `go 1.25.0` but all CI jobs run Go 1.24.2, breaking `go mod download`/build (Test Go Client, build-dns-client, build-k8s-dns). Lower to `go 1.24.0` (matches CI + rest of repo; 1.24.x is standards-acceptable while migrating). - build.yml dhcp/ntp test steps installed only `pytest`; the server tests use `@pytest.mark.asyncio`, so add `pytest-asyncio` (the async tests failed with "async def functions are not natively supported"). - version-monitor.yml's format check required exactly vMAJOR.MINOR.PATCH but the repo's .version carries a build-epoch 4th component (v2.1.1.1770072428); accept an optional trailing .epoch. - Bump golang.org/x/oauth2 v0.25.0 -> v0.30.0 (Socket HIGH CVE, transitive via k8s.io/client-go). go build + golangci-lint still clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…Trivy, Socket) Test Go Client: - .golangci.yml: target go 1.24 (was 1.25) to match CI's go1.24-built golangci-lint and go.mod; config parses clean, 0 issues. build-and-test (NTP RFC 8915): - Pin Flask 3.0.0 + Werkzeug 3.1.4 with Quart: Flask 3.1 requires a PROVIDE_AUTOMATIC_OPTIONS config key Quart doesn't set, breaking route registration (KeyError on test collection). dhcp mirrors the same pins. - cryptography 48.0.1: RFC 8915 authenticator seals empty plaintext, which cryptography <45 rejects; 48.0.1 also clears the crypto HIGH CVEs below. - ntp full suite verified 25/25. build-dns-client: - squawk-client-go/Dockerfile: restore stripped `\` line-continuations, drop Alpine-only apk/useradd -D on the Debian base, fix build path ./cmd/squawk-client (was ./cmd/squawk-dns-client). Builds amd64 locally. Trivy (dependency CVEs): - PyJWT 2.10.1 -> 2.13.0 (CVE-2026-32597/48526 HIGH + 4 more) in dhcp/ntp/manager. - python-dotenv 1.0.1 -> 1.2.2 (CVE-2026-28684). - quart 0.19.4 -> 0.20.0 in dhcp (CVE-2024-49767). - cryptography 44.0.0 -> 48.0.1 in dns-server/manager (CVE-2026-26007 + GHSA-537c-gmf6-5ccf HIGH). Socket (PR dependency alerts): - axios 1.6.0 -> 1.18.1, vite 5.0.0 -> 5.4.21 in manager/frontend and services/dns-webui (+ regenerated package-lock.json). - Regenerated docs/ and squawk-client/ pip lockfiles so the merge no longer downgrades certifi/gitpython/pathspec below main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| # does not set, breaking route registration. | ||
| quart==0.20.0 | ||
| flask==3.0.0 | ||
| werkzeug==3.1.4 |
| # does not set, breaking route registration. | ||
| quart==0.20.0 | ||
| flask==3.0.0 | ||
| werkzeug==3.1.4 |
| # Flask 3.1+ requires a PROVIDE_AUTOMATIC_OPTIONS config key this Quart line | ||
| # does not set, breaking route registration. | ||
| quart==0.20.0 | ||
| flask==3.0.0 |
CI installs golangci-lint v1.64.8 (go install @latest under go1.24 can't build v2, which needs go1.25), so a `version: 2` config was rejected ("config for v2 with v1"). Convert .golangci.yml to v1 schema (issues.exclude-dirs, drop version key) and pin the workflow install to @v1.64.8 (removes @latest drift). Fix the errcheck findings the strict config (check-blank + check-type-assertions) then surfaces: - cmd/squawk-client/main.go: check json.MarshalIndent errors (5 sites). - pkg/k8swatcher/watcher.go: guard informer type assertions with the two-value form + early return (6 sites) — unchecked assertions panic on tombstone/DeletedFinalStateUnknown objects. - pkg/performance/dns_performance.go: check os.Hostname error. Verified with golangci-lint v1.64.8 locally: 0 issues; go build + go test pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`make security` runs `gosec ./...` directly, which honors gosec's own `#nosec` annotations but ignores `//nolint:gosec` (a golangci-lint-only directive). The two intentional suppressions were written as `//nolint`, so standalone gosec still failed: - pkg/k8swatcher/watcher.go G115 (int32->uint16 port, always 0-65535) - pkg/transport/http2.go G402 (opt-in InsecureSkipVerify when VerifySSL=false) Switch both to `// #nosec Gxxx` (honored by standalone gosec and by golangci's gosec). Verified with gosec v2.22.11 and golangci-lint v1.64.8 locally: both 0 issues; go build passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ain, renovate Pod hardening: - Add `seccompProfile: RuntimeDefault` to every workload (pod-level securityContext) across k8s/helm/squawk/templates + k8s/kustomize/base (16 manifests) — completes PodSecurity "restricted" posture. Both overlays render clean. Dependabot remediation (101 open alerts → actionable set cleared): - npm: 0 critical / 0 high across root, manager/frontend, website, services/dns-webui (verified via `npm audit`). Key bumps: vite 5→6.4.3, vitest 4.1.10, next 15.5.18, @playwright/test 1.61.1, react-router-dom 7.18.1; transitive overrides for basic-ftp/lodash/ws/form-data/flatted/ minimatch/picomatch/@remix-run/router. 9 moderate/low remain (no fix / breaking-only). - Go (squawk-client-go): grpc v1.67.3→v1.79.3, quic-go v0.50.0→v0.59.1 (no source changes needed; build + golangci v1.64.8 + gosec all pass). - pip cryptography/PyJWT alerts were already cleared by the prior CVE commit (Dependabot will auto-close on rescan). shared/go_libs + shared/node_libs alert paths are stale (dropped in the merge). Supply-chain (SLSA-style): - cosign keyless (OIDC) image signing + syft SPDX SBOM generation/attestation added to push.yml, server-release.yml, go-client-release.yml. Every pushed image is signed by digest and gets an attested SBOM (+ uploaded artifact). New actions SHA-pinned; digests passed via injection-safe env vars; zizmor clean. Automation: - Add renovate.json (pinDigests for external images, vulnerability alerts, first-party images by tag) per standards. Caveat: manager/frontend and services/dns-webui have pre-existing TypeScript build errors and a react-router-dom v6→v7 major bump; these frontends are not CI-built. Flagged for a follow-up frontend validation pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The per-platform SBOM step failed on arm64: syft can't scan a push-by-digest per-arch image on the amd64 runner. Move cosign sign + syft SBOM/attest out of the per-platform build jobs and into the MERGE jobs, signing the final multi-arch index digest (the artifact users pull) — which scans cleanly on amd64. Also adds multi-arch merges + signing for dhcp-server and ntp-server (previously only per-arch tags, no merged manifest to sign). id-token: write added to merge jobs; new uses: SHA-pinned; refs passed via env-indirection (no new zizmor findings). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the global shared-secret HS256 scheme for USER tokens with asymmetric signing: the manager SIGNS with a private key (ES256 default, RS256 supported); dns-server, dhcp-server, ntp-server and the manager middleware VERIFY with the public key, accepting algorithms ["ES256","RS256"] ONLY — never HS256 (blocks the public-key-as-HMAC alg-confusion attack). Adds mandatory sub/iss/aud/tenant claims; every verify site requires exp/iat/tenant and denies an empty tenant (fail closed). Manager (signer): - config.py: JWT_ALGORITHM (default ES256), JWT_PRIVATE/PUBLIC_KEY[_FILE], JWT_ISSUER/AUDIENCE, TENANT_ID. ProductionConfig fails fast without keys; Dev/Test generate an ephemeral ES256 keypair. auth_service mints ES256 with the new claims. Per-server tokens (create_server_jwt) stay HS256 by design (per-server secret, TODO: SPIFFE/mTLS). client_config download tokens keep their per-instance HS256 secret (self-contained; TODO follow-up). Verifiers (dns-server selective_router + resilience, dhcp auth, ntp verify_jwt): - verify with the public key read at call time (supports rotation + testing), require tenant, reject HS256. Fail closed when no public key is configured. Keys + deploy: - scripts/gen-jwt-keys.sh (ES256/RS256 PEM generation, verified roundtrip). - k8s: manager mounts jwt-private-key + jwt-public-key from Secret `squawk-jwt-keys`; verifiers project ONLY jwt-public-key (private key never enters a verifier pod). Helm values + Kustomize base wired; example Secret template added (placeholder only). *.pem gitignored. Tests (verified locally): manager 86, dns-server 58 (auth-bypass regression suite rewritten for ES256 + new forged-key/missing-tenant cases), ntp 27, dhcp auth 22. New regression coverage: HS256 rejected, missing/empty tenant rejected, wrong aud/iss rejected, RS256 round-trip, ES256 forged-key denied. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This pull request merges the v2.1.x branch into main, representing a significant modernization and consolidation of the Squawk codebase.
Key Changes
1. Architectural Consolidation
2. Frontend Modernization
3. Testing & Quality Assurance
4. Infrastructure & Deployment
5. Documentation & Standards