Compare commits
No commits in common. "8d2ff95be9df46b7cc6cc65074a311646069bedc" and "158b453759adf212d858e516b1c353a91126cf9d" have entirely different histories.
8d2ff95be9
...
158b453759
12 changed files with 214 additions and 168 deletions
21
LICENSE
21
LICENSE
|
|
@ -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.
|
||||
|
|
@ -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 <contact@happydomain.org>.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package checker
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -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 <contact@happydomain.org>.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package checker
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -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 <contact@happydomain.org>.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package checker
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -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 <contact@happydomain.org>.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package checker
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 <contact@happydomain.org>.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package checker
|
||||
|
||||
import (
|
||||
|
|
|
|||
101
checker/rule.go
101
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 <contact@happydomain.org>.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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 <contact@happydomain.org>.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package checker
|
||||
|
||||
import "encoding/json"
|
||||
|
|
|
|||
2
go.mod
2
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
|
||||
)
|
||||
|
||||
|
|
|
|||
4
go.sum
4
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=
|
||||
|
|
|
|||
25
main.go
25
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 <contact@happydomain.org>.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue