Let'sEncrypt, ACME.sh and LUCI App Acme support topic

Luci uses uhttpd instead

--log [file] Specifies the log file. Defaults to "/root/.acme.sh/acme.sh.log" if argument is omitted.

Is it there perhaps?

I'm also having problem and I tried the fix mentioned before and none worked .. checking the debug I could see this wrong path "yes/.well-known" maybe the cause of my problem:

_currentRoot='yes'
wellknown_path='yes/.well-known/acme-challenge'
writing token:--REDACTED-- to yes/.well-known/acme-challenge/--REDACTED--

I checked the content of the above message is located inside the client.sh file, but it has over 8k lines .. and my knowleged isn't that big to find how to fix the directory

EDIT: I fixed just doing this:
ln -s /var/run/acme/challenge/yes/.well-known/acme-challenge/ /www/.well-known/acme-challenge

the problem is here, for some reason the yes is used in a path. Please check configuration

In case anyone else runs into this, on one of my systems it was caused by /usr/lib/acme/hook being updated for key_type but /etc/init.d/acme not being updated. I guess maybe a dependency between acme.sh and acme-common is wrong? I'm not that familiar with opkg.

Anyway, check out https://github.com/openwrt/packages/commit/66894032d482625a1a7e22ba4336c6fa5dd35d26 and apply the second diff's changes to /etc/init.d/acme and it should work.

1 Like

Thank you, that's exactly what helped me as well! According to the Wiki there should be a acme-common in version 1.4, but opkg shows me only 1.0.4 as latest version on 23.05.3. The makefile is also set to 1.4 so I assume there's a fix coming as package but its not ready yet?

1 Like

I have setup ACME with DuckDNS (using dns validation), however it is not working.
I tried manually running /etc/init.d/acme start with debug enabled, it quickly filled my terminal with big HTMLs (from Cloudflare, it seems), and it just keeps going (I have to kill it with ctrl+c).
Any idea on how to debug this?

This is my /etc/config/acme:

config acme
        option account_email '<redacted>'
        option debug '0'

config cert 'duckdns'
        option enabled '1'
        option use_staging '0'
        option keylength '2048'
        list domains '<redacted>.duckdns.org'
        list domains '*.<redacted>.duckdns.org'
        option update_uhttpd '0'
        option update_nginx '0'
        option validation_method 'dns'
        option dns 'dns_duckdns'
        list credentials 'DuckDNS_Token="<redacted>"'

Note: My DDNS setup for DuckDNS works.

Edit:
The HTML Cloudflare returned looks like this. Not sure if it is relevant to my issue


(Not sure who is 172.71.214.5, definitely not my IP though)

That looks strange. It look like your website example.duckdns.org was resolved to the ip 1.1.1.1 which is an IP of the Cloudlfare DNS. So they receive a request for your domain and they are saying that the domain is not belong to them.
The IP 172.71.214.5 itself belong to the CLOUDFLARENET. Maybe you have enabled Cloudflared tunnel or this is just their internal IP of the front server.

Hi,
just in case someone else has the same problem. I did not dig into this on why that is but maybe helps:
First problem was an error "Can not init api" without any additional error message:

[Thu Sep 26 11:27:06 CEST 2024] GET
[Thu Sep 26 11:27:06 CEST 2024] url='https://acme-v02.api.letsencrypt.org/directory'
[Thu Sep 26 11:27:06 CEST 2024] timeout=
[Thu Sep 26 11:27:06 CEST 2024] _WGET='wget -q'
[Thu Sep 26 11:27:06 CEST 2024] options='s/^  //g'
[Thu Sep 26 11:27:06 CEST 2024] Using sed  -i
[Thu Sep 26 11:27:06 CEST 2024] ret='0'
[Thu Sep 26 11:27:06 CEST 2024] response
[Thu Sep 26 11:27:06 CEST 2024] ACME_KEY_CHANGE
[Thu Sep 26 11:27:06 CEST 2024] ACME_NEW_AUTHZ
[Thu Sep 26 11:27:06 CEST 2024] ACME_NEW_ORDER
[Thu Sep 26 11:27:06 CEST 2024] ACME_NEW_ACCOUNT
[Thu Sep 26 11:27:06 CEST 2024] ACME_REVOKE_CERT
[Thu Sep 26 11:27:06 CEST 2024] ACME_AGREEMENT
[Thu Sep 26 11:27:06 CEST 2024] ACME_NEW_NONCE
[Thu Sep 26 11:27:06 CEST 2024] Sleep 10 and retry.

I saw that the script used wget and it was solved by using curl (no clue why, though):

opkg install curl

Then I had libustream-mbedtls20201210 installed which led to error: "openssl doesn't exist.". Solved with:

opkg install luci-ssl-openssl --force-overwrite

Manually it works now. However, I need the option --dnssleep 900 and don't know how to add that. I will try with option dnssleep '900' and see if that's implemented :slight_smile:

1 Like

I have Acme kind of working via LUCI. However, it's not quite right.

I am using freedns.afraid.org and so I don't think I can use DNS mode. I don't want to open uhttpd to the internet so I use standalone mode. To get that to work I have added a rule to redirect port 80 to port 8080 and so I want the acme script to listen on 8080. I can't work out how to do that via the LUCI interface or via command line config.

What I have done as a temporary fix is to edit the acme.sh script. That works - I get my certificates - but obviously it's not the way to go. What's the right way to achieve my setup? Or is some other approach more sensible?

The dns mode is supported for the ddns provider. You may use the standalone mode that will itself temporarly redirect 80 port.

I think you can only use the DNS method at freedns if you have a domain - I only have subdomain. When I tried to use the standalone mode on port 80 it complained that it was already in use - by uhttpd of course.

Is it possible to modify the cron job to tell Acme to use http port 8080? That might do the job.

acme seems to store certificates in /etc/acme whereas luci-app-acme expects them in /etc/ssl/acme. As a result, the Certificates section at the bottom of Services ¦ ACME Certificates no longer gets populated or updated. It might also mean that certificates don't get renewed when the time comes, but that is a little harder for me to test.

The openwrt package of the acme use own default to the etc/ssl/acme

How might I revert to that behaviour then? I have deleted the entries in Settings ¦ Acme Certificates as well as the issued certificates. I can't find any relevant entries in etc/config/acme to delete or change.

When I then issue the certificate from scratch, it goes into /etc/acme rather than /etc/ssl/acme.

I even tried Uninstalling acme and luci-app-acme with a view to reinstalling it, but that killed apk update, so I had to reinstall.

UPDATE: Stepping through /etc/init.d/acme, CERT_DIR is indeed set to /etc/ssl/acme in line 6, but the code first checks for the existence of state_dir in line 124, warning that that is deprecated but then using it. I can't work out where state_dir comes from or is set?

I do not have anything set in /etc/config/acme or in /etc/acme/<url>_ecc/<url>.conf

It seems that the heavy lifting is done in hook.sh when the certs are "created" by links between $domain_dir/domain.* & $CERT-DIR/$main_domain.*

Which makes me think maybe my issue is that I am trying to issue certificates for *.<example>.org without <example>.org itself, i.e. that I do not have a "main domain"?

UPADTE2: configuring to issue a certificate for <example>.org as well as *.<example>.org didn't change the behaviour.

@stokito gentle nudge to have a look at my question above regarding how to get back to the default scenario where acme-sh installs certificates to /etc/ssl/acme.

Also, a dependency of acme-sh is wget-ssl. Functionality that is already provided by uclient-fetch & aliased to wget. Guessing this is an acme-sh thing and not an OpenWRT thing. But if not, we could save some space.

The CERT_DIR=/etc/ssl/acme is hard coded in the /etc/init.d/acme
Then is used in the /usr/lib/acme/hook.
Certificates are generated in the acme.sh state_dir i.e. /etc/acme/example.com/ and at the first issue created a symlink into the CERT_DIR.

The main_domain is required and it should be specified first:

config cert 'example_wildcard'
	list domains 'example.org'
	list domains '*.example.org'

See samples in the /etc/config/acme

If something doesn't work please try to enable debug log and find an issue. If something wrong with the script report a bug or send a PR.

yes, but the uclient-fetch doesn't support sending a POST body with JSON content (only formdata) but the ACME protocol needs it https://datatracker.ietf.org/doc/html/rfc8555. Same for the DNS API which are mostly REST based except of basic DynDNSv2 that works with GET (e.g. duckdns.org).

I sent a PR to the uclient-fecth but it wasn't merged yet.
FYI: you may use curl instead of the wget-ssl. We may probably change the acme-acmesh package Depends to add the curl as an alternative so if anyone already have it then the wget won't be installed. But instead I'll rather merged the PR to the uclient-fetch.
Speaking about size reduction, the amcesh also has a dependency to socat that is needed only for the standalone mode and actually can be replaced with the uhttpd. The acme-acmesh-dnsapi package also can be split to a most used DNS providers (i.e. DynDNS like duckdns.org, afraid) and more complicated. But again, instead it would be better for all the providers to implement same API and I already raised the topic in a few places but that's not interesting to anyone.

Feel free to contribute. If you feel that something needs to be added on the wiki https://openwrt.org/docs/guide-user/services/tls/acmesh then let me know and I'll edit that page.

1 Like

Recently the uclient-fetch got a support of the --header that allows to override the Content-Type and from I see the acme.sh itself now can potentially work with the uclient-fetch

As you may see the _post() function is always called with the method POST and only in one place with HEAD (i.e. can be replaced with GET). So the --method and --body-data options aren't even needed.
The only unsupported wget option is -S (--server-response) which is probably used for debugging purposes. I attempted to implement the option https://github.com/openwrt/uclient/commit/0d3764d17b870285ef787766f1ee5fef9dfdeb4d

Also I see that for the HEAD it appends --read-timeout=3.0 --tries=2 that is also shouldn't be required.
So technically speaking today we may try to change the acme.sh to work with the uclient-fetch

2 Likes

Thank you again @stokito. I have done quite a bit more experimenting. Which confirms what you have so clearly and kindly set out.

The one exception, for my scenario & needs at least, is that issuing certificates against the * subdomains works at least as well as issuing against the domain followed by the subdomains. Are you able to set out why the domain might be required before the subdomains? Is it simply good convention?

Also a little more clarity on how removing acme (apk del acme luci-app-acme acme-acmesh-dnscf) has the effect of leaving breaking wget and therefore apk. This is because it orphans the wget link to /usr/libexe/wget-ssl in /usr/bin. Which can be mitigated by linking wget to uclient-fetch with the command ln -f /bin/uclient-fetch /usr/bin/wget.

wget breaks despite the newly added/restored dependency apk has to wget. The uclient-fetch link gets overwritten when acme is installed.

I mention this as gentle encouragement for letting uclient-fetch satisfy acme's need for wget as you propose.