Browse Source

Merge pull request #1219 from martgras/master

Add Azure DNS API
neil 7 years ago
parent
commit
b6d760b903
3 changed files with 269 additions and 0 deletions
  1. 1 0
      README.md
  2. 19 0
      dnsapi/README.md
  3. 249 0
      dnsapi/dns_azure.sh

+ 1 - 0
README.md

@@ -344,6 +344,7 @@ You don't have to do anything manually!
 1. Servercow (https://servercow.de)
 1. Servercow (https://servercow.de)
 1. Namesilo (https://www.namesilo.com)
 1. Namesilo (https://www.namesilo.com)
 1. InternetX autoDNS API (https://internetx.com)
 1. InternetX autoDNS API (https://internetx.com)
+1. Azure DNS
 
 
 And: 
 And: 
 
 

+ 19 - 0
dnsapi/README.md

@@ -684,6 +684,25 @@ acme.sh --issue --dns dns_autodns -d example.com -d www.example.com
 
 
 The `AUTODNS_USER`, `AUTODNS_PASSWORD` and `AUTODNS_CONTEXT` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 The `AUTODNS_USER`, `AUTODNS_PASSWORD` and `AUTODNS_CONTEXT` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
+## 37. Use Azure DNS
+
+You have to create a service principal first. See:[How to use Azure DNS](../../../wiki/How-to-use-Azure-DNS)
+
+```
+export AZUREDNS_SUBSCRIPTIONID="12345678-9abc-def0-1234-567890abcdef"
+export AZUREDNS_TENANTID="11111111-2222-3333-4444-555555555555"
+export AZUREDNS_APPID="3b5033b5-7a66-43a5-b3b9-a36b9e7c25ed"
+export AZUREDNS_CLIENTSECRET="1b0224ef-34d4-5af9-110f-77f527d561bd"
+```
+
+Then you can issue your certificates with:
+
+```
+acme.sh --issue --dns dns_azure -d example.com -d www.example.com
+```
+
+`AZUREDNS_SUBSCRIPTIONID`, `AZUREDNS_TENANTID`,`AZUREDNS_APPID` and `AZUREDNS_CLIENTSECRET` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
 # Use custom API
 # Use custom API
 
 
 If your API is not supported yet, you can write your own DNS API.
 If your API is not supported yet, you can write your own DNS API.

+ 249 - 0
dnsapi/dns_azure.sh

@@ -0,0 +1,249 @@
+#!/usr/bin/env sh
+
+########  Public functions #####################
+
+# Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+#
+# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/createorupdate
+#
+dns_azure_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
+  AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
+  AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
+  AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
+  
+  if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure Subscription ID "
+    return 1
+  fi
+
+  if [ -z "$AZUREDNS_TENANTID" ] ; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify then Azure Tenant ID "
+    return 1
+  fi
+
+  if [ -z "$AZUREDNS_APPID" ] ; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure App ID"
+    return 1
+  fi
+
+  if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure Client Secret"
+    return 1
+  fi
+  #save account details to account conf file.
+  _saveaccountconf_mutable AZUREDNS_SUBSCRIPTIONID "$AZUREDNS_SUBSCRIPTIONID"
+  _saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID"
+  _saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID"
+  _saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET"
+
+  accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
+
+  if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
+   _err "invalid domain"
+   return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+  _debug "$acmeRecordURI"
+  body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
+  _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken"
+  if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then
+    _info "validation record added"
+  else
+    _err "error adding validation record ($_code)"
+    return 1
+  fi   
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+#
+# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/delete
+#
+dns_azure_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}"
+  AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
+  AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
+  AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
+
+  if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure Subscription ID "
+    return 1
+  fi
+
+  if [ -z "$AZUREDNS_TENANTID" ] ; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure Tenant ID "
+    return 1
+  fi
+
+  if [ -z "$AZUREDNS_APPID" ]  ;then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify the Azure App ID"
+    return 1
+  fi
+
+  if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
+    AZUREDNS_SUBSCRIPTIONID=""
+    AZUREDNS_TENANTID=""
+    AZUREDNS_APPID=""
+    AZUREDNS_CLIENTSECRET=""
+    _err "You didn't specify Azure Client Secret"
+    return 1
+  fi
+
+  accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
+
+  if ! _get_root "$fulldomain"  "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
+   _err "invalid domain"
+   return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" |sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01"
+  _debug "$acmeRecordURI"
+  body="{\"properties\": {\"TTL\": 3600, \"TXTRecords\": [{\"value\": [\"$txtvalue\"]}]}}"
+  _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken"
+  if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then
+    _info "validation record removed"
+  else
+    _err "error removing validation record ($_code)"
+    return 1
+  fi
+}
+
+###################  Private functions below ##################################
+
+_azure_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  accesstoken="$4"
+
+  export _H1="authorization: Bearer $accesstoken"
+  export _H2="accept: application/json"
+  export _H3="Content-Type: application/json"
+  
+  _debug "$ep"
+  if [ "$m" != "GET" ]; then
+   _debug data "$data"
+   response="$(_post "$data" "$ep" "" "$m")"
+  else
+   response="$(_get "$ep")"
+  fi
+  _debug2 response "$response"
+
+  _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
+  _debug2 "http response code $_code"   
+
+  if [ "$?" != "0" ]; then
+   _err "error $ep"
+   return 1
+  fi
+  return 0
+}
+
+## Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service#request-an-access-token
+_azure_getaccess_token() {
+  TENANTID=$1
+  clientID=$2
+  clientSecret=$3
+
+  export _H1="accept: application/json"
+  export _H2="Content-Type: application/x-www-form-urlencoded"
+
+  body="resource=$(printf "%s" 'https://management.core.windows.net/'| _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret"| _url_encode)&grant_type=client_credentials"
+  _debug data "$body"
+  response="$(_post "$body" "https://login.windows.net/$TENANTID/oauth2/token" "" "POST" )"
+  accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+  _debug2 "response $response"
+
+  if [ -z "$accesstoken" ] ; then 
+    _err "no acccess token received"
+    return 1    
+  fi
+  if [ "$?" != "0" ]; then
+    _err "error $response"
+    return 1
+  fi
+  printf "%s" "$accesstoken"
+  return 0
+}
+
+_get_root() {
+  domain=$1
+  subscriptionId=$2
+  accesstoken=$3
+  i=2
+  p=1
+
+  ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
+  ## returns up to 100 zones in one response therefore handling more results is not not implemented
+  ## (ZoneListResult with  continuation token for the next page of results)
+  ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways
+  ##
+  _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" "$accesstoken"
+
+  # Find matching domain name is Json response
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug2 "Checking domain: $h"
+    if [ -z "$h" ]; then
+      #not valid
+      _err "Invalid domain"
+      return 1
+    fi
+
+    if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+      _domain_id=$(echo "$response" | _egrep_o "\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+      if [ "$_domain_id" ]; then
+        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        _domain=$h
+        return 0
+      fi
+      return 1
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}