diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 07d44d8..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 The happyDomain Authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the “Software”), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/checker/abstract.go b/checker/abstract.go index c8de1e5..6c42667 100644 --- a/checker/abstract.go +++ b/checker/abstract.go @@ -1,3 +1,24 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2026 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package checker import ( diff --git a/checker/checks.go b/checker/checks.go index 89bb952..534073f 100644 --- a/checker/checks.go +++ b/checker/checks.go @@ -1,3 +1,24 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2026 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package checker import ( diff --git a/checker/collect.go b/checker/collect.go index 7ab9990..1a49be8 100644 --- a/checker/collect.go +++ b/checker/collect.go @@ -1,3 +1,24 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2026 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package checker import ( diff --git a/checker/definition.go b/checker/definition.go index 1db8583..42370bb 100644 --- a/checker/definition.go +++ b/checker/definition.go @@ -1,3 +1,24 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2026 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package checker import ( diff --git a/checker/interactive.go b/checker/interactive.go deleted file mode 100644 index d7de7c8..0000000 --- a/checker/interactive.go +++ /dev/null @@ -1,103 +0,0 @@ -package checker - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "strings" - - sdk "git.happydns.org/checker-sdk-go/checker" - "github.com/miekg/dns" -) - -// RenderForm implements sdk.CheckerInteractive. It lists the minimal human -// inputs needed to bootstrap a check when this checker runs standalone -// (outside of a happyDomain host). -func (p *nsProvider) RenderForm() []sdk.CheckerOptionField { - return []sdk.CheckerOptionField{ - { - Id: "domain", - Type: "string", - Label: "Domain name", - Placeholder: "example.com", - Required: true, - Description: "Zone to probe. Its NS records will be resolved and each nameserver tested.", - }, - } -} - -// ParseForm implements sdk.CheckerInteractive. It resolves the NS records -// for the requested domain via DNS and assembles the CheckerOptions that -// Collect expects — replacing the AutoFill work that happyDomain would -// otherwise perform. -func (p *nsProvider) ParseForm(r *http.Request) (sdk.CheckerOptions, error) { - domain := strings.TrimSpace(r.FormValue("domain")) - if domain == "" { - return nil, errors.New("domain is required") - } - fqdn := dns.Fqdn(domain) - - nsRecords, err := resolveNS(fqdn) - if err != nil { - return nil, fmt.Errorf("could not resolve NS records for %s: %w", domain, err) - } - if len(nsRecords) == 0 { - return nil, fmt.Errorf("no NS records found for %s", domain) - } - - payload, err := json.Marshal(originPayload{NameServers: nsRecords}) - if err != nil { - return nil, fmt.Errorf("failed to encode origin payload: %w", err) - } - - svc := serviceMessage{ - Type: serviceTypeOrigin, - Domain: "", - Service: payload, - } - - return sdk.CheckerOptions{ - "service": svc, - "domainName": strings.TrimSuffix(fqdn, "."), - }, nil -} - -// resolveNS queries the system resolver for the NS records of fqdn and -// returns them as miekg *dns.NS records so they match the shape produced -// by happyDomain's Origin service payload. -func resolveNS(fqdn string) ([]*dns.NS, error) { - c := new(dns.Client) - m := new(dns.Msg) - m.SetQuestion(fqdn, dns.TypeNS) - m.RecursionDesired = true - - config, err := dns.ClientConfigFromFile("/etc/resolv.conf") - if err != nil || config == nil || len(config.Servers) == 0 { - config = &dns.ClientConfig{Servers: []string{"1.1.1.1", "8.8.8.8"}, Port: "53"} - } - - var lastErr error - for _, server := range config.Servers { - in, _, err := c.Exchange(m, server+":"+config.Port) - if err != nil { - lastErr = err - continue - } - if in.Rcode != dns.RcodeSuccess { - lastErr = fmt.Errorf("DNS response code %s", dns.RcodeToString[in.Rcode]) - continue - } - var records []*dns.NS - for _, rr := range in.Answer { - if ns, ok := rr.(*dns.NS); ok { - records = append(records, ns) - } - } - return records, nil - } - if lastErr == nil { - lastErr = errors.New("no resolver available") - } - return nil, lastErr -} diff --git a/checker/provider.go b/checker/provider.go index 6dfe8a7..b7a8d13 100644 --- a/checker/provider.go +++ b/checker/provider.go @@ -1,3 +1,24 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2026 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package checker import ( diff --git a/checker/rule.go b/checker/rule.go index 94abc35..85d1e5f 100644 --- a/checker/rule.go +++ b/checker/rule.go @@ -1,8 +1,30 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2026 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package checker import ( "context" "fmt" + "strings" sdk "git.happydns.org/checker-sdk-go/checker" ) @@ -63,65 +85,66 @@ type singleCheckRule struct { func (r *singleCheckRule) Name() string { return r.ruleName } func (r *singleCheckRule) Description() string { return r.description } -func (r *singleCheckRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState { +func (r *singleCheckRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) sdk.CheckState { var report NSRestrictionsReport if err := obs.Get(ctx, ObservationKeyNSRestrictions, &report); err != nil { - return []sdk.CheckState{{ + return sdk.CheckState{ Status: sdk.StatusError, Message: fmt.Sprintf("Failed to get NS restrictions data: %v", err), Code: r.code + "_error", - }} + } } - out := make([]sdk.CheckState, 0, len(report.Servers)) - for _, srv := range report.Servers { - meta := map[string]any{ - "check": r.checkName, - "name": srv.Name, - "address": srv.Address, - } + status := sdk.StatusOK + var summaryParts []string + failingServers := make([]map[string]string, 0) + checked := false + for _, srv := range report.Servers { item, found := findCheck(srv.Checks, r.checkName) if !found { - message := "check not performed" + // The collect step did not run this check on this server + // (e.g. IPv6 unreachable, DNS resolution failure). Surface + // the reason from whichever entry the server does have. if len(srv.Checks) > 0 { - message = fmt.Sprintf("skipped: %s", srv.Checks[0].Detail) + summaryParts = append(summaryParts, fmt.Sprintf("%s: skipped (%s)", serverLabel(srv), srv.Checks[0].Detail)) + } else { + summaryParts = append(summaryParts, fmt.Sprintf("%s: skipped", serverLabel(srv))) } - out = append(out, sdk.CheckState{ - Status: sdk.StatusUnknown, - Message: message, - Code: r.code + "_skipped", - Subject: serverLabel(srv), - Meta: meta, - }) continue } - state := sdk.CheckState{ - Code: r.code + "_result", - Subject: serverLabel(srv), - Meta: meta, - Message: item.Detail, - } + checked = true + if item.OK { - state.Status = sdk.StatusOK - if state.Message == "" { - state.Message = "OK" - } - } else { - state.Status = r.failStatus + summaryParts = append(summaryParts, fmt.Sprintf("%s: OK", serverLabel(srv))) + continue } - out = append(out, state) + + if status < r.failStatus { + status = r.failStatus + } + summaryParts = append(summaryParts, fmt.Sprintf("%s: FAIL (%s)", serverLabel(srv), item.Detail)) + failingServers = append(failingServers, map[string]string{ + "name": srv.Name, + "address": srv.Address, + "detail": item.Detail, + }) } - if len(out) == 0 { - return []sdk.CheckState{{ - Status: sdk.StatusUnknown, - Message: "no nameserver to evaluate", - Code: r.code + "_result", - }} + if !checked { + status = sdk.StatusUnknown + } + + return sdk.CheckState{ + Status: status, + Message: strings.Join(summaryParts, " | "), + Code: r.code + "_result", + Meta: map[string]any{ + "check": r.checkName, + "failing_servers": failingServers, + }, } - return out } func findCheck(items []NSCheckItem, name string) (NSCheckItem, bool) { diff --git a/checker/types.go b/checker/types.go index d84f327..2d976a5 100644 --- a/checker/types.go +++ b/checker/types.go @@ -1,3 +1,24 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2026 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package checker import "encoding/json" diff --git a/go.mod b/go.mod index 906fafe..98a5e52 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.happydns.org/checker-ns-restrictions go 1.25.0 require ( - git.happydns.org/checker-sdk-go v1.2.0 + git.happydns.org/checker-sdk-go v0.0.1 github.com/miekg/dns v1.1.72 ) diff --git a/go.sum b/go.sum index d64ca7c..eab57b3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -git.happydns.org/checker-sdk-go v1.2.0 h1:v4MpKAz0W3PwP+bxx3pya8w893sVH5xTD1of1cc0TV8= -git.happydns.org/checker-sdk-go v1.2.0/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI= +git.happydns.org/checker-sdk-go v0.0.1 h1:4RxCJr73HWKxjOyU/6NJMO8lXJmH0gMLA68EzTqLbQI= +git.happydns.org/checker-sdk-go v0.0.1/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= diff --git a/main.go b/main.go index 2ea54aa..c3f555d 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,24 @@ +// This file is part of the happyDomain (R) project. +// Copyright (c) 2020-2026 happyDomain +// Authors: Pierre-Olivier Mercier, et al. +// +// This program is offered under a commercial and under the AGPL license. +// For commercial licensing, contact us at . +// +// For AGPL licensing: +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + package main import ( @@ -8,14 +29,14 @@ import ( sdk "git.happydns.org/checker-sdk-go/checker" ) +var listenAddr = flag.String("listen", ":8080", "HTTP listen address") + // Version is the standalone binary's version. It defaults to "custom-build" // and is meant to be overridden by the CI at link time: // // go build -ldflags "-X main.Version=1.2.3" . var Version = "custom-build" -var listenAddr = flag.String("listen", ":8080", "HTTP listen address") - func main() { flag.Parse()