Browse Source

Allow scoped Cloudflare API Tokens

pull/595/head
Wade Fitzpatrick 5 years ago
parent
commit
e045239a35
3 changed files with 211 additions and 139 deletions
  1. +52
    -0
      dns_scripts/Cloudflare-README.md
  2. +79
    -69
      dns_scripts/dns_add_cloudflare
  3. +80
    -70
      dns_scripts/dns_del_cloudflare

+ 52
- 0
dns_scripts/Cloudflare-README.md View File

@ -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

+ 79
- 69
dns_scripts/dns_add_cloudflare View File

@ -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<NumParts; j++)); do
testdomain+=".${dnarray[j]}"
done
# loop through domains at cloudflare
for k in "${!domains[@]}"; do
# if match found, then set domain and domain_id
if [[ "$testdomain" == "${domains[k]}" ]]; then
domain="$testdomain"
domain_id=${ids[k]}
i="$NumParts"
# 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]}"
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<NumParts; j++)); do
testdomain+=".${dnarray[j]}"
done
# loop through domains at cloudflare
for k in "${!domains[@]}"; do
# if match found, then set domain and zone_id
if [[ "$testdomain" == "${domains[k]}" ]]; then
domain="$testdomain"
zone_id=${ids[k]}
i="$NumParts"
fi
done
((i++))
done
((i++))
done
if [[ -z "$domain" ]]; then
echo 'domain name not found on your cloudflare account'
exit 3
if [[ -z "$domain" ]]; then
echo 'domain name not found on your cloudflare account'
exit 3
fi
fi
txt_record="_acme-challenge.${fulldomain%.$domain}"
resp=$(curl --silent "${curl_params[@]}" -X POST "$API/$domain_id/dns_records" \
resp=$(curl --silent "${curl_params[@]}" -X POST "$API/$zone_id/dns_records" \
--data "{\"type\":\"TXT\",\"name\":\"${txt_record}\",\"content\":\"$token\",\"ttl\":300}")
# if it failed (success:false) then give error message


+ 80
- 70
dns_scripts/dns_del_cloudflare View File

@ -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 removes a token from cloudflare DNS for the ACME challenge
# This script removes a TXT record from cloudflare DNS for the ACME challenge
# usage dns_del_cloudflare "domain name" "token (optional)"
# if token is not specified, then all tokens are removed.
# return codes are;
@ -15,7 +17,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
@ -24,83 +30,87 @@ if [[ -z "$fulldomain" ]]; 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]}"
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
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
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<NumParts; j++)); do
testdomain+=".${dnarray[j]}"
done
# loop through domains at cloudflare
for k in "${!domains[@]}"; do
# if match found, then set domain and domain_id
if [[ "$testdomain" == "${domains[k]}" ]]; then
domain="$testdomain"
domain_id=${ids[k]}
i="$NumParts"
# 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]}"
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
((i++))
done
if [[ -z "$domain" ]]; then
echo 'domain name not found on your cloudflare account'
exit 3
# 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<NumParts; j++)); do
testdomain+=".${dnarray[j]}"
done
# loop through domains at cloudflare
for k in "${!domains[@]}"; do
# if match found, then set domain and zone_id
if [[ "$testdomain" == "${domains[k]}" ]]; then
domain="$testdomain"
zone_id=${ids[k]}
i="$NumParts"
fi
done
((i++))
done
if [[ -z "$domain" ]]; then
echo 'domain name not found on your cloudflare account'
exit 3
fi
fi
curl_request="$API/$domain_id/dns_records?type=TXT&name=_acme-challenge.$fulldomain"
curl_request="$API/$zone_id/dns_records?type=TXT&name=_acme-challenge.$fulldomain"
if [[ ! -z "$token" ]]; then # if token specified, then use it
curl_request+="&content=$token"
fi
@ -131,7 +141,7 @@ while [[ "$resp" ]]; do # iterate through records returned
echo "Error: domain ID not found"
exit 2
fi
respd=$(curl --silent "${curl_params[@]}" -X DELETE "$API/$domain_id/dns_records/$id")
respd=$(curl --silent "${curl_params[@]}" -X DELETE "$API/$zone_id/dns_records/$id")
if [[ "${respd// }" == *'"success":false'* ]]; then
re='"message":"([^"]+)"'
if [[ "$respd" =~ $re ]]; then


Loading…
Cancel
Save