Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ jobs:
echo "$out" | grep -q '^skipped' && { echo "::error::BIND missing on the runner"; exit 1; }
exit $rc

- name: test_plesk_acl.sh
run: bash hosting-panels/common/tests/test_plesk_acl.sh

# needs the bind9 installed by the step above
- name: test_plesk_acl_named.sh
run: |
out=$(bash hosting-panels/common/tests/test_plesk_acl_named.sh); rc=$?
echo "$out"
echo "$out" | grep -q '^skipped' && { echo "::error::BIND missing on the runner"; exit 1; }
exit $rc

- name: test_valid_ip.sh
run: bash hosting-panels/common/tests/test_valid_ip.sh

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Both integrations use the SecondDNS API key. See the README in each directory fo

- SecondDNS account and API key — [get one here](https://seconddns.com/dashboard/api-key)
- TCP port 53 open from your server to the SecondDNS secondary nameserver IP
- BIND or PowerDNS with `allow-transfer` and `also-notify` for the secondary IP — the installer configures this after asking, and keeps a backup of the file it edits
- BIND or PowerDNS with `allow-transfer` and `also-notify` for the secondary IP — the installer configures this after asking, and keeps a backup of the file it edits (on Plesk, transfers are opened through the Transfer Restrictions Template instead, see [Plesk](hosting-panels/plesk/))

---

Expand Down
12 changes: 9 additions & 3 deletions hosting-panels/common/tests/test_named_conf_edit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ block() { # $1 file, $2 block name -> prints the block
}

checked=0
for panel in cpanel plesk directadmin; do
for panel in cpanel directadmin; do
src="$HERE/../../$panel/install.sh"
[ -f "$src" ] || { echo "missing $src"; exit 1; }
grep -q "configure_named_axfr" "$src" || { echo "$panel: no configure_named_axfr"; exit 1; }
Expand Down Expand Up @@ -63,14 +63,20 @@ for panel in cpanel plesk directadmin; do
done

echo "== cheap greps: every installer verifies before reporting"
for panel in cpanel plesk directadmin; do
for panel in cpanel directadmin; do
src="$HERE/../../$panel/install.sh"
grep -qE 'if named-checkconf' "$src" && ok "$panel gates the reload on named-checkconf" || fail "$panel reloads without verifying"
grep -q "did not validate" "$src" && ok "$panel reports a broken config" || fail "$panel would claim success"
grep -qE 'cp "\$NAMED_BAK"' "$src" && ok "$panel restores the backup" || fail "$panel has no rollback"
done

[ "$checked" -ge 3 ] && ok "checked $checked installers" || fail "only $checked matched"
[ "$checked" -ge 2 ] && ok "checked $checked installers" || fail "only $checked matched"

echo "== plesk: zones carry allow-transfer { common-allow-transfer; }, so options must not be edited"
src="$HERE/../../plesk/install.sh"
grep -q "configure_named_axfr" "$src" && fail "plesk still edits the options block" || ok "plesk no longer edits options"
grep -q "allow-transfer { \$" "$src" && fail "plesk still tells the customer to put allow-transfer in server-wide settings" || ok "no allow-transfer advice for server-wide settings"
grep -q "server_dns -u -add-acl" "$src" && ok "plesk uses the transfer ACL" || fail "plesk does not use server_dns -add-acl"

echo
echo "passed=$PASS failed=$FAIL"
Expand Down
96 changes: 96 additions & 0 deletions hosting-panels/common/tests/test_plesk_acl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/bin/bash
# Copyright © 2025-2026 SecondDNS
# Licensed under GNU General Public License v3.0 or SecondDNS Commercial License
# See LICENSE (GPLv3) or LICENSE.COMMERCIAL (commercial) for details
# configure_plesk_acl, as the Plesk installer defines it, against a stand-in for the plesk
# CLI that behaves like Plesk 18.0.81 (checked on a live server): the transfer ACL lives in
# misc.DNS_Allow_Transfer*, and server_dns -u -add-acl adds to it and rewrites
# acl common-allow-transfer in named.conf at once.
# Requires: bash, awk. No network.
#
# Usage: ./test_plesk_acl.sh

set -u

HERE="$(cd "$(dirname "$0")" && pwd)"
SRC="$HERE/../../plesk/install.sh"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT

PASS=0; FAIL=0
ok() { PASS=$((PASS+1)); echo " ok: $1"; }
fail() { FAIL=$((FAIL+1)); echo " FAIL: $1"; }
assert_eq() { [ "$1" = "$2" ] && ok "$3" || fail "$3 (expected '$2', got '$1')"; }

# stand-in plesk: $TMP/acl is misc.DNS_Allow_Transfer*, one IP per line
cat > "$TMP/plesk" <<'EOF'
#!/bin/bash
T="$(dirname "$0")"
case "$1 $2" in
"db -Ne") cat "$T/acl" 2>/dev/null ;;
"bin server_dns")
echo "$*" >> "$T/calls"
[ -e "$T/add-fails" ] && exit 1
tr ',' '\n' <<< "${5}" >> "$T/acl"
[ -e "$T/no-regen" ] && exit 0
{ echo 'options { directory "/var"; };'
printf 'acl common-allow-transfer {\n'; sed 's/^/\t/; s/$/;/' "$T/acl"; printf '};\n'
echo 'zone "a.example" { type master; file "a.example"; allow-transfer { common-allow-transfer; }; };'
} > "$T/named.conf" ;;
esac
EOF
chmod +x "$TMP/plesk"
export SECONDDNS_PLESK="$TMP/plesk" SECONDDNS_PLESK_NAMED_CONF="$TMP/named.conf"

reset() {
rm -f "$TMP/acl" "$TMP/calls" "$TMP/add-fails" "$TMP/no-regen"
printf 'options { directory "/var"; };\nacl common-allow-transfer {\n\tnone;\n};\n' > "$TMP/named.conf"
}
acl() { awk '/^acl common-allow-transfer/,/^};/' "$TMP/named.conf" | grep -v -e acl -e '};' | tr -d '\t;' | tr '\n' ' '; }

grep -q '^configure_plesk_acl() {' "$SRC" || { echo "configure_plesk_acl not found in $SRC"; exit 1; }
eval "$(sed -n '/^configure_plesk_acl() {/,/^}/p' "$SRC")"

echo "== 1. empty ACL: both addresses added in one call, named.conf has them"
reset
out=$(configure_plesk_acl "203.0.113.9,2001:db8::9"); rc=$?
assert_eq "$rc" 0 "returns 0"
assert_eq "$(cat "$TMP/calls")" "bin server_dns -u -add-acl 203.0.113.9,2001:db8::9" "one add-acl call with both IPs"
assert_eq "$(acl)" "203.0.113.9 2001:db8::9 " "acl common-allow-transfer lists both"

echo "== 2. second run: nothing added"
: > "$TMP/calls"
out=$(configure_plesk_acl "203.0.113.9,2001:db8::9"); rc=$?
assert_eq "$rc" 0 "returns 0"
assert_eq "$(wc -l < "$TMP/calls" | tr -d ' ')" 0 "no add-acl call"
echo "$out" | grep -q "already allows" && ok "says it is already there" || fail "output: $out"

echo "== 3. one address present: only the other is added"
reset
echo "203.0.113.9" > "$TMP/acl"
configure_plesk_acl "203.0.113.9,2001:db8::9" > /dev/null
assert_eq "$(cat "$TMP/calls")" "bin server_dns -u -add-acl 2001:db8::9" "only the missing IP"

echo "== 4. a similar address is not taken for ours"
reset
echo "203.0.113.90" > "$TMP/acl"
configure_plesk_acl "203.0.113.9" > /dev/null
assert_eq "$(cat "$TMP/calls")" "bin server_dns -u -add-acl 203.0.113.9" "203.0.113.90 does not count as 203.0.113.9"

echo "== 5. named.conf not rewritten: reported, not claimed"
reset
touch "$TMP/no-regen"
out=$(configure_plesk_acl "203.0.113.9"); rc=$?
[ "$rc" -ne 0 ] && ok "returns non-zero" || fail "claimed success with the ACL still none"
echo "$out" | grep -q "not in acl common-allow-transfer" && ok "names the problem" || fail "output: $out"
echo "$out" | grep -q "Transfer Restrictions Template" && ok "says where to add it" || fail "no hint: $out"

echo "== 6. add-acl fails: reported"
reset
touch "$TMP/add-fails"
out=$(configure_plesk_acl "203.0.113.9"); rc=$?
[ "$rc" -ne 0 ] && ok "returns non-zero" || fail "claimed success after a failed add-acl"
assert_eq "$(acl)" "none " "ACL untouched"

echo
echo "passed: $PASS, failed: $FAIL"
[ "$FAIL" -eq 0 ]
97 changes: 97 additions & 0 deletions hosting-panels/common/tests/test_plesk_acl_named.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/bin/bash
# Copyright © 2025-2026 SecondDNS
# Licensed under GNU General Public License v3.0 or SecondDNS Commercial License
# See LICENSE (GPLv3) or LICENSE.COMMERCIAL (commercial) for details
# A real named with a Plesk-style named.conf: every zone has
# allow-transfer { common-allow-transfer; }. Our IP in the options block (what the old
# installer wrote) opens nothing; the same IP in the ACL, put there by
# configure_plesk_acl, does. Requires: named, dig. Skips itself when BIND is not installed.
#
# Usage: ./test_plesk_acl_named.sh

set -u

for t in named dig; do
command -v "$t" > /dev/null || { echo "skipped: $t not installed"; exit 0; }
done

HERE="$(cd "$(dirname "$0")" && pwd)"
SRC="$HERE/../../plesk/install.sh"
TMP="$(mktemp -d)"
PORT=5301
CONF="$TMP/named.conf"

PASS=0; FAIL=0
ok() { PASS=$((PASS+1)); echo " ok: $1"; }
fail() { FAIL=$((FAIL+1)); echo " FAIL: $1"; }

cat > "$TMP/a.example" <<'EOF'
$TTL 300
@ IN SOA ns1.a.example. hostmaster.a.example. 1 300 60 86400 60
@ IN NS ns1.a.example.
ns1 IN A 192.0.2.53
www IN A 192.0.2.10
EOF

# written the way Plesk writes it; $TMP/acl holds misc.DNS_Allow_Transfer*
write_conf() {
{ cat <<EOF
options {
directory "$TMP";
pid-file "$TMP/named.pid";
listen-on port $PORT { 127.0.0.1; };
listen-on-v6 { none; };
recursion no;
allow-transfer { 127.0.0.1; };
};
EOF
printf 'acl common-allow-transfer {\n'
if [ -s "$TMP/acl" ]; then sed 's/^/\t/; s/$/;/' "$TMP/acl"; else printf '\tnone;\n'; fi
printf '};\n'
cat <<EOF
zone "a.example" {
type master;
file "a.example";
allow-transfer {
common-allow-transfer;
};
};
EOF
} > "$CONF"
}

# the stand-in rewrites the config like Plesk does, then named is reloaded
cat > "$TMP/plesk" <<EOF
#!/bin/bash
TMP="$TMP"; PORT=$PORT; CONF="$CONF"
$(declare -f write_conf)
case "\$1 \$2" in
"db -Ne") cat "\$TMP/acl" 2>/dev/null ;;
"bin server_dns") tr ',' '\n' <<< "\${5}" >> "\$TMP/acl"; write_conf; kill -HUP \$(cat "\$TMP/named.pid"); sleep 1 ;;
esac
EOF
chmod +x "$TMP/plesk"
export SECONDDNS_PLESK="$TMP/plesk" SECONDDNS_PLESK_NAMED_CONF="$CONF"

write_conf
named -c "$CONF" -g > "$TMP/named.log" 2>&1 &
NAMED_PID=$!
disown $NAMED_PID 2>/dev/null || true
trap 'kill $NAMED_PID 2>/dev/null; rm -rf "$TMP"' EXIT
for i in $(seq 1 20); do dig +short -p $PORT @127.0.0.1 a.example SOA > /dev/null 2>&1 && break; sleep 0.5; done

transfers() { dig +tries=1 +time=3 -p $PORT @127.0.0.1 -b "$1" a.example AXFR 2>&1 | grep -q 'www.a.example.*192.0.2.10'; }

echo "== 1. our IP only in options (the old installer): no transfer"
transfers 127.0.0.1 && fail "options allow-transfer opened a Plesk zone" || ok "zone-level ACL overrides options: refused"

echo "== 2. configure_plesk_acl puts our IP in the ACL"
eval "$(sed -n '/^configure_plesk_acl() {/,/^}/p' "$SRC")"
out=$(configure_plesk_acl "127.0.0.1"); rc=$?
[ "$rc" -eq 0 ] && ok "configure_plesk_acl succeeds" || fail "configure_plesk_acl: $out"
transfers 127.0.0.1 && ok "our IP transfers" || fail "still refused after the ACL change"
transfers 127.0.0.2 && fail "an address outside the ACL transfers" || ok "other addresses stay refused"

echo
echo "passed: $PASS, failed: $FAIL"
[ "$FAIL" -eq 0 ]
16 changes: 10 additions & 6 deletions hosting-panels/plesk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,23 @@ The installer:

## AXFR Configuration

The installer configures `allow-transfer` and `also-notify` in `named.conf.options` directly. However, Plesk may overwrite these settings during config regeneration.
Plesk writes `allow-transfer { common-allow-transfer; };` into every zone, so `allow-transfer` in the BIND `options` block (or in **Server-wide Settings → Additional DNS settings**, which lands there) does not open transfers for Plesk zones. The ACL `common-allow-transfer` is what counts. Plesk builds it from **Tools & Settings → DNS Settings → Transfer Restrictions Template** and keeps it across config regeneration.

To make AXFR settings permanent, also add them via Plesk UI:
The installer adds the SecondDNS addresses to that template, after asking:

1. Go to **Tools & Settings** → **DNS Settings** → **Server-wide Settings**
2. In the **Additional DNS settings** field, add:
```bash
plesk bin server_dns -u -add-acl SECONDARY_IP[,SECONDARY_IPV6]
```

It skips addresses already listed and checks that they appear in `acl common-allow-transfer` in `named.conf`. On Plesk 18.0.81 the call rewrites `named.conf` and reloads named immediately. If an address does not show up, the installer says so; add it in **Transfer Restrictions Template** by hand.

NOTIFY is not set per zone, so the server-wide settings do reach it. For instant updates, add this to **Server-wide Settings → Additional DNS settings**:

```
allow-transfer { SECONDARY_IP; };
also-notify { SECONDARY_IP; };
```

Replace `SECONDARY_IP` with the IP shown by the installer. Click **Apply**.
The uninstaller does not remove the addresses from the Transfer Restrictions Template (`server_dns` has no command for it): remove them there by hand.

## DNS Template

Expand Down
Loading
Loading