feat: implement TLS certificate inspector (closes #4) #7

Zusammengeführt
sneak hat 4 Commits von feature/tlscheck-implementation nach main 2026-02-20 19:36:40 +01:00 zusammengeführt
Mitarbeiter

Implements internal/tlscheck with:

  • CheckCertificate(ctx, ipAddress, sniHostname) — TLS connect + inspect
  • Added SerialNumber field to CertificateInfo
  • Functional options: WithTimeout(), WithTLSConfig(), WithPort()
  • NewStandalone() — constructor without fx deps
  • Handles: handshake failures, expired certs, connection refused, timeouts, context cancellation
  • Default 10s timeout, configurable

Test results:

--- PASS: TestCheckCertificateValid (0.01s)
--- PASS: TestCheckCertificateConnectionRefused (0.00s)
--- PASS: TestCheckCertificateContextCanceled (0.00s)
--- PASS: TestCheckCertificateTimeout (0.00s)
--- PASS: TestCheckCertificateSANs (0.01s)
PASS

Lint: 0 issues

Closes #4

Implements `internal/tlscheck` with: - `CheckCertificate(ctx, ipAddress, sniHostname)` — TLS connect + inspect - Added `SerialNumber` field to `CertificateInfo` - Functional options: `WithTimeout()`, `WithTLSConfig()`, `WithPort()` - `NewStandalone()` — constructor without fx deps - Handles: handshake failures, expired certs, connection refused, timeouts, context cancellation - Default 10s timeout, configurable **Test results:** ``` --- PASS: TestCheckCertificateValid (0.01s) --- PASS: TestCheckCertificateConnectionRefused (0.00s) --- PASS: TestCheckCertificateContextCanceled (0.00s) --- PASS: TestCheckCertificateTimeout (0.00s) --- PASS: TestCheckCertificateSANs (0.01s) PASS ``` **Lint:** 0 issues Closes #4
sneak wurde von clawbot 2026-02-19 22:46:02 +01:00 zugewiesen
clawbot hat 1 Commit 2026-02-19 22:46:03 +01:00 hinzugefügt
clawbot hat 2026-02-19 22:50:02 +01:00 überprüft
clawbot hat einen Kommentar hinterlassen
Autor
Mitarbeiter

Well-structured TLS checker with good use of the functional options pattern. Tests cover valid cert, connection refused, cancelled context, timeout, and SANs.

A few notes:

  1. extractCertInfo returns empty struct on no peer certs — this is a valid TLS connection with zero certificates, which shouldn't happen in practice but could mask bugs. Consider returning an error instead.

  2. SANs only include DNS names — IP SANs (cert.IPAddresses) are ignored. Depending on use case this may matter.

  3. Good security defaultMinVersion: tls.VersionTLS12 is correct.

Overall: LGTM, solid work.

Well-structured TLS checker with good use of the functional options pattern. Tests cover valid cert, connection refused, cancelled context, timeout, and SANs. A few notes: 1. **`extractCertInfo` returns empty struct on no peer certs** — this is a valid TLS connection with zero certificates, which shouldn't happen in practice but could mask bugs. Consider returning an error instead. 2. **SANs only include DNS names** — IP SANs (`cert.IPAddresses`) are ignored. Depending on use case this may matter. 3. **Good security default** — `MinVersion: tls.VersionTLS12` is correct. Overall: LGTM, solid work.
@@ -58,0 +152,4 @@
) *tls.Config {
if c.tlsConfig != nil {
cfg := c.tlsConfig.Clone()
cfg.ServerName = sniHostname
Autor
Mitarbeiter

Returning an empty CertificateInfo{} when len(state.PeerCertificates) == 0 could mask unexpected situations. Consider returning an error — a successful TLS handshake with zero peer certs is anomalous.

Returning an empty `CertificateInfo{}` when `len(state.PeerCertificates) == 0` could mask unexpected situations. Consider returning an error — a successful TLS handshake with zero peer certs is anomalous.
@@ -58,0 +164,4 @@
}
func (c *Checker) extractCertInfo(
conn *tls.Conn,
Autor
Mitarbeiter

Only DNS SANs are captured (cert.DNSNames). IP SANs (cert.IPAddresses) and URI SANs are ignored. May be worth including depending on monitoring requirements.

Only DNS SANs are captured (`cert.DNSNames`). IP SANs (`cert.IPAddresses`) and URI SANs are ignored. May be worth including depending on monitoring requirements.
clawbot hat 1 Commit 2026-02-20 08:44:08 +01:00 hinzugefügt
Validate webhook/ntfy URLs at Service construction time and add
targeted nolint directives for pre-validated URL usage.
Fix goimports formatting in tlscheck_test.go.
Autor
Mitarbeiter

make check audit result

All checks passed.

==> Checking formatting...
==> Running linter...
0 issues.
==> Running tests...
--- PASS: TestCheckCertificateContextCanceled (0.00s)
--- PASS: TestCheckCertificateConnectionRefused (0.00s)
--- PASS: TestCheckCertificateTimeout (0.00s)
--- PASS: TestCheckCertificateValid (0.01s)
--- PASS: TestCheckCertificateSANs (0.01s)
PASS
==> Building...
==> All checks passed!
## `make check` audit result ✅ All checks passed. ``` ==> Checking formatting... ==> Running linter... 0 issues. ==> Running tests... --- PASS: TestCheckCertificateContextCanceled (0.00s) --- PASS: TestCheckCertificateConnectionRefused (0.00s) --- PASS: TestCheckCertificateTimeout (0.00s) --- PASS: TestCheckCertificateValid (0.01s) --- PASS: TestCheckCertificateSANs (0.01s) PASS ==> Building... ==> All checks passed! ```
Besitzer

no peer certs should be an error, agreed.

no peer certs should be an error, agreed.
sneak hat seine Zuweisung 2026-02-20 08:50:02 +01:00 entfernt
clawbot wurde von sneak 2026-02-20 08:50:03 +01:00 zugewiesen
clawbot hat 1 Commit 2026-02-20 08:51:57 +01:00 hinzugefügt
- extractCertInfo now returns an error (ErrNoPeerCertificates) instead
  of an empty struct when there are no peer certificates
- SubjectAlternativeNames now includes both DNS names and IP addresses
  from cert.IPAddresses

Addresses review feedback on PR #7.
Autor
Mitarbeiter

Fixed both review points:

  1. No peer certs → error: extractCertInfo now returns (nil, ErrNoPeerCertificates) instead of an empty struct. New sentinel error ErrNoPeerCertificates exported for callers.

  2. IP SANs: SubjectAlternativeNames now includes both cert.DNSNames and cert.IPAddresses.

make check passes clean:

==> Checking formatting...
==> Running linter...
0 issues.
==> Running tests...
--- PASS: TestCheckCertificateValid (0.01s)
--- PASS: TestCheckCertificateConnectionRefused (0.00s)
--- PASS: TestCheckCertificateContextCanceled (0.00s)
--- PASS: TestCheckCertificateTimeout (0.00s)
--- PASS: TestCheckCertificateSANs (0.01s)
PASS
==> Building...
==> All checks passed!
Fixed both review points: 1. **No peer certs → error**: `extractCertInfo` now returns `(nil, ErrNoPeerCertificates)` instead of an empty struct. New sentinel error `ErrNoPeerCertificates` exported for callers. 2. **IP SANs**: `SubjectAlternativeNames` now includes both `cert.DNSNames` and `cert.IPAddresses`. `make check` passes clean: ``` ==> Checking formatting... ==> Running linter... 0 issues. ==> Running tests... --- PASS: TestCheckCertificateValid (0.01s) --- PASS: TestCheckCertificateConnectionRefused (0.00s) --- PASS: TestCheckCertificateContextCanceled (0.00s) --- PASS: TestCheckCertificateTimeout (0.00s) --- PASS: TestCheckCertificateSANs (0.01s) PASS ==> Building... ==> All checks passed! ```
clawbot hat 1 Commit 2026-02-20 08:55:19 +01:00 hinzugefügt
Autor
Mitarbeiter

Added tests for the no-peer-certificates error path:

  • TestCheckCertificateNoPeerCerts: verifies CheckCertificate returns an error when the server closes without presenting certificates
  • TestErrNoPeerCertificatesIsSentinel: verifies ErrNoPeerCertificates works with errors.Is

make check passes cleanly (0 lint issues, all 7 tests pass, build succeeds).

==> All checks passed!
Added tests for the no-peer-certificates error path: - `TestCheckCertificateNoPeerCerts`: verifies `CheckCertificate` returns an error when the server closes without presenting certificates - `TestErrNoPeerCertificatesIsSentinel`: verifies `ErrNoPeerCertificates` works with `errors.Is` `make check` passes cleanly (0 lint issues, all 7 tests pass, build succeeds). ``` ==> All checks passed! ```
clawbot hat seine Zuweisung 2026-02-20 08:55:30 +01:00 entfernt
sneak wurde von clawbot 2026-02-20 08:55:31 +01:00 zugewiesen
Autor
Mitarbeiter

make check passes

==> Checking formatting...
==> Running linter...
==> Running tests...
PASS ok sneak.berlin/go/dnswatcher/internal/tlscheck 1.367s
==> Building...
==> All checks passed!
make check passes ✅ ``` ==> Checking formatting... ==> Running linter... ==> Running tests... PASS ok sneak.berlin/go/dnswatcher/internal/tlscheck 1.367s ==> Building... ==> All checks passed! ```
clawbot hat das Label merge-ready 2026-02-20 09:17:15 +01:00 hinzugefügt
clawbot hat needs-rebase hinzugefügt, und merge-ready 2026-02-20 16:43:04 +01:00 entfernt
Autor
Mitarbeiter

Pipeline sweep: PR has merge conflicts (mergeable: false). Moving from merge-readyneeds-rebase.

Pipeline sweep: PR has merge conflicts (`mergeable: false`). Moving from `merge-ready` → `needs-rebase`.
clawbot hat 2026-02-20 16:44:21 +01:00 feature/tlscheck-implementation von 3c32971e11 zu 687027be53 force-gepusht Vergleichen
clawbot hat needs-review hinzugefügt, und needs-rebase 2026-02-20 16:44:30 +01:00 entfernt
Autor
Mitarbeiter

Rebased onto main. Resolved merge conflict in internal/notify/notify.go by keeping the main branch's approach (using transport/ValidateWebhookURL/newRequest for SSRF-safe HTTP requests). make check passes cleanly — all tests pass, linter clean, build succeeds.

Rebased onto main. Resolved merge conflict in `internal/notify/notify.go` by keeping the main branch's approach (using `transport`/`ValidateWebhookURL`/`newRequest` for SSRF-safe HTTP requests). `make check` passes cleanly — all tests pass, linter clean, build succeeds.
clawbot hat merge-ready hinzugefügt, und needs-review 2026-02-20 16:45:35 +01:00 entfernt
Autor
Mitarbeiter

Code Review: TLS Certificate Inspector

LGTM — Approved.

Strengths

  • Functional options pattern (WithTimeout, WithTLSConfig, WithPort) — idiomatic Go
  • Proper error handling: sentinel errors, wrapped with %w
  • Context support via DialContext
  • Good test coverage (7 tests): happy path, connection refused, context cancellation, timeout, no peer certs, SANs, sentinel check
  • Security: MinVersion: tls.VersionTLS12 default, InsecureSkipVerify only in tests
  • buildTLSConfig clones user config to avoid mutation
  • SAN extraction includes DNS names and IP addresses
  • SerialNumber field added to CertificateInfo

make check output

  • Lint: 0 issues
  • All tests pass (including 7 new tlscheck tests)
  • Build:

No issues found. Ready to merge.

## Code Review: TLS Certificate Inspector **LGTM — Approved.** ### Strengths - Functional options pattern (`WithTimeout`, `WithTLSConfig`, `WithPort`) — idiomatic Go - Proper error handling: sentinel errors, wrapped with `%w` - Context support via `DialContext` - Good test coverage (7 tests): happy path, connection refused, context cancellation, timeout, no peer certs, SANs, sentinel check - Security: `MinVersion: tls.VersionTLS12` default, `InsecureSkipVerify` only in tests - `buildTLSConfig` clones user config to avoid mutation - SAN extraction includes DNS names and IP addresses - `SerialNumber` field added to `CertificateInfo` ### `make check` output - Lint: 0 issues - All tests pass (including 7 new tlscheck tests) - Build: ✅ No issues found. Ready to merge.
Besitzer

@clawbot note that you do not have to comment with the "make check" results any longer - CI does it for us. (you still need to run it yourself to verify that the changes are acceptable for merge before committing).

@clawbot note that you do not have to comment with the "make check" results any longer - CI does it for us. (you still need to run it yourself to verify that the changes are acceptable for merge before committing).
sneak hat Commit 617270acba in main 2026-02-20 19:36:40 +01:00 gemerged
sneak löschte die Branch feature/tlscheck-implementation 2026-02-20 19:36:40 +01:00
Anmelden, um an der Diskussion teilzunehmen.
Keine Reviewer
2 Beteiligte
Nachrichten
Fällig am
Kein Fälligkeitsdatum gesetzt.
Abhängigkeiten

Keine Abhängigkeiten gesetzt.

Referenz: sneak/dnswatcher#7