#!/usr/bin/env bash # Read https://www.linode.com/docs/email/postfix/configure-spf-and-dkim-in-postfix-on-debian-8 first! # Run this script at the beginning of each month # (Optional) Email notification if the script is interrupted #function notify() { # printf 'Refer to the logs for further info.\n' | mail -s 'DKIM rotation process was interrupted' 'admin@example.com' #} # #trap notify SIGHUP SIGINT SIGTERM cf_prefix="https://api.cloudflare.com/client/v4" cf_email="admin@example.com" cf_api_key="my-cloudflare-api-key" config_dir="/etc/opendkim" declare -a cf_headers=( "-H" "X-Auth-Email: $cf_email" "-H" "X-Auth-Key: $cf_api_key" "-H" "Content-Type: application/json" ) declare -a domains=( "example.com" "instance.com" ) declare -A zone_list=() declare -A record_list=() function get_zones() { # $1: print results or not for domain in ${domains[@]}; do response=$(curl "${cf_headers[@]}" -sL "$cf_prefix/zones?name=$domain&status=active&match=all") zone_id=$(jq -r '.result[0].id' <<< "$response") zone_list["$domain"]="$zone_id" if [[ "$1" -eq 1 ]]; then printf 'Domain %s ID:\t%s\n' "$domain" "$zone_id" fi done } function get_txt_records() { # $1: domain record_list=() zone_id="${zone_list[$1]}" response=$(curl "${cf_headers[@]}" -sL "$cf_prefix/zones/$zone_id/dns_records?type=TXT&match=all") while read line; do name=$(sed "s/^\([^\t]*\)\t\(.*\)$/\1/" <<< "$line") record_id=$(sed "s/^\([^\t]*\)\t\(.*\)$/\2/" <<< "$line") record_list["$name"]="$record_id" done < <(jq -r '.result[] | .name + "\t" + .id' <<< "$response") } function delete_record() { # $1: domain, $2: record name response=$(curl -X DELETE "${cf_headers[@]}" -sL "$cf_prefix/zones/${zone_list[$1]}/dns_records/${record_list[$2]}") if [[ $? -ne 0 ]]; then return 1 fi if [[ $(jq -r '.success' <<< "$response") != "true" ]]; then return 1 fi return 0 } function create_record() { # $1: domain, $2: selector content=$( (tr -d $'\n' | sed 's/^.*"\(v=DKIM.*\)".*$/\1/' | tr -d '"' | tr -d $'\t' | tr -d ' ' | sed 's/;/; /g' | sed 's/rsa-sha256/sha256/') < "$config_dir/keys/$1-$2.txt" ) data=$(jq -r ".name = \"$2._domainkey.$1\" | .content = \"$content\" | .type = \"TXT\"" <<< '{}') curl -X POST "${cf_headers[@]}" -d "$data" -sL "$cf_prefix/zones/${zone_list[$1]}/dns_records" > /dev/null if [[ $? -ne 0 ]]; then return 1 fi for i in $(seq 1 10); do sleep 60 opendkim-testkey -d "$1" -s "$2" -k "$config_dir/keys/$1-$2.private" > /dev/null if [[ $? -eq 0 ]]; then return 0 fi done return 1 } function generate_dkim_keys() { # $1: domain, $2: selector if [[ -f "$config_dir/keys/$1-$2.private" ]]; then return 2 fi opendkim-genkey -b 2048 -h rsa-sha256 -r -s "$2" -d "$1" -D "$config_dir/keys" 2>&1 > /dev/null if [[ $? -ne 0 ]]; then return 1 else mv "$config_dir/keys/$2.private" "$config_dir/keys/$1-$2.private" chown opendkim:opendkim "$_" mv "$config_dir/keys/$2.txt" "$config_dir/keys/$1-$2.txt" chown opendkim:opendkim "$_" return 0 fi } function update_key_table() { # $1: domain, $2: selector sed "/$domain/d" -i "$config_dir/key.table" printf '%s\t%s:%s:%s/keys/%s.private\n' "$domain" "$domain" "$selector" "$config_dir" "$domain" >> "$config_dir/key.table" } function diff_months() { months1=$(( 10#${1:0:4} * 12 + 10#${1: -2} )) months2=$(( 10#${2:0:4} * 12 + 10#${2: -2} )) result=$(( $months1 - $months2 )) if [[ $result -le 0 ]]; then result=$(( - $result )) fi printf "$result" } function main() { selector=$(date +%Y%m) get_zones for domain in ${domains[@]}; do get_txt_records "$domain" declare -a to_delete=() for name in "${!record_list[@]}"; do if [[ "$name" == *"._domainkey.$domain" ]] && [[ "$name" != "_adsp._domainkey.$domain" ]] && [[ $(diff_months "${name:0:6}" "$selector") -gt 3 ]]; then to_delete+=("$name") fi done if [[ ${#to_delete[@]} -gt 0 ]]; then printf 'Records to delete for %s:\n' "$domain" for name in ${to_delete[@]}; do printf '\t%s\t...\t' "$name" delete_record "$domain" "$name" [[ $? -eq 0 ]] && printf 'deleted\n' || printf 'failed to delete\n' done fi generate_dkim_keys "$domain" "$selector" case $? in 2) printf 'DKIM key %s for %s already exists\n' "$selector" "$domain" ;; 1) printf 'Failed to generate DKIM key %s for %s\n' "$selector" "$domain" ;; 0) create_record "$domain" "$selector" if [[ $? -ne 0 ]]; then printf 'Failed to create DKIM record %s for %s\n' "$selector" "$domain" else sleep 60 ln -sf "$config_dir/keys/$domain-$selector.private" "$config_dir/keys/$domain.private" chown -h opendkim:opendkim "$_" update_key_table "$domain" "$selector" systemctl reload opendkim.service printf 'DKIM record %s for %s created\n' "$selector" "$domain" fi ;; esac done } if [[ "$USER" != "root" ]]; then printf 'Must run as root\n' exit 1 else main fi