add initial Go implementation of arcline-audit
Implements the full site health auditor with four check groups: - SSL/TLS (certificate validity, expiry, chain, TLS version, ciphers) - HTTP (redirect chain, security headers, response time) - DNS (A/AAAA, MX, SPF, DKIM, DMARC, DNSSEC) - Infrastructure (CDN detection, common port probes) Includes CLI with --checks filter, --json and --out flags, cross-compile Makefile, and GitLab CI pipeline. Signed-off-by: Blake Ridgway <blake@blakeridgway.com>
This commit is contained in:
110
internal/report/renderer.go
Normal file
110
internal/report/renderer.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"arcline-audit/internal/types"
|
||||
)
|
||||
|
||||
const sectionWidth = 55
|
||||
|
||||
// Terminal renders the audit result to the given writer in a human-readable terminal format.
|
||||
func Terminal(w io.Writer, result types.AuditResult) {
|
||||
fmt.Fprintf(w, "\n")
|
||||
printSection(w, "SSL", result.SSL.Checks, func() {
|
||||
if result.SSL.Expiry != (time.Time{}) {
|
||||
fmt.Fprintf(w, " Issuer %s\n", result.SSL.Issuer)
|
||||
fmt.Fprintf(w, " Expires %s (%d days)\n",
|
||||
result.SSL.Expiry.Format("2006-01-02"), result.SSL.DaysLeft)
|
||||
if result.SSL.TLSVersion != "" {
|
||||
fmt.Fprintf(w, " TLS %s\n", result.SSL.TLSVersion)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
printSection(w, "HTTP", result.HTTP.Checks, func() {
|
||||
if result.HTTP.FinalURL != "" {
|
||||
fmt.Fprintf(w, " Final URL %s\n", result.HTTP.FinalURL)
|
||||
}
|
||||
fmt.Fprintf(w, " Response %dms\n", result.HTTP.ResponseTime.Milliseconds())
|
||||
})
|
||||
|
||||
printSection(w, "DNS", result.DNS.Checks, nil)
|
||||
printSection(w, "Infrastructure", result.Infra.Checks, func() {
|
||||
if result.Infra.CDN != "" {
|
||||
fmt.Fprintf(w, " CDN %s\n", result.Infra.CDN)
|
||||
}
|
||||
if result.Infra.ASN != "" {
|
||||
fmt.Fprintf(w, " ASN %s\n", result.Infra.ASN)
|
||||
}
|
||||
if result.Infra.Org != "" {
|
||||
fmt.Fprintf(w, " Org %s\n", result.Infra.Org)
|
||||
}
|
||||
})
|
||||
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
|
||||
// JSON renders the audit result as indented JSON.
|
||||
func JSON(w io.Writer, result types.AuditResult) {
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
enc.Encode(result)
|
||||
}
|
||||
|
||||
// PlainText renders the audit result as plain text (no ANSI codes).
|
||||
func PlainText(w io.Writer, result types.AuditResult) {
|
||||
sections := []struct {
|
||||
name string
|
||||
checks []types.CheckResult
|
||||
}{
|
||||
{"SSL", result.SSL.Checks},
|
||||
{"HTTP", result.HTTP.Checks},
|
||||
{"DNS", result.DNS.Checks},
|
||||
{"Infrastructure", result.Infra.Checks},
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "Audit for %s — %s\n\n", result.Domain, result.Time.Format(time.RFC3339))
|
||||
|
||||
for _, sec := range sections {
|
||||
fmt.Fprintf(w, "── %s %s\n", sec.name, strings.Repeat("─", sectionWidth-5-len(sec.name)))
|
||||
for _, c := range sec.checks {
|
||||
fmt.Fprintf(w, "[%s] %s\n", c.Status, c.Message)
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func printSection(w io.Writer, name string, checks []types.CheckResult, extra func()) {
|
||||
header := fmt.Sprintf("── %s ", name)
|
||||
fmt.Fprintf(w, " %s%s\n", header, strings.Repeat("─", sectionWidth-len(header)-2))
|
||||
|
||||
for _, c := range checks {
|
||||
prefix := statusPrefix(c.Status)
|
||||
fmt.Fprintf(w, " %s %s\n", prefix, c.Message)
|
||||
}
|
||||
|
||||
if extra != nil {
|
||||
extra()
|
||||
}
|
||||
}
|
||||
|
||||
func statusPrefix(s types.Status) string {
|
||||
switch s {
|
||||
case types.StatusOK:
|
||||
return "[OK] "
|
||||
case types.StatusWarn:
|
||||
return "[WARN]"
|
||||
case types.StatusFail:
|
||||
return "[FAIL]"
|
||||
case types.StatusInfo:
|
||||
return "[INFO]"
|
||||
default:
|
||||
return "[????]"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user