Skip to content

feat(scanner): service / application discovery (P2-01 / 26.12)#15

Merged
CryptoJones merged 1 commit into
mainfrom
sprint/26.12-service-discovery
May 27, 2026
Merged

feat(scanner): service / application discovery (P2-01 / 26.12)#15
CryptoJones merged 1 commit into
mainfrom
sprint/26.12-service-discovery

Conversation

@CryptoJones
Copy link
Copy Markdown
Owner

Summary

Turns `port 22 is open` into `SSH-2.0-OpenSSH_9.6p1 Ubuntu`. And similar for nine more protocols. `Port.Service` is now populated for every TCP port the scanner persists — the column has been in the schema since the initial migration but never had data flowing into it.

Protocols added

Port(s) Strategy Sample output
21 line banner `FTP: 220 (vsFTPd 3.0.3)`
23 line banner `Telnet: `
25, 465, 587 line banner `SMTP: 220 mx.example.com ESMTP Postfix`
110 line banner `POP3: +OK Dovecot ready.`
143 line banner `IMAP: * OK IMAP4rev1 Service Ready`
80, 8080, 8000, 8888 HTTP HEAD `HTTP: nginx/1.24.0`
443, 8443 TLS+HTTP `HTTPS: CN=router.example.com Server=lighttpd/1.4.69`
3306 MySQL v10 handshake `MySQL: 8.0.35-MySQL Community Server - GPL`
22 SSH banner (pre-existing) `SSH-2.0-OpenSSH_9.6p1 Ubuntu`

Test plan

  • `go test -race ./internal/scanner/...` — banner suite covers each protocol's happy path + silent-server timeout + wrong-protover MySQL guard + HTTPS handshake against `httptest.NewUnstartedServer` with a self-signed cert
  • `golangci-lint run ./...` → 0 issues
  • No new external deps (encoding/base64 is stdlib)

Notes

  • UDP banner-grabbing (DNS, SNMP, NTP) deferred — each needs a protocol-specific request packet rather than passive listen. Can ship in a future sprint if asked.
  • The HTTPS path uses `InsecureSkipVerify` intentionally — self-signed and expired certs are the norm on internal inventory targets. We scrape the cert for identification, not security.

🤖 Generated with Claude Code

Banner-grab on every persisted TCP port. Port.Service has been in the
schema since the initial migration but was never written until now.

New protocol decoders (internal/scanner/banner.go):
- lineBanner — SMTP, FTP, POP3, IMAP, Telnet (server greets first).
  Bounded capReader defends against a peer that floods without EOL.
- tlsHTTPSFingerprint — 443/8443. Completes TLS handshake
  (InsecureSkipVerify intentionally: scraping for ID, not trusting),
  peeks at cert CN/SAN, then reuses the same connection for HEAD to
  capture the Server header. Two IDs for one dial.
- mysqlGreeting — 3306. Reads v10 handshake packet, extracts the
  server-version string. Passive — never writes to the socket.

Wired into scanner.upsertPort via a new `service string` parameter.
Three call sites (liveness, deepScan, udpScan) updated. UDP banner-
grabbing is protocol-specific (DNS/SNMP/NTP each need a request
packet) and out of scope for this sprint.

Test coverage:
- Valid SMTP/FTP greeting parses
- Silent-server timeout returns "" (must not hang the scan loop)
- Valid v10 MySQL handshake → parsed version string
- Wrong-protover MySQL handshake → "" (guard against random services
  that happen to dial-clean on 3306)
- HTTPS via httptest.NewUnstartedServer with a self-signed cert,
  asserts CN extraction and Server-header capture both flow through

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CryptoJones CryptoJones merged commit 833326d into main May 27, 2026
5 of 6 checks passed
@CryptoJones CryptoJones deleted the sprint/26.12-service-discovery branch May 27, 2026 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant