Skip to content

Instantly share code, notes, and snippets.

@Jacke
Forked from Frederick888/dkim_rotation.sh
Created October 27, 2023 15:22
Show Gist options
  • Save Jacke/48d087aa36bdf344d0b62a16c489529c to your computer and use it in GitHub Desktop.
Save Jacke/48d087aa36bdf344d0b62a16c489529c to your computer and use it in GitHub Desktop.

Revisions

  1. @Frederick888 Frederick888 revised this gist Feb 5, 2022. No changes.
  2. @Frederick888 Frederick888 revised this gist Feb 5, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions dkim_rotation.sh
    Original file line number Diff line number Diff line change
    @@ -105,8 +105,8 @@ function update_key_table() {
    }

    function diff_months() {
    months1=$(( ${1:0:4} * 12 + ${1: -2} ))
    months2=$(( ${2:0:4} * 12 + ${2: -2} ))
    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 ))
  3. @Frederick888 Frederick888 revised this gist Mar 15, 2019. 1 changed file with 7 additions and 0 deletions.
    7 changes: 7 additions & 0 deletions dkim_rotation.sh
    Original file line number Diff line number Diff line change
    @@ -3,6 +3,13 @@
    # 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' '[email protected]'
    #}
    #
    #trap notify SIGHUP SIGINT SIGTERM

    cf_prefix="https://api.cloudflare.com/client/v4"
    cf_email="[email protected]"
    cf_api_key="my-cloudflare-api-key"
  4. @Frederick888 Frederick888 created this gist Mar 10, 2019.
    159 changes: 159 additions & 0 deletions dkim_rotation.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,159 @@
    #!/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

    cf_prefix="https://api.cloudflare.com/client/v4"
    cf_email="[email protected]"
    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=$(( ${1:0:4} * 12 + ${1: -2} ))
    months2=$(( ${2:0:4} * 12 + ${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