115 lines
2.5 KiB
Go
115 lines
2.5 KiB
Go
// SPDX-License-Identifier: MIT
|
|
|
|
package checker
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// ParseGrokOutput decodes the JSON produced by `dnsviz grok` into a typed
|
|
// map of zone analyses. The returned Order slice lists zone FQDNs sorted
|
|
// from the most-specific (queried name) up to the root, matching DNSViz's
|
|
// natural chain order.
|
|
func ParseGrokOutput(raw []byte) (map[string]ZoneAnalysis, []string, error) {
|
|
var top map[string]json.RawMessage
|
|
if err := json.Unmarshal(raw, &top); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
out := make(map[string]ZoneAnalysis, len(top))
|
|
for k, v := range top {
|
|
out[k] = decodeZone(v)
|
|
}
|
|
|
|
keys := make([]string, 0, len(out))
|
|
for k := range out {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
return labelDepth(keys[i]) > labelDepth(keys[j])
|
|
})
|
|
return out, keys, nil
|
|
}
|
|
|
|
func decodeZone(raw json.RawMessage) ZoneAnalysis {
|
|
var z ZoneAnalysis
|
|
var m map[string]json.RawMessage
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
var s string
|
|
if json.Unmarshal(raw, &s) == nil {
|
|
z.Status = s
|
|
}
|
|
return z
|
|
}
|
|
|
|
if v, ok := m["status"]; ok {
|
|
_ = json.Unmarshal(v, &z.Status)
|
|
}
|
|
z.Errors = decodeFindings(m["errors"])
|
|
z.Warnings = decodeFindings(m["warnings"])
|
|
|
|
delete(m, "status")
|
|
delete(m, "errors")
|
|
delete(m, "warnings")
|
|
if len(m) > 0 {
|
|
z.Extra = make(map[string]any, len(m))
|
|
for k, v := range m {
|
|
var any any
|
|
_ = json.Unmarshal(v, &any)
|
|
z.Extra[k] = any
|
|
}
|
|
}
|
|
return z
|
|
}
|
|
|
|
func decodeFindings(raw json.RawMessage) []Finding {
|
|
if len(raw) == 0 {
|
|
return nil
|
|
}
|
|
var arr []map[string]any
|
|
if err := json.Unmarshal(raw, &arr); err != nil {
|
|
var strs []string
|
|
if json.Unmarshal(raw, &strs) == nil {
|
|
out := make([]Finding, 0, len(strs))
|
|
for _, s := range strs {
|
|
out = append(out, Finding{Description: s})
|
|
}
|
|
return out
|
|
}
|
|
return nil
|
|
}
|
|
out := make([]Finding, 0, len(arr))
|
|
for _, item := range arr {
|
|
f := Finding{}
|
|
if s, ok := item["code"].(string); ok {
|
|
f.Code = s
|
|
}
|
|
if s, ok := item["description"].(string); ok && s != "" {
|
|
f.Description = s
|
|
} else if s, ok := item["message"].(string); ok && s != "" {
|
|
f.Description = s
|
|
} else {
|
|
b, _ := json.Marshal(item)
|
|
f.Description = string(b)
|
|
}
|
|
if servers, ok := item["servers"].([]any); ok {
|
|
for _, srv := range servers {
|
|
if s, ok := srv.(string); ok {
|
|
f.Servers = append(f.Servers, s)
|
|
}
|
|
}
|
|
}
|
|
out = append(out, f)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func labelDepth(zone string) int {
|
|
z := strings.TrimSuffix(zone, ".")
|
|
if z == "" {
|
|
return 0
|
|
}
|
|
return strings.Count(z, ".") + 1
|
|
}
|