diff --git a/README.md b/README.md index 65f2d0d..34b9243 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,7 @@ You don't have to do anything manually! 1. CloudFlare.com API 1. DNSPod.cn API +1. DNSimple API 1. CloudXNS.com API 1. GoDaddy.com API 1. OVH, kimsufi, soyoustart and runabove API @@ -315,6 +316,8 @@ You don't have to do anything manually! 1. ClouDNS.net API 1. Infoblox NIOS API (https://www.infoblox.com/) 1. VSCALE (https://vscale.io/) +1. Dynu API (https://www.dynu.com) + **More APIs coming soon...** diff --git a/deploy/README.md b/deploy/README.md index d8c2f57..232fdb4 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -21,8 +21,11 @@ acme.sh --deploy -d example.com --deploy-hook cpanel ## 2. Deploy ssl cert on kong proxy engine based on api. Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert). +Currently supports Kong-v0.10.x. -(TODO) +```sh +acme.sh --deploy -d ftp.example.com --deploy-hook kong +``` ## 3. Deploy the cert to remote server through SSH access. diff --git a/deploy/kong.sh b/deploy/kong.sh index e1873f3..d3a6bc4 100755 --- a/deploy/kong.sh +++ b/deploy/kong.sh @@ -1,13 +1,7 @@ #!/usr/bin/env sh - -# This deploy hook will deploy ssl cert on kong proxy engine based on api request_host parameter. -# Note that ssl plugin should be available on Kong instance -# The hook will match cdomain to request_host, in case of multiple domain it will always take the first -# one (acme.sh behaviour). -# If ssl config already exist it will update only cert and key not touching other parameter -# If ssl config doesn't exist it will only upload cert and key and not set other parameter -# Not that we deploy full chain -# See https://getkong.org/plugins/dynamic-ssl/ for other options +# If certificate already exist it will update only cert and key not touching other parameter +# If certificate doesn't exist it will only upload cert and key and not set other parameter +# Note that we deploy full chain # Written by Geoffroi Genot ######## Public functions ##################### @@ -31,14 +25,15 @@ kong_deploy() { _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" - #Get uuid linked to the domain - uuid=$(_get "$KONG_URL/apis?request_host=$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') - if [ -z "$uuid" ]; then - _err "Unable to get Kong uuid for domain $_cdomain" - _err "Make sure that KONG_URL is correctly configured" - _err "Make sure that a Kong api request_host match the domain" - _err "Kong url: $KONG_URL" - return 1 + #Get ssl_uuid linked to the domain + ssl_uuid=$(_get "$KONG_URL/certificates/$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + if [ -z "$ssl_uuid" ]; then + _debug "Unable to get Kong ssl_uuid for domain $_cdomain" + _debug "Make sure that KONG_URL is correctly configured" + _debug "Make sure that a Kong certificate match the sni" + _debug "Kong url: $KONG_URL" + _info "No existing certificate, creating..." + #return 1 fi #Save kong url if it's succesful (First run case) _saveaccountconf KONG_URL "$KONG_URL" @@ -48,12 +43,14 @@ kong_deploy() { #Set Header _H1="Content-Type: multipart/form-data; boundary=$delim" #Generate data for request (Multipart/form-data with mixed content) - #set name to ssl - content="--$delim${nl}Content-Disposition: form-data; name=\"name\"${nl}${nl}ssl" + if [ -z "$ssl_uuid" ]; then + #set sni to domain + content="--$delim${nl}Content-Disposition: form-data; name=\"snis\"${nl}${nl}$_cdomain" + fi #add key - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" #Add cert - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" #Close multipart content="$content${nl}--$delim--${nl}" #Convert CRLF @@ -61,17 +58,16 @@ kong_deploy() { #DEBUG _debug header "$_H1" _debug content "$content" - #Check if ssl plugins is aready enabled (if not => POST else => PATCH) - ssl_uuid=$(_get "$KONG_URL/apis/$uuid/plugins" | _egrep_o '"id":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"[a-zA-Z0-9\-\,\"_\:]*"name":"ssl"' | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') - _debug ssl_uuid "$ssl_uuid" + #Check if sslcreated (if not => POST else => PATCH) + if [ -z "$ssl_uuid" ]; then #Post certificate to Kong - response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins" "" "POST") + response=$(_post "$content" "$KONG_URL/certificates" "" "POST") else #patch - response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins/$ssl_uuid" "" "PATCH") + response=$(_post "$content" "$KONG_URL/certificates/$ssl_uuid" "" "PATCH") fi - if ! [ "$(echo "$response" | _egrep_o "ssl")" = "ssl" ]; then + if ! [ "$(echo "$response" | _egrep_o "created_at")" = "created_at" ]; then _err "An error occurred with cert upload. Check response:" _err "$response" return 1 diff --git a/dnsapi/README.md b/dnsapi/README.md index 12d76be..f53d8ad 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -422,31 +422,31 @@ acme.sh --issue --dns dns_cloudns -d example.com -d www.example.com ``` ## 22. Use Infoblox API - + First you need to create/obtain API credentials on your Infoblox appliance. - + ``` export Infoblox_Creds="username:password" export Infoblox_Server="ip or fqdn of infoblox appliance" ``` - + Ok, let's issue a cert now: ``` acme.sh --issue --dns dns_infoblox -d example.com -d www.example.com ``` - + Note: This script will automatically create and delete the ephemeral txt record. The `Infoblox_Creds` and `Infoblox_Server` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. ## 23. Use VSCALE API - + First you need to create/obtain API tokens on your [settings panel](https://vscale.io/panel/settings/tokens/). - + ``` VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" ``` - + Ok, let's issue a cert now: ``` acme.sh --issue --dns dns_vscale -d example.com -d www.example.com @@ -468,6 +468,31 @@ acme.sh --issue --dns dns_dynu -d example.com -d www.example.com The `Dynu_ClientId` and `Dynu_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +## 25. Use DNSimple API + +First you need to login to your DNSimple account and generate a new oauth token. + +https://dnsimple.com/a/{your account id}/account/access_tokens + +Note that this is an _account_ token and not a user token. The account token is +needed to infer the `account_id` used in requests. A user token will not be able +to determine the correct account to use. + +``` +export DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje" +``` + +To issue the cert just specify the `dns_dnsimple` API. + +``` +acme.sh --issue --dns dns_dnsimple -d example.com +``` + +The `DNSimple_OAUTH_TOKEN` will be saved in `~/.acme.sh/account.conf` and will +be reused when needed. + +If you have any issues with this integration please report them to +https://github.com/pho3nixf1re/acme.sh/issues. # Use custom API diff --git a/dnsapi/dns_dnsimple.sh b/dnsapi/dns_dnsimple.sh new file mode 100644 index 0000000..0bfe2b9 --- /dev/null +++ b/dnsapi/dns_dnsimple.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env sh + +# DNSimple domain api +# https://github.com/pho3nixf1re/acme.sh/issues +# +# This is your oauth token which can be acquired on the account page. Please +# note that this must be an _account_ token and not a _user_ token. +# https://dnsimple.com/a//account/access_tokens +# DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje" + +DNSimple_API="https://api.dnsimple.com/v2" + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_dnsimple_add() { + fulldomain=$1 + txtvalue=$2 + + if [ -z "$DNSimple_OAUTH_TOKEN" ]; then + DNSimple_OAUTH_TOKEN="" + _err "You have not set the dnsimple oauth token yet." + _err "Please visit https://dnsimple.com/user to generate it." + return 1 + fi + + # save the oauth token for later + _saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN" + + if ! _get_account_id; then + _err "failed to retrive account id" + return 1 + fi + + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _get_records "$_account_id" "$_domain" "$_sub_domain" + + if [ "$_records_count" = "0" ]; then + _info "Adding record" + if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then + if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then + _info "Added" + return 0 + else + _err "Unexpected response while adding text record." + return 1 + fi + fi + _err "Add txt record error." + else + _info "Updating record" + _extract_record_id "$_records" "$_sub_domain" + + if _dnsimple_rest \ + PATCH \ + "$_account_id/zones/$_domain/records/$_record_id" \ + "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then + + _info "Updated!" + return 0 + fi + + _err "Update error" + return 1 + fi +} + +# fulldomain +dns_dnsimple_rm() { + fulldomain=$1 + + if ! _get_account_id; then + _err "failed to retrive account id" + return 1 + fi + + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _get_records "$_account_id" "$_domain" "$_sub_domain" + _extract_record_id "$_records" "$_sub_domain" + + if [ "$_record_id" ]; then + + if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$_record_id"; then + _info "removed record" "$_record_id" + return 0 + fi + fi + + _err "failed to remove record" "$_record_id" + return 1 + +} + +#################### Private functions bellow ################################## +# _acme-challenge.www.domain.com +# returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + i=2 + previous=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + if [ -z "$h" ]; then + # not valid + return 1 + fi + + if ! _dnsimple_rest GET "$_account_id/zones/$h"; then + return 1 + fi + + if _contains "$response" 'not found'; then + _debug "$h not found" + else + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$previous) + _domain="$h" + + _debug _domain "$_domain" + _debug _sub_domain "$_sub_domain" + + return 0 + fi + + previous="$i" + i=$(_math "$i" + 1) + done + return 1 +} + +# returns _account_id +_get_account_id() { + _debug "retrive account id" + if ! _dnsimple_rest GET "whoami"; then + return 1 + fi + + if _contains "$response" "\"account\":null"; then + _err "no account associated with this token" + return 1 + fi + + if _contains "$response" "timeout"; then + _err "timeout retrieving account id" + return 1 + fi + + _account_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"email\":" | cut -d: -f2 | cut -d, -f1) + _debug _account_id "$_account_id" + + return 0 +} + +# returns +# _records +# _records_count +_get_records() { + account_id=$1 + domain=$2 + sub_domain=$3 + + _debug "fetching txt records" + _dnsimple_rest GET "$account_id/zones/$domain/records?per_page=100" + + if ! _contains "$response" "\"id\":"; then + _err "failed to retrieve records" + return 1 + fi + + _records_count=$(printf "%s" "$response" | _egrep_o "\"name\":\"$sub_domain\"" | wc -l | _egrep_o "[0-9]+") + _records=$response + _debug _records_count "$_records_count" +} + +# returns _record_id +_extract_record_id() { + _record_id=$(printf "%s" "$_records" | _egrep_o "\"id\":[^,]*,\"zone_id\":\"[^,]*\",\"parent_id\":null,\"name\":\"$_sub_domain\"" | cut -d: -f2 | cut -d, -f1) + _debug "_record_id" "$_record_id" +} + +# returns response +_dnsimple_rest() { + method=$1 + path="$2" + data="$3" + request_url="$DNSimple_API/$path" + _debug "$path" + + export _H1="Accept: application/json" + export _H2="Authorization: Bearer $DNSimple_OAUTH_TOKEN" + + if [ "$data" ] || [ "$method" = "DELETE" ]; then + _H1="Content-Type: application/json" + _debug data "$data" + response="$(_post "$data" "$request_url" "" "$method")" + else + response="$(_get "$request_url" "" "" "$method")" + fi + + if [ "$?" != "0" ]; then + _err "error $request_url" + return 1 + fi + _debug2 response "$response" + return 0 +}