Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c5c13960d5 | |||
| 1493ef4d3f | |||
| 52a3e56c4f | |||
| 56db4cc59d | |||
| fde892a958 | |||
| 23d2cafaad |
12 changed files with 380 additions and 83 deletions
22
.drone-manifest.yml
Normal file
22
.drone-manifest.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
image: happydomain/checker-alias:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
manifests:
|
||||
- image: happydomain/checker-alias:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: happydomain/checker-alias:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
variant: v8
|
||||
- image: happydomain/checker-alias:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
||||
variant: v7
|
||||
187
.drone.yml
Normal file
187
.drone.yml
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build-amd64
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: checker build
|
||||
image: golang:1-alpine
|
||||
commands:
|
||||
- apk add --no-cache git make
|
||||
- make
|
||||
environment:
|
||||
CHECKER_VERSION: "${DRONE_BRANCH}-${DRONE_COMMIT}"
|
||||
CGO_ENABLED: 0
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
|
||||
- name: checker build tag
|
||||
image: golang:1-alpine
|
||||
commands:
|
||||
- apk add --no-cache git make
|
||||
- make
|
||||
environment:
|
||||
CHECKER_VERSION: "${DRONE_SEMVER}"
|
||||
CGO_ENABLED: 0
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
- name: publish on Docker Hub
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: happydomain/checker-alias
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- CHECKER_VERSION=${DRONE_BRANCH}-${DRONE_COMMIT}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
|
||||
- name: publish on Docker Hub (tag)
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: happydomain/checker-alias
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- CHECKER_VERSION=${DRONE_SEMVER}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
exclude:
|
||||
- renovate/*
|
||||
event:
|
||||
- cron
|
||||
- push
|
||||
- tag
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build-arm64
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: checker build
|
||||
image: golang:1-alpine
|
||||
commands:
|
||||
- apk add --no-cache git make
|
||||
- make
|
||||
environment:
|
||||
CHECKER_VERSION: "${DRONE_BRANCH}-${DRONE_COMMIT}"
|
||||
CGO_ENABLED: 0
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
|
||||
- name: checker build tag
|
||||
image: golang:1-alpine
|
||||
commands:
|
||||
- apk add --no-cache git make
|
||||
- make
|
||||
environment:
|
||||
CHECKER_VERSION: "${DRONE_SEMVER}"
|
||||
CGO_ENABLED: 0
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
- name: publish on Docker Hub
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: happydomain/checker-alias
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- CHECKER_VERSION=${DRONE_BRANCH}-${DRONE_COMMIT}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
event:
|
||||
exclude:
|
||||
- tag
|
||||
|
||||
- name: publish on Docker Hub (tag)
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: happydomain/checker-alias
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile
|
||||
build_args:
|
||||
- CHECKER_VERSION=${DRONE_SEMVER}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- cron
|
||||
- push
|
||||
- tag
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: docker-manifest
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: publish on Docker Hub
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
spec: .drone-manifest.yml
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
exclude:
|
||||
- renovate/*
|
||||
event:
|
||||
- cron
|
||||
- push
|
||||
- tag
|
||||
|
||||
depends_on:
|
||||
- build-amd64
|
||||
- build-arm64
|
||||
|
|
@ -49,6 +49,7 @@ func (p *aliasProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (a
|
|||
}
|
||||
|
||||
observeCoexistence(ctx, data, servers, owner)
|
||||
observeDNAMECoexistence(ctx, data, servers)
|
||||
observeDNSSEC(ctx, data, servers, apex, owner)
|
||||
|
||||
return data, nil
|
||||
|
|
@ -57,19 +58,24 @@ func (p *aliasProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (a
|
|||
// resolveOwner prefers the "service" option because its dns.CNAME owner is
|
||||
// authoritative; subdomain + domain_name is the fallback for ad-hoc forms.
|
||||
func resolveOwner(opts sdk.CheckerOptions) (string, error) {
|
||||
parent, _ := sdk.GetOption[string](opts, "domain_name")
|
||||
parent = strings.TrimSuffix(parent, ".")
|
||||
|
||||
if svcMsg, ok := sdk.GetOption[serviceMessage](opts, "service"); ok && len(svcMsg.Service) > 0 {
|
||||
var c cnameService
|
||||
if err := json.Unmarshal(svcMsg.Service, &c); err == nil && c.Record != nil && c.Record.Hdr.Name != "" {
|
||||
return lowerFQDN(c.Record.Hdr.Name), nil
|
||||
if err := json.Unmarshal(svcMsg.Service, &c); err == nil && c.Record != nil {
|
||||
// svcMsg.Domain holds the subdomain (relative to apex); the
|
||||
// record's Hdr.Name is relative to that mount point. Build the
|
||||
// origin first, then join the record name into it.
|
||||
origin := sdk.JoinRelative(strings.TrimSuffix(svcMsg.Domain, "."), parent)
|
||||
return lowerFQDN(sdk.JoinRelative(c.Record.Hdr.Name, origin)), nil
|
||||
}
|
||||
}
|
||||
|
||||
parent, _ := sdk.GetOption[string](opts, "domain_name")
|
||||
sub, _ := sdk.GetOption[string](opts, "subdomain")
|
||||
if parent == "" {
|
||||
return "", fmt.Errorf("missing 'domain_name' option")
|
||||
}
|
||||
parent = strings.TrimSuffix(parent, ".")
|
||||
if sub == "" || sub == "@" {
|
||||
return lowerFQDN(parent), nil
|
||||
}
|
||||
|
|
@ -395,29 +401,21 @@ func observeApex(ctx context.Context, data *AliasData, servers []string, apex st
|
|||
|
||||
if (hasA || hasAAAA) && !data.ApexHasCNAME {
|
||||
data.ApexFlattening = true
|
||||
// Synthesize a pseudo-hop so the report's chain view shows the ALIAS
|
||||
// indirection that would otherwise be invisible from the wire.
|
||||
data.Chain = append(data.Chain, ChainHop{
|
||||
Owner: lowerFQDN(apex),
|
||||
Kind: KindALIAS,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func observeCoexistence(ctx context.Context, data *AliasData, servers []string, owner string) {
|
||||
if !data.OwnerHasCNAME {
|
||||
return
|
||||
}
|
||||
|
||||
siblings := []uint16{
|
||||
// querySiblings returns RRsets of common types that sit alongside a CNAME or DNAME at owner.
|
||||
// Filter on owner+type: a DNAME-synthesized CNAME would otherwise count as a sibling.
|
||||
func querySiblings(ctx context.Context, servers []string, owner string) []CoexistingRRset {
|
||||
candidates := []uint16{
|
||||
dns.TypeA, dns.TypeAAAA, dns.TypeMX, dns.TypeTXT,
|
||||
dns.TypeNS, dns.TypeSRV, dns.TypeCAA,
|
||||
}
|
||||
seen := map[string]uint32{}
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(siblings))
|
||||
for _, qt := range siblings {
|
||||
wg.Add(len(candidates))
|
||||
for _, qt := range candidates {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
q := dns.Question{Name: owner, Qtype: qt, Qclass: dns.ClassINET}
|
||||
|
|
@ -425,8 +423,6 @@ func observeCoexistence(ctx context.Context, data *AliasData, servers []string,
|
|||
if err != nil || r == nil {
|
||||
return
|
||||
}
|
||||
// Filter on owner+type because a DNAME-synthesized CNAME would
|
||||
// otherwise count as a sibling of every queried type.
|
||||
for _, rr := range r.Answer {
|
||||
if rr.Header().Rrtype != qt {
|
||||
continue
|
||||
|
|
@ -442,9 +438,42 @@ func observeCoexistence(ctx context.Context, data *AliasData, servers []string,
|
|||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var out []CoexistingRRset
|
||||
for t, ttl := range seen {
|
||||
data.Coexisting = append(data.Coexisting, CoexistingRRset{Type: t, TTL: ttl})
|
||||
out = append(out, CoexistingRRset{Type: t, TTL: ttl})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func observeCoexistence(ctx context.Context, data *AliasData, servers []string, owner string) {
|
||||
if !data.OwnerHasCNAME {
|
||||
return
|
||||
}
|
||||
data.Coexisting = querySiblings(ctx, servers, owner)
|
||||
}
|
||||
|
||||
func observeDNAMECoexistence(ctx context.Context, data *AliasData, servers []string) {
|
||||
if len(data.DNAMESubstitutions) == 0 {
|
||||
return
|
||||
}
|
||||
results := make(map[string][]CoexistingRRset, len(data.DNAMESubstitutions))
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(data.DNAMESubstitutions))
|
||||
for _, hop := range data.DNAMESubstitutions {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
siblings := querySiblings(ctx, servers, hop.Owner)
|
||||
if len(siblings) > 0 {
|
||||
mu.Lock()
|
||||
results[hop.Owner] = siblings
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if len(results) > 0 {
|
||||
data.DNAMECoexistence = results
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ func Definition() *sdk.CheckerDefinition {
|
|||
Version: Version,
|
||||
Availability: sdk.CheckerAvailability{
|
||||
ApplyToService: true,
|
||||
ApplyToDomain: true,
|
||||
ApplyToZone: true,
|
||||
LimitToServices: []string{
|
||||
"svcs.CNAME",
|
||||
"svcs.SpecialCNAME",
|
||||
|
|
@ -65,6 +63,11 @@ func Definition() *sdk.CheckerDefinition {
|
|||
Label: "Service",
|
||||
AutoFill: sdk.AutoFillService,
|
||||
},
|
||||
{
|
||||
Id: "domain_name",
|
||||
Label: "Parent domain name",
|
||||
AutoFill: sdk.AutoFillDomainName,
|
||||
},
|
||||
},
|
||||
},
|
||||
Rules: []sdk.CheckRule{
|
||||
|
|
@ -77,6 +80,7 @@ func Definition() *sdk.CheckerDefinition {
|
|||
cnameAtApexRule{},
|
||||
apexFlatteningRule{},
|
||||
cnameCoexistenceRule{},
|
||||
dnameCoexistenceRule{},
|
||||
cnameDnssecRule{},
|
||||
targetResolvableRule{},
|
||||
multipleRecordsRule{},
|
||||
|
|
|
|||
|
|
@ -104,7 +104,15 @@ func buildReportView(data *AliasData, states []sdk.CheckState) *reportView {
|
|||
v.FinalAddresses = append(v.FinalAddresses, data.FinalA...)
|
||||
v.FinalAddresses = append(v.FinalAddresses, data.FinalAAAA...)
|
||||
|
||||
for i, h := range data.Chain {
|
||||
chain := data.Chain
|
||||
if data.ApexFlattening {
|
||||
chain = append(chain, ChainHop{
|
||||
Owner: data.Apex,
|
||||
Kind: KindALIAS,
|
||||
})
|
||||
}
|
||||
|
||||
for i, h := range chain {
|
||||
step := chainStep{
|
||||
Index: i + 1,
|
||||
Owner: h.Owner,
|
||||
|
|
@ -112,7 +120,7 @@ func buildReportView(data *AliasData, states []sdk.CheckState) *reportView {
|
|||
Target: h.Target,
|
||||
TTL: h.TTL,
|
||||
Server: h.Server,
|
||||
IsLast: i == len(data.Chain)-1,
|
||||
IsLast: i == len(chain)-1,
|
||||
}
|
||||
switch h.Kind {
|
||||
case KindCNAME:
|
||||
|
|
|
|||
|
|
@ -189,24 +189,10 @@ type targetResolvableRule struct{}
|
|||
|
||||
func (targetResolvableRule) Name() string { return "target_resolvable" }
|
||||
func (targetResolvableRule) Description() string {
|
||||
return "Verifies that the final target of the alias chain publishes at least one A or AAAA record."
|
||||
return "Verifies that the final target of the alias chain exists in DNS (returns NOERROR, not NXDOMAIN)."
|
||||
}
|
||||
|
||||
func (targetResolvableRule) Options() sdk.CheckerOptionsDocumentation {
|
||||
return sdk.CheckerOptionsDocumentation{
|
||||
UserOpts: []sdk.CheckerOptionDocumentation{
|
||||
{
|
||||
Id: "requireResolvableTarget",
|
||||
Type: "bool",
|
||||
Label: "Require resolvable target",
|
||||
Description: "When enabled, a chain whose final target returns no A/AAAA is reported as critical (otherwise a warning).",
|
||||
Default: defaultRequireResolvableTarget,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (targetResolvableRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
||||
func (targetResolvableRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
||||
data, errState := loadAlias(ctx, obs)
|
||||
if errState != nil {
|
||||
return errState
|
||||
|
|
@ -217,22 +203,14 @@ func (targetResolvableRule) Evaluate(ctx context.Context, obs sdk.ObservationGet
|
|||
if data.ChainTerminated.Reason != TermOK {
|
||||
return skipped("chain did not terminate normally")
|
||||
}
|
||||
if len(data.FinalA) > 0 || len(data.FinalAAAA) > 0 {
|
||||
return okState(data.FinalTarget, fmt.Sprintf("target %s resolves to %d address(es)", data.FinalTarget, len(data.FinalA)+len(data.FinalAAAA)))
|
||||
}
|
||||
status := sdk.StatusWarn
|
||||
if sdk.GetBoolOption(opts, "requireResolvableTarget", defaultRequireResolvableTarget) {
|
||||
status = sdk.StatusCrit
|
||||
}
|
||||
rcode := data.FinalRcode
|
||||
if rcode == "" {
|
||||
rcode = "no A/AAAA"
|
||||
if data.FinalRcode != "NXDOMAIN" {
|
||||
return okState(data.FinalTarget, fmt.Sprintf("target %s exists in DNS", data.FinalTarget))
|
||||
}
|
||||
return []sdk.CheckState{withHint(sdk.CheckState{
|
||||
Status: status,
|
||||
Status: sdk.StatusCrit,
|
||||
Subject: data.FinalTarget,
|
||||
Message: fmt.Sprintf("final target %s does not resolve to an address (%s)", data.FinalTarget, rcode),
|
||||
}, "Point the alias at a name that publishes at least one A or AAAA record, or fix the upstream zone.")}
|
||||
Message: fmt.Sprintf("final target %s does not exist (NXDOMAIN)", data.FinalTarget),
|
||||
}, "The alias points at a name that does not exist; create the missing record or update the alias target.")}
|
||||
}
|
||||
|
||||
type multipleRecordsRule struct{}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,41 @@ import (
|
|||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
type dnameCoexistenceRule struct{}
|
||||
|
||||
func (dnameCoexistenceRule) Name() string { return "dname_coexistence" }
|
||||
func (dnameCoexistenceRule) Description() string {
|
||||
return "Flags RRsets that sit at the same owner as a DNAME (RFC 6672 §2.3)."
|
||||
}
|
||||
|
||||
func (dnameCoexistenceRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
||||
data, errState := loadAlias(ctx, obs)
|
||||
if errState != nil {
|
||||
return errState
|
||||
}
|
||||
if !apexKnown(data) {
|
||||
return skipped("apex lookup failed")
|
||||
}
|
||||
if len(data.DNAMESubstitutions) == 0 {
|
||||
return skipped("no DNAME in chain")
|
||||
}
|
||||
if len(data.DNAMECoexistence) == 0 {
|
||||
return okState(data.Owner, "all DNAME nodes have no sibling records")
|
||||
}
|
||||
var out []sdk.CheckState
|
||||
for owner, coexisting := range data.DNAMECoexistence {
|
||||
for _, rr := range coexisting {
|
||||
out = append(out, withHint(sdk.CheckState{
|
||||
Status: sdk.StatusCrit,
|
||||
Subject: owner,
|
||||
Message: fmt.Sprintf("%s and DNAME both exist at %s (RFC 6672 §2.3)", rr.Type, owner),
|
||||
Code: rr.Type,
|
||||
}, "Remove the sibling record or move it under a different label; a DNAME owner must not carry other data."))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type cnameCoexistenceRule struct{}
|
||||
|
||||
func (cnameCoexistenceRule) Name() string { return "cname_coexistence" }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import (
|
|||
const (
|
||||
defaultMaxChainLength = 8
|
||||
defaultMinTargetTTL = 60
|
||||
defaultRequireResolvableTarget = true
|
||||
defaultAllowApexCNAME = false
|
||||
defaultRecognizeApexFlattening = true
|
||||
|
||||
|
|
|
|||
|
|
@ -266,6 +266,38 @@ func TestCnameCoexistenceRule(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestDnameCoexistenceRule(t *testing.T) {
|
||||
t.Run("skip when no DNAME in chain", func(t *testing.T) {
|
||||
d := apexKnownData()
|
||||
assertSkipped(t, run(dnameCoexistenceRule{}, d, nil), "no DNAME in chain")
|
||||
})
|
||||
t.Run("ok when DNAME has no siblings", func(t *testing.T) {
|
||||
d := apexKnownData()
|
||||
d.DNAMESubstitutions = []ChainHop{{Owner: "old.example.com.", Kind: KindDNAME, Target: "new.example.com."}}
|
||||
assertSingle(t, run(dnameCoexistenceRule{}, d, nil), sdk.StatusOK)
|
||||
})
|
||||
t.Run("crit when DNAME has siblings", func(t *testing.T) {
|
||||
d := apexKnownData()
|
||||
d.DNAMESubstitutions = []ChainHop{{Owner: "old.example.com.", Kind: KindDNAME, Target: "new.example.com."}}
|
||||
d.DNAMECoexistence = map[string][]CoexistingRRset{
|
||||
"old.example.com.": {{Type: "MX"}, {Type: "A"}},
|
||||
}
|
||||
states := run(dnameCoexistenceRule{}, d, nil)
|
||||
if len(states) != 2 {
|
||||
t.Fatalf("want 2 states, got %d: %+v", len(states), states)
|
||||
}
|
||||
for _, s := range states {
|
||||
if s.Status != sdk.StatusCrit {
|
||||
t.Fatalf("want CRIT, got %v", s.Status)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("skip when apex unknown", func(t *testing.T) {
|
||||
d := &AliasData{Owner: "x.", ApexLookupError: "boom"}
|
||||
assertSkipped(t, run(dnameCoexistenceRule{}, d, nil), "apex")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCnameDnssecRule(t *testing.T) {
|
||||
t.Run("skip unsigned zone", func(t *testing.T) {
|
||||
d := apexKnownData()
|
||||
|
|
@ -290,24 +322,25 @@ func TestCnameDnssecRule(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTargetResolvableRule(t *testing.T) {
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
t.Run("ok when NOERROR with A record", func(t *testing.T) {
|
||||
d := apexKnownData()
|
||||
d.ChainTerminated.Reason = TermOK
|
||||
d.FinalTarget = "target."
|
||||
d.FinalA = []string{"1.2.3.4"}
|
||||
assertSingle(t, run(targetResolvableRule{}, d, nil), sdk.StatusOK)
|
||||
})
|
||||
t.Run("crit by default", func(t *testing.T) {
|
||||
t.Run("ok when NOERROR with no A/AAAA (e.g. service label)", func(t *testing.T) {
|
||||
d := apexKnownData()
|
||||
d.ChainTerminated.Reason = TermOK
|
||||
d.FinalTarget = "target."
|
||||
assertSingle(t, run(targetResolvableRule{}, d, nil), sdk.StatusCrit)
|
||||
d.FinalTarget = "_2772._tcp.znc.example."
|
||||
assertSingle(t, run(targetResolvableRule{}, d, nil), sdk.StatusOK)
|
||||
})
|
||||
t.Run("warn when requireResolvableTarget=false", func(t *testing.T) {
|
||||
t.Run("crit when NXDOMAIN", func(t *testing.T) {
|
||||
d := apexKnownData()
|
||||
d.ChainTerminated.Reason = TermOK
|
||||
d.FinalTarget = "target."
|
||||
assertSingle(t, run(targetResolvableRule{}, d, sdk.CheckerOptions{"requireResolvableTarget": false}), sdk.StatusWarn)
|
||||
d.FinalRcode = "NXDOMAIN"
|
||||
assertSingle(t, run(targetResolvableRule{}, d, nil), sdk.StatusCrit)
|
||||
})
|
||||
t.Run("skip when chain did not terminate normally", func(t *testing.T) {
|
||||
d := apexKnownData()
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ type AliasData struct {
|
|||
|
||||
// Coexisting is populated only when Owner has a CNAME.
|
||||
Coexisting []CoexistingRRset `json:"coexisting,omitempty"`
|
||||
// DNAMECoexistence maps each DNAME owner (from DNAMESubstitutions) to its sibling RRsets.
|
||||
DNAMECoexistence map[string][]CoexistingRRset `json:"dname_coexistence,omitempty"`
|
||||
|
||||
OwnerIsApex bool `json:"owner_is_apex,omitempty"`
|
||||
OwnerHasCNAME bool `json:"owner_has_cname,omitempty"`
|
||||
|
|
|
|||
12
go.mod
12
go.mod
|
|
@ -3,14 +3,14 @@ module git.happydns.org/checker-alias
|
|||
go 1.25.0
|
||||
|
||||
require (
|
||||
git.happydns.org/checker-sdk-go v1.5.0
|
||||
git.happydns.org/checker-sdk-go v1.7.0
|
||||
github.com/miekg/dns v1.1.72
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/net v0.54.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
)
|
||||
|
|
|
|||
24
go.sum
24
go.sum
|
|
@ -1,16 +1,16 @@
|
|||
git.happydns.org/checker-sdk-go v1.5.0 h1:5uD5Cm6xJ+lwnhbJ09iCXGHbYS9zRh+Yh0NeBHkAPBY=
|
||||
git.happydns.org/checker-sdk-go v1.5.0/go.mod h1:aNAcfYFfbhvH9kJhE0Njp5GX0dQbxdRB0rJ0KvSC5nI=
|
||||
git.happydns.org/checker-sdk-go v1.7.0 h1:dSgo2js5mfXluLc6x0WWZ0MQULd9XV2GI9z0Usk+Qgw=
|
||||
git.happydns.org/checker-sdk-go v1.7.0/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=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue