jarld checks the reverse DNS of the public zones' MX and SPF addresses as the internet sees it, every 6 hours (Exposure page, jarl exposure mail, jarl_mail_reverse_dns_ok). The Server options page, jarl option generated and GET /options/generated list what Jarl writes into named.conf by itself; stats.rate_limit_log turns off the query-errors logging. Option lists of three or more elements are shown one per line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
127 lines
4.6 KiB
Go
127 lines
4.6 KiB
Go
package render
|
|
|
|
import (
|
|
"net/netip"
|
|
"strings"
|
|
"testing"
|
|
|
|
"jarl/internal/model"
|
|
)
|
|
|
|
func statsHooks() *StatsHooks {
|
|
return &StatsHooks{Channel: netip.MustParseAddrPort("127.0.0.1:8058"), LogFile: "/var/log/named/jarl-responses.log", LogVersions: 2, LogSize: "20m"}
|
|
}
|
|
|
|
func TestStatsHooksAdded(t *testing.T) {
|
|
s := sampleState(t)
|
|
// Without a logging statement of its own, Jarl adds one.
|
|
for id, o := range s.Options {
|
|
if o.Name == "logging" {
|
|
delete(s.Options, id)
|
|
}
|
|
}
|
|
p := Paths{ConfigDir: "/etc/bind/jarl", ZoneDir: "/var/lib/bind/jarl", CacheDir: "/var/cache/bind/jarl"}
|
|
conf := string(Render(s, p, Extras{Stats: statsHooks()}).File(p.MainConfig()).Content)
|
|
for _, want := range []string{
|
|
"statistics-channels {\n\tinet 127.0.0.1 port 8058 allow { 127.0.0.1; };\n};",
|
|
`file "/var/log/named/jarl-responses.log" versions 2 size 20m;`,
|
|
"\tcategory responses { jarl_responses; };",
|
|
"\tresponselog yes;\n",
|
|
} {
|
|
if !strings.Contains(conf, want) {
|
|
t.Errorf("named.conf lacks %q:\n%s", want, conf)
|
|
}
|
|
}
|
|
// Counters only: no logging, no responselog.
|
|
h := statsHooks()
|
|
h.LogFile = ""
|
|
conf = string(Render(s, p, Extras{Stats: h}).File(p.MainConfig()).Content)
|
|
if !strings.Contains(conf, "statistics-channels") || strings.Contains(conf, "responselog") || strings.Contains(conf, "jarl_responses") {
|
|
t.Errorf("counters-only config:\n%s", conf)
|
|
}
|
|
// Disabled: nothing.
|
|
conf = string(Render(s, p, Extras{}).File(p.MainConfig()).Content)
|
|
if strings.Contains(conf, "statistics-channels") || strings.Contains(conf, "responselog") {
|
|
t.Errorf("stats disabled but present:\n%s", conf)
|
|
}
|
|
}
|
|
|
|
func TestStatsHooksMerged(t *testing.T) {
|
|
s := sampleState(t)
|
|
add := func(scope, name, value string) {
|
|
id := int64(10000 + len(s.Options))
|
|
s.Options[id] = &model.Option{ID: id, Scope: scope, Name: name, Value: value, Position: int(id)}
|
|
}
|
|
for _, o := range s.Options {
|
|
if o.Name == "logging" {
|
|
o.Value = `{ channel default_log { stderr; severity info; }; category default { default_log; }; category responses { default_log; }; }`
|
|
}
|
|
}
|
|
add(model.ScopeTopLevel, "statistics-channels", `{ inet 127.0.0.1 port 8080 allow { 127.0.0.1; }; }`)
|
|
add(model.ScopeOptions, "responselog", "no")
|
|
out := checkWithNamed(t, s, Extras{Stats: statsHooks()})
|
|
var conf string
|
|
for _, f := range out.Files {
|
|
if f.Kind == KindConfig {
|
|
conf = string(f.Content)
|
|
}
|
|
}
|
|
if strings.Count(conf, "logging {") != 1 || strings.Count(conf, "statistics-channels {") != 1 {
|
|
t.Errorf("statements duplicated instead of merged:\n%s", conf)
|
|
}
|
|
for _, want := range []string{"port 8080", "port 8058", "category responses { default_log; jarl_responses; };", "responselog no;"} {
|
|
if !strings.Contains(conf, want) {
|
|
t.Errorf("named.conf lacks %q:\n%s", want, conf)
|
|
}
|
|
}
|
|
if strings.Contains(conf, "responselog yes") {
|
|
t.Errorf("Jarl overrode the user's responselog:\n%s", conf)
|
|
}
|
|
}
|
|
|
|
func TestStatsHooksWithNamed(t *testing.T) {
|
|
checkWithNamed(t, sampleState(t), Extras{Stats: statsHooks()})
|
|
}
|
|
|
|
// Rate limiting's log lines go to the response log unless
|
|
// stats.rate_limit_log is off; the list of what Jarl adds says so.
|
|
func TestRateLimitLogAndGenerated(t *testing.T) {
|
|
s := sampleState(t)
|
|
for id, o := range s.Options {
|
|
if o.Name == "logging" {
|
|
delete(s.Options, id)
|
|
}
|
|
}
|
|
s.Options[20000] = &model.Option{ID: 20000, Scope: model.ScopeOptions, Name: "rate-limit", Value: "{ responses-per-second 10; log-only yes; }", Position: 20000}
|
|
p := Paths{ConfigDir: "/etc/bind/jarl", ZoneDir: "/var/lib/bind/jarl", CacheDir: "/var/cache/bind/jarl"}
|
|
conf := string(Render(s, p, Extras{Stats: statsHooks()}).File(p.MainConfig()).Content)
|
|
if !strings.Contains(conf, "category query-errors { jarl_responses; default_syslog; };") {
|
|
t.Errorf("query-errors not logged:\n%s", conf)
|
|
}
|
|
gen := GeneratedStatements(s, p, Extras{Stats: statsHooks()})
|
|
var logging *Generated
|
|
for i := range gen {
|
|
if strings.HasPrefix(gen[i].Text, "logging") {
|
|
logging = &gen[i]
|
|
}
|
|
}
|
|
if logging == nil || !strings.Contains(logging.Text, "query-errors") || !strings.Contains(logging.Setting, "stats.rate_limit_log") {
|
|
t.Errorf("generated logging = %+v (all %+v)", logging, gen)
|
|
}
|
|
if !strings.HasPrefix(gen[0].Text, "include ") || gen[0].Where != "top level" {
|
|
t.Errorf("first generated = %+v", gen[0])
|
|
}
|
|
|
|
h := statsHooks()
|
|
h.NoRateLimitLog = true
|
|
conf = string(Render(s, p, Extras{Stats: h}).File(p.MainConfig()).Content)
|
|
if strings.Contains(conf, "query-errors") || !strings.Contains(conf, "category responses") {
|
|
t.Errorf("rate_limit_log off but query-errors logged:\n%s", conf)
|
|
}
|
|
for _, g := range GeneratedStatements(s, p, Extras{Stats: h}) {
|
|
if strings.Contains(g.Text, "query-errors") {
|
|
t.Errorf("listed while off: %+v", g)
|
|
}
|
|
}
|
|
}
|