From e045239a356219a2de71bf51e2a0727b4223614e Mon Sep 17 00:00:00 2001 From: Wade Fitzpatrick Date: Fri, 30 Oct 2020 17:00:56 +1000 Subject: [PATCH] Allow scoped Cloudflare API Tokens --- dns_scripts/Cloudflare-README.md | 52 +++++++++++ dns_scripts/dns_add_cloudflare | 148 ++++++++++++++++-------------- dns_scripts/dns_del_cloudflare | 150 ++++++++++++++++--------------- 3 files changed, 211 insertions(+), 139 deletions(-) create mode 100644 dns_scripts/Cloudflare-README.md diff --git a/dns_scripts/Cloudflare-README.md b/dns_scripts/Cloudflare-README.md new file mode 100644 index 0000000..f831cfb --- /dev/null +++ b/dns_scripts/Cloudflare-README.md @@ -0,0 +1,52 @@ +## Using Cloudflare DNS for LetsEncrypt domain validation + +### Enabling the scripts + +Set the following options in `getssl.cfg` (either global or domain-specific): + +``` +VALIDATE_VIA_DNS="true" +DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_cloudflare" +DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_cloudflare" +``` + +### Authentication + +There are 2 methods of authenticating with Cloudflare: + +1. API Keys - Account level, all-purpose tokens +2. API Tokens - Scoped and permissioned access to resources + +Both are configured from your profile in the [Cloudflare dashboard][1] + +[1]: https://dash.cloudflare.com/profile/api-tokens + +#### API Keys + +The **Zone ID** for the domain will be searched for programmatically. + +Set the following options in `getssl.cfg`: + +``` +export CF_EMAIL="..." # Cloudflare account email address +export CF_KEY="..." # Global API Key +``` + +#### API Tokens + +Cloudflare provides a template for creating an API Token with access to edit +zone records. Tokens must be created with at least '**DNS:Edit** permissions +for the domain to add/delete records. + +The API requires higher privileges to be able to list zones, therefore this +method also requires the **Zone ID** from the Overview tab in the Cloudflare +Dashboard. + +Set the following options in the domain-specific `getssl.cfg` + +``` +export CF_API_TOKEN="..." +export CF_ZONE_ID="..." +``` + +__Note__: API Keys will be used instead if also configured diff --git a/dns_scripts/dns_add_cloudflare b/dns_scripts/dns_add_cloudflare index ea323c1..b1a6a16 100755 --- a/dns_scripts/dns_add_cloudflare +++ b/dns_scripts/dns_add_cloudflare @@ -1,9 +1,11 @@ #!/usr/bin/env bash -# need to add your email address and API key to cloudflare below or set as env variables +# either configure here or export environment variables in getssl.cfg email=${CF_EMAIL:-''} key=${CF_KEY:-''} +api_token=${CF_API_TOKEN:-''} +zone_id=${CF_ZONE_ID:-''} -# This script adds a token to cloudflare DNS for the ACME challenge +# This script adds a TXT record to cloudflare DNS for the ACME challenge # usage dns_add_cloudflare "domain name" "token" # return codes are; # 0 - success @@ -14,7 +16,11 @@ key=${CF_KEY:-''} fulldomain="${1}" token="${2}" API='https://api.cloudflare.com/client/v4/zones' -curl_params=( -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H 'Content-Type: application/json' ) +if [[ -z "$api_token" ]]; then + curl_params=( -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H 'Content-Type: application/json' ) +else + curl_params=( -H "Authorization: Bearer $api_token" -H 'Content-Type: application/json' ) +fi # check initial parameters @@ -28,84 +34,88 @@ if [[ -z "$token" ]]; then exit 1 fi -if [[ -z "$email" ]]; then - echo "CF_EMAIL (email) parameter not set" - exit 1 -fi +if [[ -z "$api_token" ]]; then + if [[ -z "$email" ]]; then + echo "CF_EMAIL (email) parameter not set" + exit 1 + fi -if [[ -z "$key" ]]; then - echo "CF_KEY (key) parameter not set" - exit 1 + if [[ -z "$key" ]]; then + echo "CF_KEY (key) parameter not set" + exit 1 + fi fi -# get a list of all domain names from cloudflare -# If you have a lot, you may need add "&page=1&per_page=1000" and/or "&status=active" -resp=$(curl --silent "${curl_params[@]}" -X GET "$API") -re='"result":\[(([^][]*\[[^][]*])*[^][]*)]' # find result section -if [[ "${resp// }" =~ $re ]]; then - resp="${BASH_REMATCH[1]}" -fi - -# iterate through all sections to obtain a list of domains -while [[ "$resp" ]]; do - re='[^}{]*\{(([^}{]*\{[^}{]*})*[^}{]*)}(.*)' - if [[ "$resp" =~ $re ]]; then - first="${BASH_REMATCH[1]}" - resp="${BASH_REMATCH[3]}" +if [[ -z "$zone_id" ]]; then + # get a list of all domain names from cloudflare + # If you have a lot, you may need add "&page=1&per_page=1000" and/or "&status=active" + resp=$(curl --silent "${curl_params[@]}" -X GET "$API") + re='"result":\[(([^][]*\[[^][]*])*[^][]*)]' # find result section + if [[ "${resp// }" =~ $re ]]; then + resp="${BASH_REMATCH[1]}" fi - # remove subsections - leave only domain level - while [[ "$first" =~ (.*)[\[\{][^]\{\}[]*[\]\}](.*) ]]; do - first="${BASH_REMATCH[1]}${BASH_REMATCH[2]}" - done - re='"name":"([^"]*)"' - if [[ "$first" =~ $re ]]; then - domains=( "${domains[@]}" "${BASH_REMATCH[1]}" ) - else - echo "Error getting domain name" - exit 2 - fi - re='"id":"([^"]*)"' - if [[ "$first" =~ $re ]]; then - ids=( "${ids[@]}" "${BASH_REMATCH[1]}" ) - else - echo "Error getting domain id" - exit 2 - fi -done -# split required domain name into an array -dnarray=(${fulldomain//./ }) -# get number of parts in required domain name -NumParts=${#dnarray[@]} -# build a test domain name, starting with the largest, and reduce it -# until a match is found, set domain = first ( longest) match. -domain="" -i=1 -while [ $i -lt "$NumParts" ]; do - testdomain="${dnarray[i-1]}" - for ((j=i; j