forked from 0k/0k-charms
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
331 lines
13 KiB
331 lines
13 KiB
# -*- mode: shell-script -*-
|
|
|
|
BIND_CONFIG_DIR=/etc/bind
|
|
|
|
|
|
## Generate bind config files in $cfgdir from YAML configuration $cfg
|
|
bind:cfg:generate() {
|
|
local cfg="$1" vars="$2" zone_file include_zone_statements all_domain \
|
|
domains expanded_domains zone_cfg
|
|
|
|
## __all__ domain is treated here like a normal domain
|
|
|
|
expanded_domains=""
|
|
while read-0 domain zone_cfg; do
|
|
if ! [[ "$domain" =~ ^[a-z0-9_\(\),.-]+$ ]]; then
|
|
err "Invalid domain '$domain' as key in ${WHITE}zones${NORMAL} records."
|
|
return 1
|
|
fi
|
|
domains=()
|
|
bind:parse:expand-in "$domain" domains "$vars" || return 1
|
|
for domain in "${domains[@]}"; do
|
|
expanded_domains=$(merge_yaml_str "$expanded_domains" \
|
|
"$(yaml_key_val_str "$domain" "$zone_cfg")")
|
|
done
|
|
done < <(e "$cfg" | shyaml key-values-0)
|
|
|
|
## __all__ domain is merged in other domain domain
|
|
|
|
all_domain=$(e "$expanded_domains" | shyaml get-value "__all__")
|
|
resolved_domains=""
|
|
while read-0 domain zone_cfg; do
|
|
if [ "$domain" == "__all__" ]; then
|
|
continue
|
|
fi
|
|
resolved_domains=$(merge_yaml_str \
|
|
"$resolved_domains" \
|
|
"$(yaml_key_val_str "$domain" "$all_domain")" \
|
|
"$(yaml_key_val_str "$domain" "$zone_cfg")")
|
|
done < <(e "$expanded_domains" | shyaml key-values-0)
|
|
|
|
include_zone_statements=
|
|
while read-0 domain zone_cfg; do
|
|
info "Considering ${WHITE}$domain${NORMAL} zone configuration"
|
|
zone_file="${BIND_CONFIG_DIR}/db.${domain}"
|
|
zone_hash_file="${BIND_CONFIG_DIR}/.db.${domain}.hash"
|
|
zone_def=$(bind:content:zone_definition \
|
|
"$domain" "$zone_cfg" "$vars") || return 1
|
|
zone_def_hash=$(H "$zone_def")
|
|
|
|
## We need to store the last serial as well as the last hash in
|
|
## the datastore to allow a full restore (will correct serials)
|
|
## without discontinuity of services.
|
|
|
|
current_hash=
|
|
current_serial=
|
|
if [ -e "${SERVICE_DATASTORE}${zone_hash_file}" ]; then
|
|
current_hash_content="$(cat "${SERVICE_DATASTORE}${zone_hash_file}")" || true
|
|
current_hash=${current_hash_content% *}
|
|
current_serial=${current_hash_content#* }
|
|
echo " found a previous hash file with serial: $current_serial" >&2
|
|
fi
|
|
|
|
if [ "$current_hash" == "$zone_def_hash" ]; then ## no need to change full serial
|
|
echo " can use previous serial as zone definition seems to have not changed" >&2
|
|
serial=${current_serial}
|
|
else
|
|
echo " needs a new serial as zone definition seems to have changed" >&2
|
|
date=$(date +%Y%m%d)
|
|
if [ "$date" == "${current_serial:0:8}" ]; then
|
|
serial="$date$(printf "%02d" "$((${current_serial: -2:2} + 1))")"
|
|
echo " increment serial to $serial" >&2
|
|
else
|
|
serial="${date}00"
|
|
echo " first definition for the current date" >&2
|
|
fi
|
|
fi
|
|
zone_def=$(e "$zone_def" | sed -r "s/%%SERIAL%%/$serial/g")
|
|
if ! [ -e "${SERVICE_CONFIGSTORE}${zone_file}" ] ||
|
|
[ "$zone_def" != "$(cat "${SERVICE_CONFIGSTORE}${zone_file}")" ]; then
|
|
echo " ${DARKYELLOW}writing${NORMAL} zone def file to config with serial $serial." >&2
|
|
e "${zone_def}" > "${SERVICE_CONFIGSTORE}${zone_file}"
|
|
else
|
|
echo " zone def already available in config with serial $serial." >&2
|
|
fi
|
|
if ! [ -e "${SERVICE_DATASTORE}${zone_hash_file}" ] ||
|
|
[ "$zone_def_hash $serial" != "$(cat "${SERVICE_DATASTORE}${zone_hash_file}")" ]; then
|
|
echo " ${DARKYELLOW}writing${NORMAL} zone file hash to data with serial $serial." >&2
|
|
e "$zone_def_hash $serial" > "${SERVICE_DATASTORE}${zone_hash_file}"
|
|
else
|
|
echo " zone def hash already available in data with serial $serial." >&2
|
|
fi
|
|
|
|
include_zone_statements+=$(
|
|
bind:content:include_zone_statement "$domain" "${zone_file}"
|
|
)$'\n'
|
|
config_hash=$(p0 "$config_hash" "$zone_def_hash" | md5_compat) || exit 1
|
|
done < <(e "$resolved_domains" | shyaml key-values-0)
|
|
|
|
e "$include_zone_statements" > "${SERVICE_CONFIGSTORE}${BIND_CONFIG_DIR}/named.conf.local"
|
|
}
|
|
|
|
|
|
bind:content:include_zone_statement() {
|
|
local domain="$1" zone_file="$2"
|
|
|
|
cat <<EOF
|
|
zone "${domain}" {
|
|
type master;
|
|
notify yes;
|
|
file "${zone_file}";
|
|
};
|
|
EOF
|
|
|
|
}
|
|
|
|
bind:check:is_time() {
|
|
local label="$1" value="$2"
|
|
if ! [[ "$value" =~ ^[0-9smhdw]+$ ]]; then
|
|
err "Invalid value for $label: '$value'." >&2
|
|
echo " Please use a number of seconds or use one-character time units (IE: 1w, 1h30, 3600)" >&2
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
bind:parse:expand-in() {
|
|
local value="$1" aname="$2" vars="$3"
|
|
|
|
if ! [[ "$value" =~ ^[a-z0-9_\(\)\ \$,.-]+$ ]]; then
|
|
err "Invalid value '$value' to expand."
|
|
return 1
|
|
fi
|
|
declare -n cur="$aname"
|
|
if [[ "$value" == *","* ]]; then
|
|
value="${value//\(/\{}"
|
|
value="${value//\)/\}}"
|
|
value="${value// /\\ }"
|
|
else
|
|
value="\"$value\""
|
|
fi
|
|
|
|
value="${value//\$/\$_vars_}"
|
|
while read-0 label v; do
|
|
local "_vars_$label"="$v"
|
|
done < <(e "$vars" | shyaml key-values-0)
|
|
eval "cur+=($value)" || {
|
|
err "Couldn't eval '$value'."
|
|
return 1
|
|
}
|
|
}
|
|
|
|
bind:content:zone_definition() {
|
|
local domain="$1" zone_cfg="$2" vars_cfg="$3" \
|
|
vtype cache_file="$CACHEDIR/$FUNCNAME.cache.$(H "$@")"
|
|
if [ -e "$cache_file" ]; then
|
|
# debug "$FUNCNAME: cache hit ($*)"
|
|
cat "$cache_file"
|
|
return 0
|
|
fi
|
|
|
|
## Zone definition is created with %%SERIAL%% as placeholder in
|
|
## the serial number intended final emplacement. This allows to
|
|
## check the output's hash of this function to decide if we need
|
|
## to change the serial.
|
|
|
|
zone_cfg=$(merge_yaml_str "
|
|
\$ttl: 1w
|
|
soa:
|
|
email: dns@${domain}
|
|
refresh: 8h
|
|
retry: 4h
|
|
expire: 1w
|
|
ttl: 1d
|
|
ns:
|
|
_: dns
|
|
" "$zone_cfg") || return 1
|
|
|
|
vars_cfg=$(merge_yaml_str "
|
|
mydomain: \"$domain\"
|
|
" "$vars_cfg") || return 1
|
|
|
|
while read-0 key subcfg; do
|
|
# echo "KEY: $key" >&2
|
|
# echo "SUBCONFIG:" >&2
|
|
# echo "$subcfg" | prefix " | " >&2
|
|
declare -A decls
|
|
ttl=
|
|
if [[ "$key" == *" "* ]]; then
|
|
ttl=${key#* }
|
|
key=${key%% *}
|
|
fi
|
|
case "$key" in
|
|
"\$"*)
|
|
case "$key" in
|
|
"\$ttl")
|
|
bind:check:is_time ttl "$subcfg" || return 1
|
|
;;
|
|
*)
|
|
echo "Unsupported variable '${key}'." >&2
|
|
return 1
|
|
esac
|
|
decls[vars]+="${key^^} $subcfg"$'\n'
|
|
;;
|
|
soa)
|
|
soa_values="$(printf "%-12s ; %s" "%%SERIAL%%" "serial")"$'\n'
|
|
for k in refresh retry expire ttl; do
|
|
value=$(e "$subcfg" | shyaml get-value "$k" 2>/dev/null) || {
|
|
err "Couldn't find any value for '$k' in '${domain}''s 'soa' config."
|
|
return 1
|
|
}
|
|
bind:check:is_time "soa.$k" "$value" || {
|
|
err "Error while reading soa definition of domain '$domain'."
|
|
return 1
|
|
}
|
|
soa_values+="$(printf "%-12s ; %s" "$value" "$k")"$'\n'
|
|
done
|
|
email=$(e "$subcfg" | shyaml get-value "email" 2>/dev/null) || {
|
|
err "Couldn't find any value for 'email' in '${domain}''s 'soa' config."
|
|
return 1
|
|
}
|
|
soa_values="$(e "$soa_values" | prefix " ")"
|
|
decls[soa]+="$(printf "%-28s IN SOA %s. %s. (" "@" "${domain}" "${email/@/.}")"$'\n'
|
|
decls[soa]+="${soa_values}"$'\n'")"$'\n'
|
|
|
|
;;
|
|
ns|mx|name|spf|txt)
|
|
while read-0 name name_cfg; do
|
|
if ! [[ "$name" =~ ^[a-z0-9_\(\),.-]+$ ]]; then
|
|
err "Invalid name '$name' in ${domain}'s $key records."
|
|
return 1
|
|
fi
|
|
names=()
|
|
bind:parse:expand-in "$name" names "$vars_cfg" || return 1
|
|
## values
|
|
vtype=$(e "$name_cfg" | shyaml get-type)
|
|
# echo TYPE: $vtype >&2
|
|
values=()
|
|
case "$vtype" in
|
|
"sequence")
|
|
array_read-0 values < <(e "$name_cfg" | shyaml get-values-0)
|
|
;;
|
|
"str")
|
|
values=("$(e "$name_cfg" | shyaml get-value)")
|
|
;;
|
|
*)
|
|
err "Unsupported type '$vtype' of '$key' in $domain's zone definition."
|
|
exit 1
|
|
;;
|
|
esac
|
|
values_expanded=()
|
|
for value in "${values[@]}"; do
|
|
case "$key" in
|
|
ns|name)
|
|
if ! [[ "$value" =~ ^[a-z0-9_\$\(\),.-]+$ ]]; then
|
|
err "Invalid value '$value' in ${domain}'s $key records for '$name'."
|
|
return 1
|
|
fi
|
|
## Append in values_expanded
|
|
bind:parse:expand-in "$value" values_expanded "$vars_cfg" || return 1
|
|
;;
|
|
mx)
|
|
## space is allowed (actually mandatory)
|
|
if ! [[ "$value" =~ ^[a-z0-9_\$\(\),.\ -]+$ ]]; then
|
|
err "Invalid value '$value' in ${domain}'s $key records for '$name'."
|
|
return 1
|
|
fi
|
|
## Append in values_expanded
|
|
bind:parse:expand-in "$value" values_expanded "$vars_cfg" || return 1
|
|
;;
|
|
spf|txt)
|
|
## No expansion for them
|
|
values_expanded+=("\"$value\"")
|
|
;;
|
|
esac
|
|
done
|
|
|
|
for name in "${names[@]}"; do
|
|
[ "$name" == "_" ] && name="@"
|
|
|
|
for value in "${values_expanded[@]}"; do
|
|
rkey="$key"
|
|
if [ "$key" == "name" ]; then
|
|
if [[ "$value" =~ ^([0-9]{1,3}\.){3,3}[0-9]{1,3}$ ]]; then
|
|
rkey="a"
|
|
elif [[ "$value" =~ ^([a-z0-9-]+\.?)+$ ]]; then
|
|
rkey="cname"
|
|
elif [ -z "$value" ]; then
|
|
err "Empty value for 'name' '$name', was '${values[@]}' before evaluation"
|
|
echo " in '$domain' zone definition." >&2
|
|
return 1
|
|
else
|
|
err "Unrecognised value for 'name': '$value'."
|
|
echo " in '$domain' zone definition." >&2
|
|
return 1
|
|
fi
|
|
fi
|
|
if [ -z "$value" ]; then
|
|
err "Empty value for 'name' '$name'."
|
|
echo " in '$domain' zone definition." >&2
|
|
return 1
|
|
fi
|
|
decls[$rkey]+=$(printf "%-15s %-12s IN %s %s" "$name" "$ttl" "${rkey^^}" "$value")$'\n'
|
|
done
|
|
done
|
|
done < <(e "$subcfg" | shyaml key-values-0)
|
|
;;
|
|
*)
|
|
err "Unknown keyword '$key' in '$domain' zone definition."
|
|
return 1
|
|
esac
|
|
done < <(e "$zone_cfg" | shyaml key-values-0)
|
|
for k in vars soa ns mx a cname spf txt; do
|
|
[ -z "${decls[$k]}" ] && continue
|
|
if [ "$k" != "vars" ]; then
|
|
echo
|
|
echo
|
|
echo ";; $k"
|
|
echo
|
|
fi
|
|
echo "${decls[$k]}" | grep -v "^$" | {
|
|
case "$k" in
|
|
a|ns|cname|spf|txt|vars)
|
|
sort
|
|
;;
|
|
soa|mx)
|
|
cat
|
|
;;
|
|
esac
|
|
}
|
|
done | tee "$cache_file"
|
|
}
|