Hurricane Electric's IPv6 Tunnel Broker Forums

DNS.HE.NET Topics => General Questions & Suggestions => Topic started by: finalbeta on April 13, 2016, 01:43:01 PM

Title: DNS ACME challenge. (Let's encrypt validation)
Post by: finalbeta on April 13, 2016, 01:43:01 PM
Hello,

I've had a look (used) at the let's encrypt project. it allows everyone to obtain (free) certificates for their website (and other services).
To retrieve a certificate, they require you to validate that you actually control the service/domain.
Two methods exist that allow this validation.

1) Place a challenge accessible on your web site. Port 80 or 433, so the let's encrypt servers can validate that you control the server the certificate points to.
2) Place a challenge inside a TXT record. This has the added advantage that validation can happen for services other then webservers running on port 80/443. (I'm thinking of VPN, alternative port webservers, media servers etc etc).
Validity of let's encrypt certificates is 90 days. Thus renewing of certificates can happen +- every 60 days. Automation is a must.

I would like to use this functionality (DNS validation) for my HE hosted domain. (I believe this question will become more and more frequent)
To make a long story short, can you please extend the dynamic DNS functionality to TXT records? This will allow me to script an update of a TXT record so validation can happen.

(Some more info https://letsencrypt.github.io/acme-spec/#rfc.section.7.4 )
PS: Thank you for providing me with great dynamic DNS for years!
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: HQuest on June 26, 2016, 04:32:35 PM
While at it, static CAA records can be another alternative to dynamic TXT records.

example.org. CAA 1 issue "letsencrypt.org" <-- req'd
example.org. CAA 1 iodef "mailto:caa@example.org" <-- currently optional/not yet supported by LE
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: dig1234 on July 11, 2016, 01:21:29 PM
+1 this would be great. I really want to be able to use LE certs with HE.net dynamic dns. It's already supported by many other DNS providers but no one that rocks the house like HE!
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: seeed on February 17, 2017, 07:05:30 AM
So first of all, i am well aware that this topic is quite old.
However, the issue still persists and HE does not provide an API to update TXT records dynamically.

Therefore i took the time to create a rudimentary  bash script which basically logs into the Website,
parses the actual HTML code (very ugly) and finally updates the desired DNS record.

Use at your own risk, improvements are welcome.
Note, that the _acme-challenge.$host TXT record has to exist beforehand!

#!/bin/bash

#
# $1 is supposed to be the hostname
# $2 is supposed to be the acme-response, edited in the TXT record
#
# Note: The _acme-challenge.$host TXT record has to exist beforehand!

HENET='https://dns.he.net/'
HENET_USERNAME=''
HENET_PASS='''

#
# Get initial cookie from HE.net
#
cookie=$(mktemp)
wget --save-cookies $cookie --keep-session-cookies -q $HENET

#
# Log in using your username and password
# store the resulting page
#
result=$(mktemp)
wget --load-cookies $cookie  --post-data "email=$HENET_USERNAME&pass=$HENET_PASS" -q -O $result $HENET

#
# Every zone has its own key we need to parse, e.g.
# 'alt="delete"  onclick="delete_dom(this);" name="example.org" value="123456789"'
# save in the format 'example.org;123456789'
#
domains=$(mktemp)
grep 'alt="delete"  onclick="delete_dom(this);" name="' $result | sed -e 's/.*alt="delete"  onclick="delete_dom(this);" name="//g' -e 's/" value="/;/g' -e 's/".*//g' > $domains

# Find host in domainlist and return key
domain_key=$(awk -v host=$1 -F\; '
host ~ $1 {r=$2}
END {print r}' $domains)

#
# Every dns entry has its own key we need to parse from the actual domain page
# 'onclick="event.cancelBubble=true;deleteRecord('1103350666','_acme-challenge.www.kleinet.at','TXT')" '
#
wget --load-cookies $cookie -q -O $result "$HENET?hosted_dns_zoneid=$domain_key&menu=edit_zone&hosted_dns_editzone"
host_key=$(grep "_acme-challenge.$1','TXT')" $result | sed -e 's/.*onclick="event.cancelBubble=true;deleteRecord(.//g' -e "s/','_acme-challenge.$1','TXT').*//g")

#
# Perform the actual 'edit'
#
wget --load-cookies $cookie --post-data "menu=edit_zone&Type=txt&hosted_dns_zoneid=$domain_key&hosted_dns_recordid=$host_key&hosted_dns_editzone=1&Name=_acme-challenge.$1&Content=%22$2%22&TTL=600&hosted_dns_editrecord=Update" -q -O $result $HENET

#
# On success, the Website returns the following String, if you explicitly want to return 0 or 1
# 'Successfully updated record'
#

rm $cookie $result $domains


Honestly, i'm posting this to push HE to implement the actual API ;)
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: passport123 on February 19, 2017, 07:45:40 AM
Quote from: seeed on February 17, 2017, 07:05:30 AM
...Therefore i took the time to create a rudimentary  bash script which basically logs into the Website,
parses the actual HTML code (very ugly) and finally updates the desired DNS record.
...


When I've had to do things like this, I've used QA automation scripts.  There was a free (i.e., no-charge) QA automation tool available a few years ago when I last needed to do this.  I don't remember its name, but google should be helpful...
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: spil on February 19, 2017, 12:02:17 PM
I've been playing with this over the weekend using Python. Both to get dns-01 validation working with HE.net and also to learn a bit about Python.

Parsing the HTML is indeed arduous as noted by seeed. ;D

This isn't fully functional yet, but I have been able to get the list of domains, delete and add records...

#!/usr/local/bin/python2.7
from requests import Request, Session
from lxml import html, etree

# All classes will work with the session
sess = Session()

class HEsession(object):

    def __init__(self, sess, user_id, user_pw):
        self.user_id = user_id
        self.user_pw = user_pw
        # Authenticate to HE.net DNS service
        post_data = { 'email' : self.user_id,
                      'pass'  : self.user_pw,
                      'submit': 'Login!' }
        resp = sess.post('https://dns.he.net/',
                         data = post_data)
        # Store landing page for later reference
        self.landing = html.fromstring(resp.content)
        # Check for login error
        selector = "//div[@class='caption']/../div[@id='dns_err']/text()"
        nodes = self.landing.xpath(selector)
        if nodes == 'Incorrect':
            print "Login incorrect"
            quit()
        # Get list of domains + id's (not reverse domains)
        selector = "//img[@alt='delete']" \
                        "[not(substring(@name, string-length(@name) - 4) = '.arpa')]" \
                   "/@*[name()='value' or name()='name']"
        nodes = self.landing.xpath(selector)
        # Transform to a dictionary of domain: id
        self.domains = dict(zip(nodes[0::2], nodes[1::2]))

    def getDomains(self):
        return self.domains
       
    def getZoneId(self,domain):
        return self.domains[domain]
   
    def getRR(self,domain, type):
        if self.domains is None:
            self.login()

    def delACMERR(self,domain):
        post_data = { 'hosted_dns_zoneid':   zoneid,
                      'menu':                "edit_zone",
                      'hosted_dns_editzone': ""}
        resp = self.sess.post('https://dns.he.net/',
                              data = post_data)
        tree = html.fromstring(resp.content)
        selector = "//tr[td='_acme-challenge." \
                   + domain \
                   + "']/td/img[@data='TXT']/../../td[@class='dns_delete']/@onclick"
        nodes = tree.xpath(selector)

class ResourceRecord(object):
        def __init__(self, id, name, type, data)
            self.id   = id
            self.name = name
            self.type = type
            self.data = data

class HEdomain(object):
    Domain = etree.Element('Domain')

    def __init__(self, sess, HEnet, domain):
        # Get the ZoneID
        self.zoneid = HEnet.getZoneId(domain)
        post_data = { 'hosted_dns_zoneid':   self.zoneid,
                      'menu':                "edit_zone",
                      'hosted_dns_editzone': ""}
        resp = sess.post('https://dns.he.net/',
                         data = post_data )
        tree = html.fromstring(resp.content)
        # Extract Id, name, type and data
        selector =  "//tr[@class='dns_tr']/td[2]/text()" \
                    "|//tr[@class='dns_tr']/td[3]/text()" \
                    "|//tr[@class='dns_tr']/td[4]/img/@data" \
                    "|//tr[@class='dns_tr']/td[7]/@data"
        nodes = tree.xpath(selector)
        # Transform into a simple XML structure
        Ids   = nodes[0::4]
        names = nodes[1::4]
        types = nodes[2::4]
        datas = nodes[3::4]
        for i in range(len(Ids)):
            ResR = etree.Subelement(self.Domain, 'Record', id=Ids[i])
            Name = etree.SubElement(RR, 'Name')
            Name.text = names[i]
            Type = etree.SubElement(RR, 'Type')
            Type.text = types[i]
            Data = etree.Subelement(RR, 'Data')
            Data.text = datas[i]
       
    def listRRs():
        for i in range(len(self.Ids)):
            print "Id: %s, Name: %s, Type: %s, Data %s" % \
                (self.Ids[i], self.names[i], self.types[i], self.datas[i])

    def getRecord(id)
        selector = "//Record[Type='TXT'][Name='_acme.brnrd.eu']"
       
    def delRecord(recordid)
        post_data = { 'hosted_dns_zoneid':     self.zoneid,
                      'hosted_dns_recordid':   recordid,
                      'menu':                  "edit_zone",
                      'hosted_dns_delconfirm': "delete",
                      'hosted_dns_delrecord':  "1",
                      'hosted_dns_editzone':   "1"}

    def addRecord()
        post_data = {
            'menu': "edit_zone"
            'Type': "TXT"
            'hosted_dns_zoneid':501311
            'hosted_dns_editzone':1
            'Priority':
            'Name': "_acme-challenge"
            'Content': "testing123"
            'TTL': "900"
            'hosted_dns_editrecord': "Submit" }
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: garyfowler on February 19, 2017, 07:36:35 PM
Added an additional DNS api called dns_manual..
I just modified the dns_myapi.sh

The allows the following command to work effectively.

acme.sh --signcsr --csr /somedir/someweb.csr --dns dns_manual

The result is that the FQDM you need to modify and the associated key string are output for you to manually key into your DNS interface.
The script pauses for you press ENTER.. and the acme.sh waits an additional 120 seconds for DNS records to sync etc.


----

########  Public functions #####################

#Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_manual_add() {
  fulldomain=$1
  txtvalue=$2
  _info "Using myapi"
  _debug fulldomain "$fulldomain"
  _debug txtvalue "$txtvalue"
  _err "Not implemented!"

  echo
  echo "Create TXT Record ${fulldomain}  with value  ${txtvalue}"
  echo
  echo
  echo "Press ENTER when done."
  read somevariable
  return 0
}

#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_manual_rm() {
  fulldomain=$1
  txtvalue=$2
  _info "Using myapi"
  _debug fulldomain "$fulldomain"
  _debug txtvalue "$txtvalue"
  echo
  echo "Remove TXT Record ${fulldomain}  with value  ${txtvalue}"
  echo
  echo
  echo "Press ENTER when done."
  read somevariable
}

####################  Private functions below ##################################
Title: Script to create/update/delete records
Post by: adamel on March 17, 2017, 03:25:21 AM
I created a simple script that can create/update/delete records in HE DNS, which I'm now using with https://github.com/lukas2511/dehydrated

In the deploy_challenge hook I have:
hednsupdate _acme-challenge.${DOMAIN}. TXT "${TOKEN_VALUE}" 300

And in clean_challenge:
hednsupdate _acme-challenge.${DOMAIN}. DELETE

#!/bin/sh

url=https://dns.he.net/

# These should be set in .hedns-credentials
login=
password=
zoneid=
# Optionally override these.
ttl=300

if [ -e ${HOME}/.hedns-credentials ]; then
    . ${HOME}/.hedns-credentials
fi

function usage()
{
    prog=$(basename $0)
    cat <<EOF
Create/update:
$prog NAME TYPE CONTENT [TTL]
Delete:
$prog NAME "DELETE"
EOF
}

record=$1
type=$2
content=$3
usettl=${4-${ttl}}

if [ $# -lt 2 ] || [ $# -lt 3 -a "${type}" != DELETE ]; then
    usage
    exit 1
fi

cookies=${HOME}/.hedns-cookies.txt
touch ${cookies}
chmod 0600 ${cookies}

function get_id()
{
    curl -sS --cookie ${cookies} "${url}?hosted_dns_zoneid=${zoneid}&menu=edit_zone&hosted_dns_editzone" | grep -B5 ">${record}" | sed -nre 's/.*dns_tr.* id="([^"]*)".*/\1/p' | head -n 1
}

# Get any initial cookies.
curl -sS --cookie-jar ${cookies} ${url} -o /dev/null
# Login.
curl -sS --cookie ${cookies} --cookie-jar ${cookies} --data "email=${login}&pass=${password}&submit=Login%21" ${url} -o /dev/null

# Check if we should update or create new.
recordid=$(get_id)
if [ "${type}" == DELETE ]; then
    if [ ! "${recordid}" ]; then
echo "No such record"
exit 1
    fi
    curl -sS --cookie ${cookies} --data "menu=edit_zone&hosted_dns_zoneid=${zoneid}&hosted_dns_recordid=${recordid}&hosted_dns_editzone=1&hosted_dns_delrecord=1" "${url}index.cgi" | grep "^${record}"
    [ $? -ne 0 ]
    exit
fi
if [ "${recordid}" ]; then
    op=Update
else
    op=Submit
fi
curl -sS --cookie ${cookies} --data "account=&menu=edit_zone&Type=${type}&hosted_dns_zoneid=${zoneid}&hosted_dns_recordid=${recordid}&hosted_dns_editzone=1&Priority=&Name=${record}&Content=${content}&TTL=${usettl}&hosted_dns_editrecord=${op}" "${url}?hosted_dns_zoneid=${zoneid}&menu=edit_zone&hosted_dns_editzone" | grep "^${record}"
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: matthiaspfaller on April 14, 2017, 06:55:40 AM
Hi,

thank's for your script. I modified it a little to make it possible to specify more than one zoneid. You can put something like this into your credentials file:

zoneids[my.domain.]=123456
zoneids[sub.my.domain.]=123457

Please note that the trailing "." is necessary. When you just specify

zoneid=123456

everything should work as before. The downside is that it's bash specific now.


#!/usr/local/bin/bash

url=https://dns.he.net/

# These should be set in .hedns-credentials
login=
password=
zoneid=
# Optionally override these.
ttl=300

declare -A zoneids
if [ -e /usr/local/etc/dehydrated/hedns-credentials ]; then
    . /usr/local/etc/dehydrated/hedns-credentials
fi

function usage()
{
    prog=$(basename $0)
    cat <<EOF
Create/update:
$prog NAME TYPE CONTENT [TTL]
Delete:
$prog NAME "DELETE"
EOF
}

function hasdot()
{
    case $1 in
        *.*) return 0 ;;
        *)   return 1 ;;
    esac
}

record=$1
type=$2
content=$3
usettl=${4-${ttl}}

if [ $# -lt 2 ] || [ $# -lt 3 -a "${type}" != DELETE ]; then
    usage
    exit 1
fi

cookies=/usr/local/etc/dehydrated/hedns-cookies.txt
touch ${cookies}
chmod 0600 ${cookies}

i=$record
while hasdot $i; do
    if [ "${zoneids[$i]}" != "" ]; then
        zoneid=${zoneids[$i]}
        break
    fi
    i=${i#*.}
done

function get_id()
{
    curl -sS --cookie ${cookies} "${url}?hosted_dns_zoneid=${zoneid}&menu=edit_zone&hosted_dns_editzone" | grep -B5 ">${record}" | sed -nre 's/.*dns_tr.* id="([^"]*)".*/\1/p' | head -n 1
}

# Get any initial cookies.
curl -sS --cookie-jar ${cookies} ${url} -o /dev/null
# Login.
curl -sS --cookie ${cookies} --cookie-jar ${cookies} --data "email=${login}&pass=${password}&submit=Login%21" ${url} -o /dev/null

# Check if we should update or create new.
recordid=$(get_id)
if [ "${type}" == DELETE ]; then
    if [ ! "${recordid}" ]; then
        echo "No such record"
        exit 1
    fi
    curl -sS --cookie ${cookies} --data "menu=edit_zone&hosted_dns_zoneid=${zoneid}&hosted_dns_recordid=${recordid}&hosted_dns_editzone=1&hosted_dns_delrecord=1" "${url}index.cgi" | grep "^${record}"
    [ $? -ne 0 ]
    exit
fi
if [ "${recordid}" ]; then
    op=Update
else
    op=Submit
fi
curl -sS --cookie ${cookies} --data "account=&menu=edit_zone&Type=${type}&hosted_dns_zoneid=${zoneid}&hosted_dns_recordid=${recordid}&hosted_dns_editzone=1&Priority=&Name=${record}&Content=${content}&TTL=${usettl}&hosted_dns_editrecord=${op}" "${url}?hosted_dns_zoneid=${zoneid}&menu=edit_zone&hosted_dns_editzone" | grep "^${record}"
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: angel333 on May 20, 2017, 07:05:47 AM
Here's my quick and dirty take on this - I needed to renew all my domains so I wrote a hook script certbot. Note that it takes a session ID instead of login details. You can obtain a session id from your browser (look for a cookie named CGISESSID). On the other hand, you don't have to fiddle with zone IDs, the script will figure them out.

Here's how you renew all domains:


HE_SESSID=<session_id> certbot renew --preferred-challenges dns --manual-auth-hook /path/to/the/certbot-he-hook.sh  --manual-public-ip-logging-ok


Validating a new domain works too:


HE_SESSID=<session_id> certbot certonly --manual -d <requested.domain.com> -m your@email.com --preferred-challenges dns --manual-auth-hook /path/to/certbot-he-hook.sh  --manual-public-ip-logging-ok


Here's the script:


#!/bin/bash

TLD=$(echo $CERTBOT_DOMAIN | grep -Eo '[a-z0-9]+$')
SLD=$(echo $CERTBOT_DOMAIN | grep -Eo '[a-z0-9]+\.[a-z0-9]+$' | grep -Eo '^[a-z0-9]+')
HE_ZONEID=$(curl --stderr - --cookie CGISESSID=$HE_SESSID https://dns.he.net/index.cgi \
  | grep -Eo "delete_dom.*name=\"$SLD\.$TLD\" value=\"[0-9]+" | grep -Eo "[0-9]+$")

curl --stderr - -o /dev/null --cookie CGISESSID=$HE_SESSID https://dns.he.net/index.cgi \
  -d "account=&menu=edit_zone&Type=TXT&hosted_dns_zoneid=$HE_ZONEID&hosted_dns_recordid=&hosted_dns_editzone=1&Priority=&Name=_acme-challenge.$CERTBOT_DOMAIN&Content=$CERTBOT_VALIDATION&TTL=300&hosted_dns_editrecord=Submit"


It's also on gist: https://gist.github.com/angel333/1aae17b1ea53a9cd538966979781d8aa (https://gist.github.com/angel333/1aae17b1ea53a9cd538966979781d8aa)

Certbot also supports a cleanup script (so you're not left with "_acme-challenge" records) but I haven't written one yet.
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: angel333 on July 08, 2017, 04:16:43 PM
Here's a somewhat better version of my certbot hook: https://github.com/angel333/certbot-he-hook

It now also supports passing login credentials (instead of session id) so you can do something like this to renew all domains:


HE_USER=<username> HE_PASS=<password> certbot renew \
  --preferred-challenges dns \
  --manual-auth-hook /path/to/certbot-he-hook.sh  \
  --manual-public-ip-logging-ok
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: EddyBeaupre on August 25, 2017, 07:25:19 AM
acme.sh support dns.he.net out of the box... Basically all you have to do is:

First install acme.sh

wget -O -  https://get.acme.sh | sh

Next, you need to provide your credential (acme.sh will save them automatically to ~/.acme.sh/account.conf). This is only needed for the first run:

export HE_Username="yourusername"
export HE_Password="password"

or set them directly into ~/.acme.sh/account.conf by adding theses lines:

HE_Username='yourusername'
HE_Password='password'

Then you can issue your certificate:

acme.sh --issue --dns dns_he -d example.com -d www.example.com

The HE_Username and HE_Password settings will be saved in ~/.acme.sh/account.conf and will be reused when needed. That may be a security concern...
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: divad27182 on August 30, 2017, 09:00:14 PM
Quote from: EddyBeaupre on August 25, 2017, 07:25:19 AM
wget -O -  https://get.acme.sh | sh

Of course, if you have ANY security concerns, you will not do this at all!
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: PJSalt on September 25, 2017, 07:43:28 AM
Any progress on getting a *real* API from HE.net itself?

Or at least something that let's us more easily add (and remove!) TXT records via a HTTPS call instead of these (nice and clever, by the way) hacks by parsing the HTML code, which are prone to fail when they make a breaking change.

They already have something in place for dynamic DNS updates via curl, so in theory they could go and take that and adapt it for doing TXT stuff.

There is something on the homepage under "Upcoming Features!" named "Expanding our DDNS service to support TXT records" but that seems to be related to DDNS (I assume that abbreviation stands for dynamic DNS), and thus not "normal" hostnames. Unless it is just poorly worded by the person that typed out that line of text. It is confusing to me. I don't use DDNS.
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: notzac on October 22, 2017, 05:55:05 AM
Quote from: PJSalt on September 25, 2017, 07:43:28 AM
There is something on the homepage under "Upcoming Features!" named "Expanding our DDNS service to support TXT records" but that seems to be related to DDNS (I assume that abbreviation stands for dynamic DNS), and thus not "normal" hostnames. Unless it is just poorly worded by the person that typed out that line of text. It is confusing to me. I don't use DDNS.

The way I read the note about expanding the DDNS service to cover TXT records is that it will allow exactly what is being described in this thread - a method of using an API to update a TXT record.

This would be especially helpful for me, as all these awesome scripts don't work with 2FA.

:)
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: PJSalt on November 05, 2017, 01:34:22 PM
Yeah, I also don't like having to store the login credentials of the account in a file like that. A system with an API key would be much better.

Even better would be if we could also limit what the API key can do and assign rights to it. For example: only create/edit/remove TXT records. So that when somebody unauthorized gets a hold of the API key that they can't do too much damage by for example changing A/AAAA records and such.
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: PJSalt on March 14, 2018, 06:28:28 AM
Any updates on this? Now that Let's Encrypt has officially launched their v2 API with wildcard support (which only works with the dns-01 challenge method by the way), it would be nice if dns.he.net had an API as well.
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: TemiD on April 24, 2018, 04:46:09 PM
+1 for Let's Encrypt and API integration. Cloudflare supported the api, but I moved to he.net for the ipv6 cert course. I use a wildcard for my network and manually renewing certs is going to bite.
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: beneckema on July 16, 2018, 02:40:13 AM
+1 i like to use an scripted dns-01 challange, so it would be great to use the API like the "dynamic" A and AAAA Records
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: mkbloke on August 13, 2018, 03:08:38 AM
+1 for an API supporting TXT records to make Let's Encrypt easy.

Ian
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: wrtpoona on September 21, 2019, 08:14:47 AM
+1 for an TXT RR API, any update on this?
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: FostWare on March 02, 2020, 10:42:35 AM
+1 for API that doesn't require removing 2FA
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: Vazhnov on April 11, 2020, 09:49:50 AM
As I see on title page (https://dns.he.net/):

Quote
We're looking into implementing:

  • Expanding our DDNS service to support TXT records
  • ...

Updated 11.28.2018

But still no news...
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: matth1187 on July 10, 2020, 11:21:38 PM
 I would've used it if it was available however, someone paranoid convinced me it may be a good idea to keep acme challenges on a separate provider of your main, assuming he, domain. in case your API key /pass gets compromised.

i found luadns.com to be noobishly easy to use and is default supported provider by most acme programs (is mentioned on LE website as a provider easily integrated, free). in addition to API it has a slick gui. create a zone like acme.domain.com. point some ns records from he to there. Then use cname in he. _acme-challenge.www.domain.com-> luadns, www.acme.domain.com. now can be automated and no messing with port 80. HTH!

Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: jvandenbroek on July 20, 2020, 05:02:41 AM
Was looking for this and found out that it's now actually possible to set DDNS for a TXT record. Just needed some trial and error to get it working:

curl -k https://dyn.dns.he.net/nic/update -d "hostname=_acme-challenge.mydomain.com" -d "password=mypassword" -d "txt=somevalue"
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: Com DAC on August 02, 2020, 04:38:47 PM
found that ddns is now possible for txt records (YAY). The things I'm unable to figure out now is how to update the records if you have two of the same txt records? For example if you have a Let's Encrypt certificate for *.domain.ext and domain.ext then you need two txt entries _acme-challenge.domain.ext and _acme-challenge.domain.ext. I can do this manually but when I setup the entries to be dynamic I'm only able to update the last one I updated with a password. Does anyone know if there is a trick for this situation or if this part isn't implemented yet?
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: matthiaspfaller on August 10, 2020, 04:39:56 AM
Quote from: Com DAC on August 02, 2020, 04:38:47 PM
found that ddns is now possible for txt records (YAY). The things I'm unable to figure out now is how to update the records if you have two of the same txt records? For example if you have a Let's Encrypt certificate for *.domain.ext and domain.ext then you need two txt entries _acme-challenge.domain.ext and _acme-challenge.domain.ext. I can do this manually but when I setup the entries to be dynamic I'm only able to update the last one I updated with a password. Does anyone know if there is a trick for this situation or if this part isn't implemented yet?

While the new feature is neat, it just doesn't help us. In order for this to be really use full, we would need to be able to create new ddns txt records without the web interface. But its a very nice fist step.

regards, Matthias
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: tjeske on August 11, 2020, 05:14:24 AM
I know that a dedicated API call would be nice. But I am sure some mediocre programmer is able to code some python module for that :)
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: rill on January 17, 2021, 02:30:00 PM
Quote from: jvandenbroek on July 20, 2020, 05:02:41 AM
Was looking for this and found out that it's now actually possible to set DDNS for a TXT record. Just needed some trial and error to get it working:

curl -k https://dyn.dns.he.net/nic/update -d "hostname=_acme-challenge.mydomain.com" -d "password=mypassword" -d "txt=somevalue"

This works. Only one thing needed for this.
There is an optional parameter for `DDNS` feature to each dns record on the UI. You need to active this feature then generate a secret for that. Use this secret as password in the above command.
Title: Re: DNS ACME challenge. (Let's encrypt validation)
Post by: pmarks on September 23, 2022, 11:08:37 PM
I tried adding the new dynamic TXT API to acme.sh, but the problem is that it cannot support multiple TXT records under the same name, for cases like this:

./acme.sh --staging --issue --dns dns_he_dyntxt -d 'test1.he.example.com' -d '*.test1.he.example.com'

Here is my code, but I think it's fundamentally too broken to upstream it:


$ cat dnsapi/dns_he_dyntxt.sh
#!/usr/bin/env sh

########################################################################
# Hurricane Electric hook script for acme.sh, with simple dynamic TXT API.
#
# Unlike dns_he.sh, this script does not use your full account password,
# but all _acme-challenge TXT records must be created manually, and these
# records must share the same DDNS key.
#
# Environment variables:
#
#  - $HE_DynTXT_Key - DDNS key for all _acme-challenge TXT records
#

HE_DynTXT_Api="https://dyn.dns.he.net"

########  Public functions #####################

#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_he_dyntxt_add() {
  fulldomain=$1
  txtvalue=$2

  HE_DynTXT_Key="${HE_DynTXT_Key:-$(_readaccountconf_mutable HE_DynTXT_Key)}"

  if [ -z "$HE_DynTXT_Key" ]; then
    HE_DynTXT_Key=""
    _err "You did not specify HE_DynTXT_Key."
    _err "Please log into https://dns.he.net/, create a TXT record for '$fulldomain', and generate a DDNS key."
    _err "The same key should be shared among all TXT records managed by this script."
    _err ""
    return 1
  fi

  #save the DDNS key to the account conf file.
  _saveaccountconf_mutable HE_DynTXT_Key "$HE_DynTXT_Key"

  _info "Updating record $fulldomain"
  if _he_dyntxt_rest POST "nic/update" "hostname=$fulldomain&password=$HE_DynTXT_Key&txt=$txtvalue"; then
    if _contains "$response" "good"; then
      _info "Updated, OK"
      return 0
    elif _contains "$response" "badauth"; then
      _err "TXT record $fulldomain does not exist, or incorrect DDNS key"
      return 1
    fi
  fi
  _err "Update TXT record error."
  return 1
}

#fulldomain txtvalue
dns_he_dyntxt_rm() {
  fulldomain=$1
  txtvalue='""' # Just clear the TXT record.

  HE_DynTXT_Key="${HE_DynTXT_Key:-$(_readaccountconf_mutable HE_DynTXT_Key)}"

  _info "Clearing record $fulldomain"
  if _he_dyntxt_rest POST "nic/update" "hostname=$fulldomain&password=$HE_DynTXT_Key&txt=$txtvalue"; then
    if _contains "$response" "good"; then
      _info "Cleared, OK"
      return 0
    elif _contains "$response" "badauth"; then
      _err "TXT record $fulldomain does not exist, or incorrect DDNS key"
      return 1
    fi
  fi
  _err "Clearing TXT record error."
  return 1
}

#####################  Private functions below ##################################

_he_dyntxt_rest() {
  m=$1
  ep="$2"
  data="$3"
  _debug "$ep"

  if [ "$m" = "POST" ]; then
    _debug data "$data"
    response="$(_post "$data" "$HE_DynTXT_Api/$ep" "" "$m")"
  else
    _err "unimplemented method: $m"
    return 1
  fi

  if [ "$?" != "0" ]; then
    _err "error $ep"
    return 1
  fi
  _debug2 response "$response"
  return 0
}