breard-r / acmed Goto Github PK
View Code? Open in Web Editor NEWACME (RFC 8555) client daemon
License: MIT License
ACME (RFC 8555) client daemon
License: MIT License
Hello,
I deployed ACMED in a podman environment.
It works well and generates a cert at launch if it does not exist. Unfortunately it
fails when trying to determine renew period with randomness...
[2023-11-10T16:23:39Z TRACE acmed::main_event_loop] Adding certificate: nginx_default_rsa4096
[2023-11-10T16:23:39Z DEBUG acmed::certificate] certificate "nginx_default_rsa4096": checking for renewal (identifiers: )
[2023-11-10T16:23:39Z TRACE acmed::storage] certificate "nginx_default_rsa4096": testing file path: /var/certs/nginx_default_rsa4096.pk.pem
[2023-11-10T16:23:39Z TRACE acmed::storage] certificate "nginx_default_rsa4096": testing file path: /var/certs/nginx_default_rsa4096.crt.pem
[2023-11-10T16:23:39Z TRACE acmed::storage] certificate "nginx_default_rsa4096": reading file "/var/certs/nginx_default_rsa4096.crt.pem"
[2023-11-10T16:23:39Z DEBUG acmed::certificate] certificate "nginx_default_rsa4096": certificate expires in 89 days (30 days delay)
thread 'main' panicked at /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/rand-0.8.5/src/rng.rs:134:9:
cannot sample empty range
stack backtrace:
0: 0x55c70a28324c - std::backtrace_rs::backtrace::libunwind::trace::he43a6a3949163f8c
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/../../backtrace/src/backtrace/libunwind.rs:93:5
1: 0x55c70a28324c - std::backtrace_rs::backtrace::trace_unsynchronized::h50db52ca99f692e7
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
2: 0x55c70a28324c - std::sys_common::backtrace::_print_fmt::hd37d595f2ceb2d3c
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/sys_common/backtrace.rs:67:5
3: 0x55c70a28324c - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::h678bbcf9da6d7d75
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/sys_common/backtrace.rs:44:22
4: 0x55c70a132c3c - core::fmt::rt::Argument::fmt::h3a159adc080a6fc9
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/fmt/rt.rs:138:9
5: 0x55c70a132c3c - core::fmt::write::hb8eaf5a8e45a738e
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/fmt/mod.rs:1094:21
6: 0x55c70a27f1ce - std::io::Write::write_fmt::h9663fe36b2ee08f9
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/io/mod.rs:1714:15
7: 0x55c70a283034 - std::sys_common::backtrace::_print::hcd4834796ee88ad2
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/sys_common/backtrace.rs:47:5
8: 0x55c70a283034 - std::sys_common::backtrace::print::h1360e9450e4f922a
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/sys_common/backtrace.rs:34:9
9: 0x55c70a289ce3 - std::panicking::default_hook::{{closure}}::h2609fa95cd5ab1f4
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:270:22
10: 0x55c70a289a2f - std::panicking::default_hook::h6d75f5747cab6e8d
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:290:9
11: 0x55c70a28a1d6 - std::panicking::rust_panic_with_hook::h57e78470c47c84de
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:707:13
12: 0x55c70a28a0b4 - std::panicking::begin_panic_handler::{{closure}}::h3dfd2453cf356ecb
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:597:13
13: 0x55c70a288e46 - std::sys_common::backtrace::__rust_end_short_backtrace::hdb177d43678e4d7e
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/sys_common/backtrace.rs:170:18
14: 0x55c70a289e72 - rust_begin_unwind
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:595:5
15: 0x55c70a058893 - core::panicking::panic_fmt::hd1e971d8d7c78e0e
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/panicking.rs:67:14
16: 0x55c70a058923 - core::panicking::panic::hd907d42d09324a32
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/panicking.rs:117:5
17: 0x55c70a0ce237 - acmed::main_event_loop::MainEventLoop::run::{{closure}}::h5010543ffbdf423e
18: 0x55c70a0d2fde - acmed::inner_main::{{closure}}::h516ef2296ac78188
19: 0x55c70a0d173c - acmed::main::hc37f30cf3252ae80
20: 0x55c70a06c26a - std::sys_common::backtrace::__rust_begin_short_backtrace::h54e4788db0d8db37
21: 0x55c70a06c691 - std::rt::lang_start::{{closure}}::hf3a7d1b7532d3c6f
22: 0x55c70a27922b - core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once::hbcc4f8a3f5ada78c
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/ops/function.rs:284:13
23: 0x55c70a27922b - std::panicking::try::do_call::he5f117a9e13dadde
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:502:40
24: 0x55c70a27922b - std::panicking::try::h2f3af9afce3a0443
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:466:19
25: 0x55c70a27922b - std::panic::catch_unwind::h6d6c387f38ef05ea
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panic.rs:142:14
26: 0x55c70a27922b - std::rt::lang_start_internal::{{closure}}::h6ca09d5905711415
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/rt.rs:148:48
27: 0x55c70a27922b - std::panicking::try::do_call::ha9fd18ea06654a4b
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:502:40
28: 0x55c70a27922b - std::panicking::try::hda5c2a4432362341
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:466:19
29: 0x55c70a27922b - std::panic::catch_unwind::h440f731b142bc235
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panic.rs:142:14
30: 0x55c70a27922b - std::rt::lang_start_internal::hc0b4e50f058f62ce
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/rt.rs:148:20
31: 0x55c70a06c686 - std::rt::lang_start::hfb1428766cb0010e
32: 0x7f506606409b - __libc_start_main
33: 0x55c70a063ada - _start
34: 0x0 - <unknown>
Aborted
exit code: 134
Hi!
Provided you want to maintain DANE RFC6698 records, that is a task that is due every time an ACME certificate is renewed, which brings me to the conclusion that this functionality would be well placed alongside an ACME client implementation.
Functionality would be relatively simple and similar to DaneBot.
Is this a feature you would consider to implement?
If several certificates uses the same account and this account doesn't exists yet, they will both try to create it, resulting in a race condition. Hence, most certificates requests will fail at the first call, but will succeed in the next one (1 hour later or manual restart).
This faulty behavior has been introduced in version 0.6 with parallel certificate renewals. Since it is not blocking and we are in early development, it will not be fixed until version 0.7.
The ualpn daemon from uacme can pass through connections on port 443. To use it, I need to have {{proof}}
available in a different format.
uacme/ualpn: https://github.com/ndilieto/uacme#tls-alpn-01-challenge-support
man page for ualpn, with examples: https://ndilieto.github.io/uacme/ualpn.html
Need the proof encoded in base64url format, when using challenge-tls-alpn-01.
Having it available as {{proof_b64u}}
or some other identifier would make it work.
Right now I make this work by using a script to crop and reencode {{proof}}, and run ualpn. But would like to use ualpn directly.
[[hook]]
name = "ualpn-auth"
type = ["challenge-tls-alpn-01"]
cmd = "/etc/acmed/fix-ualpn.php"
stdin_str = "auth {{identifier_tls_alpn}} {{proof}}"
[[hook]]
name = "ualpn-auth-need-patch"
type = ["challenge-tls-alpn-01"]
cmd = "/usr/bin/ualpn"
stdin_str = "auth {{identifier_tls_alpn}} {{proof_b64u}}"
[[hook]]
name = "ualpn-unauth"
type = ["challenge-tls-alpn-01-clean"]
cmd = "/usr/bin/ualpn"
stdin_str = "unauth {{identifier_tls_alpn}}"
[[group]]
name = "ualpn"
hooks = [
"ualpn-auth",
"ualpn-unauth",
]
script:
#!/usr/bin/php
<?php
function base64url_encode($data) {
$b64 = base64_encode($data);
$url = strtr($b64, '+/', '-_');
return rtrim($url, '=');
}
$line = trim(fgets(STDIN)); // reads one line from STDIN
$lines = explode(" ", $line);
$len = strlen("1.3.6.1.5.5.7.1.31=critical,DER:04:20");
$cert = substr($lines[2], $len);
$cert = str_replace(":", "", $cert);
$cert = hex2bin($cert);
$lines[2] = base64url_encode($cert);
$fixed = vsprintf("%s %s %s\n", $lines);
$ualpn = popen("/usr/bin/ualpn", "w");
fwrite($ualpn, $fixed); // pipe fixed string to ualpn
fclose($ualpn);
Looks like fix introduced in 220f580 now emits a warning, due to rust-lang/libc#1848 and the test added along with the fix fails on Alpine Linux edge:
---- tests::certificate::cert_expiration_date_past stdout ----
thread 'tests::certificate::cert_expiration_date_past' panicked at 'called `Result::unwrap()` on an `Err` value: Error { message: "error:0D0D90AD:asn1 encoding routines:ASN1_TIME_adj:error getting time:crypto/asn1/a_time.c:330:" }', acme_common/src/tests/certificate.rs:179:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
There will be a point where it is necessary to store informations about a certificate that do not belong to the configuration. For example, this may be necessary to implement STAR certificates (RFC 8739). I have not read the full specification, but from the few I read it seems necessary to keep track of some state on the client side, at least in order to know if it's a "traditional" certificate or a STAR.
There is multiple possibilities, the one I find the more promising being:
I would prefer the one file per certificate approche since it would be coherent with what has already be done for the accounts and is easier for people to manage.
Using this approche, the file_name_format
will be used with:
file_type
set to something like meta
ext
set to bin
(if bincode is chosen)The file format itself should be the same one as the account's. Currently that is the binary format provided by the bincode crate, however it might evolve if necessary.
Because the file's content may evolve in the future, this should be taken into consideration from the beginning. I don't remember how bincode allows this, but I think the following method should work: try to load the latest structure, if it fails try the previous ones until it matches and upgrade to the latest.
I would like to limit new features before the async rewrite, however it doesn't prevent discussing this future improvement.
As you may already know, a bug in the CAA rechecking part of Boulder has been found. Hence, Let's Encrypt is revoking every certificate emitted when the bug has been known to be present, which means (almost ?) all certificates.
Force-renew certificates based on various parameters is not yet supported. In the future, it will be included in a tool, acmectl, which role will be to manage the deamon.
The recommended procedure is to manually delete your certificates so ACMEd will create and request signing for new ones. Depending on your archiving policy, deleting certificates could be done in various ways. The most commons are:
git rm
and then git commit
.rm
the file if you do not keep any archive.Only the certificate files (.crt.pem
) needs to be removed. You may also remove the certificate's private key (.pk.pem
) but this is not necessary.
You should not remove your accounts keys (.priv-key.pem
and .pub-key.pem
) as those are not affected at all.
Onece your certificates has been removed, you can either restart ACMEd or wait for it to automatically detect that the files are missing. If you chose to wait, it could last up to an hour.
Some simplistic configurations could lead to errors. The most common case if when the post-operation hook contains a command which restarts a service that uses several certificates (Eg: a web server). In such case, this post-operation hook will fail for every certificate but the last since the service restart will fail due to the missing certificates.
If you are in such situation, you should remove the certificates one by one and restart ACMEd after each deletion.
I just started with default configuration where I have added the certificate with (default) rsa2048 algorithm but I got this error.
It looks like the account file is using EC type that is not supported?
$ target/release/acmed -f --log-stderr --log-level trace -c test.toml
[2019-11-14T17:01:10Z INFO acmed::config] Loading configuration file: test.toml
[2019-11-14T17:01:10Z TRACE acmed::certificate] crt-1: Testing file path: /tmp/acc/eHh4.priv-key.pem
[2019-11-14T17:01:10Z TRACE acmed::certificate] crt-1: Writing file "/tmp/acc/eHh4.pub-key.pem"
[2019-11-14T17:01:10Z TRACE acmed::certificate] crt-1: Writing file "/tmp/acc/eHh4.priv-key.pem"
[2019-11-14T17:01:10Z INFO acmed::certificate] crt-1: Account xxx created.
[2019-11-14T17:01:10Z TRACE acmed::certificate] crt-1: Testing file path: /tmp/certs/hello.xxx.net_rsa2048.pk.pem
[2019-11-14T17:01:10Z DEBUG acmed::certificate] crt-1: certificate does not exist: requesting one
[2019-11-14T17:01:10Z DEBUG acmed::certificate] crt-1: GET: https://acme-staging-v02.api.letsencrypt.org/directory
[2019-11-14T17:01:11Z DEBUG acmed::certificate] crt-1: HEAD: https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
[2019-11-14T17:01:12Z TRACE acmed::certificate] crt-1: New nonce: 0001V2hQXxOcbr6ly8PN0IpUdfSiV8F6_OFScmN1cOp0b3E
[2019-11-14T17:01:12Z TRACE acmed::certificate] crt-1: Reading file "/tmp/acc/eHh4.priv-key.pem"
[2019-11-14T17:01:12Z WARN acmed::certificate] crt-1: Unable to renew the certificate: Unsupported EC key
I have tried this on two systems, one with OpenSSL 1.0.2k-fips 26 Jan 2017
and other that has LibreSSL 2.9.2
with same result.
$ target/release/acmed --version
ACMEd 0.6.1 x86_64-unknown-linux-gnu
Compiled with:
LibreSSL 2.9.2
http_req 0.5.3
My configuration:
[global]
accounts_directory = "/tmp/acc"
certificates_directory = "/tmp/certs"
[[rate-limit]]
name = "LE min"
number = 20
period = "1s"
[[endpoint]]
name = "letsencrypt v2 staging"
url = "https://acme-staging-v02.api.letsencrypt.org/directory"
rate_limits = ["LE min"]
tos_agreed = true
[[account]]
name = "xxx"
email = "[email protected]"
[[certificate]]
account = "xxx"
endpoint = "letsencrypt v2 staging"
domains = [{ challenge = "dns-01", dns = "hello.xxx.net" }]
hooks = []
Hello,
I'm actually trying to write an init script for FreeBSD for acmed 0.6.0 :
#!/bin/sh
# PROVIDE: acmed
# REQUIRE: LOGIN
# KEYWORD: shutdown
# Add the following lines to /etc/rc.conf to enable `acmed':
#
# acmed_enable="YES"
#
. /etc/rc.subr
name="acmed"
rcvar=acmed_enable
command="/usr/local/bin/acmed"
pidfile="/var/run/$name.pid"
# read configuration and set defaults
load_rc_config "$name"
: ${acmed_enable="NO"}
: ${acmed_configfile:="/usr/local/etc/acmed/$name.toml"}
required_files="${acmed_configfile}"
command_args="--pid-file ${pidfile} --config ${acmed_configfile} --log-syslog --log-level info --foregroung"
run_rc_command "$1"
the --foregroung
option seems to doesn't work with --pid-file
option :
$ sudo service start acmed
Starting acmed.
error: The argument '--foregroung' cannot be used with '--pid-file <FILE>'
USAGE:
acmed --config <FILE> --foregroung --log-level <LEVEL> --pid-file <FILE> --log-syslog
For more information try --help
/usr/local/etc/rc.d/acmed: WARNING: failed to start acmed
It is normal the normal behavior or a bug ?
Best regards,
Yan
From this i386 log:
error[E0308]: mismatched types
--> acme_common/src/crypto/openssl_certificate.rs:111:39
|
111 | let now = Asn1Time::from_unix(timestamp)?;
| ^^^^^^^^^ expected `i32`, found `i64`
|
help: you can convert an `i64` to `i32` and panic if the converted value wouldn't fit
|
111 | let now = Asn1Time::from_unix(timestamp.try_into().unwrap())?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Maybe the timestamp
should not hardcode i64
..
Hi. Is it possible to provide a pure Rust-based generation of certificates (that avoids openssl)? There seem to be libraries/apps that generate certificates using Rust already: https://crates.io/crates/certainly
Avoiding openssl makes sense for users who are worried about openssl vulnerabilities and [unnecessary] complexity.
Using the pure-rust implementation could then be hidden under a feature flag if openssl is desired to be used by default.
The use static linking along with multiple dependencies which have their own dependencies makes the final executable completely bloated (8.8Mo in release mode and 58Mo in debug mode). Using many dependencies is very useful at the beginning in order to quickly have a working prototype. However, this situation is unacceptable and has to be fixed before the 1.0 release.
Reasons to fix this:
acmed
currently allows setting rate limits on endpoints, that specify how many
requests can be made per interval. It does not take the kind of request that is
made to the endpoint into consideration, which means that the rate limits
actually set by CAs (see https://letsencrypt.org/docs/rate-limits/) can not
really be represented by this.
My suggestion is to extend the current mechanism by giving rate-limits an
optional scope. This scope can be the path in the HTTP request, a path prefix,
an ACME resource (as listed in the directory response), etc. A request is then
only blocked, if there is a currently blocking rate-limit on that endpoint, that
matches this request. The specifics on how to model that and how to expose the
configuration for this aren't trivial, but I think if at least path (prefixes)
and ACME resources can be used as a scope, a few important rate limits can now
be set properly, that couldn't be set before, like the one for the newOrder
resource (300/3h), a shared one for the ACME resources newNonce
, newAccount
,
newOrder
and revokeCert
(20/1s), and the one for /directory
and anything under /acme
(40/1s).
Even after that, there is a bunch of limits that this doesn't catch, but it'd be
a big improvement over the status quo.
Use case: As a large provider with many certificates, we want to run acmed in a centralized way, and after a new certificate has been ordered successfully, we want to distribute the chain and key to the clients. Right now, we'd need to guess the file locations based on the identifiers and the key type, but having the locations exposed as variables would be a lot more robust.
Hi,
Can this be used as a library to set up, generate the challanges and download the certificates?
I used to use onur's acme package, but it doesn't support acme v2 unfortunately.
I have my own (rust) server running, and I made calls to the library to know which challanges to serve when the boulder server was verifying the challanges, and after that was done, i made another call to the library to get the signed certificate data.
It seems acmed is a standalone app, but can it be used as a library as well?
Hey @breard-r
Thank you for your effor in acmed. It's a nice peace of software. However, can you please consider providing static linked binaries (including libc and openssl)? I would like to use acmed on CentOS6 and there is some problem in OpenSSL version (since i'm a total rust noob i can't even install rust tools env).
Let's Encrypt has an integration guide for hosting providers and client software that's meant to be used with Let's Encrypt that has a few things that they want those users to implement, and most clients do fail to implement those in one way or another.
The integration guide is available over at https://letsencrypt.org/docs/integration-guide/. I'll try to summarize the requirements below, and whether acmed
is implementing it in a compliant way, if that requirement is applicable at all. I'll not add a checkmark iff there is a recommendation that is definitely applicable to acmed
and which acmed
doesn't apply properly.
acmed
to support multiple accounts, because different providers could require different key algorithms for example, the documentation could be improved here, to recommend sticking to a single account. Even worse than not recommending to use a single account though, the README
even encourages using multiple accounts (at least when using multiple endpoints). Edit/Clarification: the recommendation from LE is just about larger hosting providers, and the recommendation from the README
is just about different endpoints, so my initial assessment here was off.acmed
acmed
provides hooks for storing files and reusing them properly, so this is definitely okay. What Let's Encrypt says here is to not generate certificates in volatile environments such as short lived containers, which is not really applicable to acmed
.acmed
supports all current challenge types, this is in good shape right now.acmed
, as this depends more on the deployment than the acme client.acmed
as well.acmed
does right now is 3 weeks before the end of the expiration, so 21 days. This is not the only way that things could be improved here though: Especially if you're running very big deployments with thousands of certificates, you can reduce the cost for Let's Encrypt by spacing out the renewals randomly. My suggestions here would be to allow setting a range here, instead of a concrete value, and to take a random time between the start of the range and the end of the range for the renewal, so something like 35 days until expiry and 30 days until expiry, and then it chooses to do it at 33.78 days before expiry for example. With the current structure of how acmed
handles this though, it's not that easy to do this.acmed
is pretty far off here (as far as I can tell, I'm not quite sure actually). As far as I can tell, acmed
will check each certificate for whether it needs renewal once per hour (at least that's the default) and not do any sort of exponential backoff. Some specific errors are also treated as recoverable
, which are then retried 20 times with 1s delay in between. Whether retrying recoverable errors in such quick succession is in line with what Let's Encrypt wants aside, the more important thing would be to implement some for of exponential backoff. Their suggested schedule is retrying after 1 minute, then 10 minutes, then 100 minutes and each subsequent retry after 1 day. I think it'd be in the spirit of acmed
to have this as something that's configurable per endpoint, with defaulting to the schedule suggested by Let's Encrypt.Summarizing: acmed
is already doing a lot of things right here, with the documentation on accounts being one thing that could be improved and scheduling of renewals and retrying failures being the other thing.
Hi, this looks like a very interesting project! I'm thinking about building ACME support into a project for which I would like to use rustls (which relies on ring). Would you be open to changes that allow the use of ring and rustls as an alternative to openssl?
The configuration file should accept globs in the include directive.
Example:
include = [
"domains_enabled/*.toml"
]
ACMEd uses custom code for some things that are already provided by external crates.
The function acme_common::crypto::openssl_certificate::gen_certificate()
uses X509Extension::new
which is deprecated by the openssl
crate since version 0.10.51.
Currently, the extensions
module does not provide the structure for the acmeIdentifier
extension introduced by RFC 8737, leaving no other choice than using X509Extension::new_from_der
.
The use of deprecated function warning has been silenced, however it is not a long-term solution. The call to X509Extension::new
should be replaced by something else.
Hi,
i come with a good first issue, i want to list necessary packages to compile Acmed on Alpine Linux in README file;
openssl-dev
to avoid compilation problem with openssl cratealpine-sdk
similar to debian-essentialsgit
of courserust
cargo
Splitting this out of #71.
acmed
is currently renewing certificates with a fixed offset from the expiry of the certificate. It's configurable, but defaults to 3 weeks. The recommendation from Let's Encrypt here is a third of the certificate lifetime, so that means 30 days before expiry. The default should therefore probably be changed here.
Another thing to do about scheduling renewals, to help acme providers with load spikes, is adding some randomization to the renewal time. Let's Encrypt suggests to space out certificate renewals by renewing some certificates a few days early. This is difficult to implement with the current architecture, but should be nearly trivial to implement once the async rewrite is done.
Dehydrated automatically fetches intermediate certificates by walking up the chain:
From this, a fullchain.pem
is generated for every issued certificate.
Could something like this be implemented for acmed
as well, maybe as a default hook?
Add support for ACME Renewal Information (ARI)
https://datatracker.ietf.org/doc/draft-ietf-acme-ari/
Implementation info/guide posted on let's encrypt website
https://letsencrypt.org/2024/04/25/guide-to-integrating-ari-into-existing-acme-clients
OpenSSL supports EdDSA since version 1.1.1, so in theory ACMEd can use it to support Ed25519 and Ed448 certificates and account keys.
However, if certificates are not a problem, account keys are because OpenSSL's EdDSA support is not complete yet:
The ACME protocol requires that account keys be exported in the JWK (JSON Web Keys) format. To do so, we uses bindings to OpenSSL's EVP_PKEY_get1_*
functions (EVP_PKEY_get1_RSA
for RSA and EVP_PKEY_get1_EC_KEY
for ECDSA). Unfortunately, OpenSSL does not currently provides the EVP_PKEY_get1_ED25519
and EVP_PKEY_get1_ED448
functions we need. Those functions has recently been added in OpenSSL (openssl/openssl@7c664b1) and should be available in version 3.0.0, which has not been released when this issue has been created.
Having those functions coming with OpenSSL 3.0.0 is a thing, but having bindings to those functions is a different topic. Although the people behind the openssl
crate already started to work on OpenSSL 3.0.0 support (sfackler/rust-openssl#1263), bindings for those new EVP_PKEY_get1_*
functions are yet to be done (sfackler/rust-openssl#1334).
On ACMEd's side, the EdDSA has already implemented wherever possible and is subject to conditional compilation and therefore not activated. I do not plan to release partial EdDSA support: either it is supported for both certificates and account keys or not supported at all. However, Ed25519 and Ed448 are separated: one can be activated without the other.
So, the plan to support EdDSA in ACMEd is the following :
EVP_PKEY_get1_ED25519
and EVP_PKEY_get1_ED448
implemented in the openssl
crate.Contributing to the openssl
crate in order to add bindings to EdDSA deserializers seems to be the most effective (and only?) way to help ACMEd on this topic.
During the port from attohttpc
to reqwest
, I've noticed that there's a bunch of boilerplate code around error handling. A lot of the formatting/From impls etc can be reduced a lot by using stuff like https://lib.rs/crates/thiserror.
The README why-is-rsa-2048-the-default section makes two statements that could benefit a user a bit more to clarify.
It is not obvious at the first sight, but RSA 4096 is NOT twice more secure than RSA 2048. In fact, it adds a lot more calculation while providing only a small security improvement.
Appears to be related to the term security level? I came across an article that explained it a bit more clearly here:
In this regard, a common RSA 2048-bit public key provides a security level of 112 bits. However, ECDSA requires only 224-bit sized public keys to provide the same 112-bit security level.
In the next common level of 128 bits, RSA requires a 3072-bit key, while ECDSA only 256 bits.
A similar comparison of security level without comparing to ECDSA is presented on Wikipedia:
1024-bit RSA keys are equivalent in strength to 80-bit symmetric keys, 2048-bit RSA keys to 112-bit symmetric keys, 3072-bit RSA keys to 128-bit symmetric keys, and 15360-bit RSA keys to 256-bit symmetric keys.
This isn't an area I understand that well, but I thought increasing the bits still has a notable impact on computational costs? (to attack)
2(1-bit), 4, 8, 16 ,32, 64 (8 bits), the amount of values doubles with each additional bit added. The above information doesn't clarify symmetric key size equivalent bits (which I've referred to as security level above, but is what these bit values are meant to be representing?), but we can see that between 2048 and 3072 RSA sizes, 16 bits should be well over 2x the amount of computation to break?
This StackExchange answer shows RSA-4096 should be ~150 bits, with a delta of ~40 bits. This article seems to address the topic well in regards to understanding the statement the README has(although I don't quite know if the 2x strength you mention implying it's less of an increase is valid?). Perhaps both, or one of these links would be suitable to add to the README?
If so, using RSA 4096 in the final certificate will not add any additional security since a system's global security level is equal to the level of its weakest point.
This also had me curious, and I'm not sure if it's entirely valid either? See this StackExchange discussion.
I think you're referring to security here in terms of CA certs in the chain, but not the end-entity cert strength which is still useful for encryption security of messages being exchanged, whereas the other certs are for validation, but breaking them won't decrypt any prior recorded encrypted messages.
The link seems to indicate that there is still benefit (security wise) for end-entity certs that have larger key sizes(possibly not for DHE though where it's only used for signing but not in the key exchange according to one of the comments there).
Related to #80 and kinda also #24, it'd be helpful to have better insight into what acmed
is doing, by exposing certain metrics on it. As everything in acmed
is related to http requests in one form or another, it's probably enough to expose counters for http requests, including labels for
In addition to that, it might also make sense to export some metrics on a few other areas, like the hooks and rate limit state, but I think those metrics are less useful than the ones for the http requests.
While it's also possible to expose certain metrics for the certificates themselves, I'm not sure whether that makes sense, as it's easily possible to do externally (see https://gist.github.com/jcgruenhage/8ef2f2fbc0af48189ee4585fb8054e40 for example), where as these are metrics only available to acmed
itself.
trying to build with --locked
reports an error, because the version wasn't bumped in the lockfile:
error: the lock file acmed-0.22.0/Cargo.lock needs to be updated but --locked was passed to prevent this
If you want to try to generate the lock file without accessing the network, remove the --locked flag and use --offline instead.
Listing all files in include can be cumbersome. It would be more easy if include
supported directories or globs instead of single files.
When starting acmed
like this:
acmed -f --log-stderr --log-level debug
...I get the following error:
Error: IO error: Permission denied (os error 13)
I'm sure I'll find out what the reason is, but the error message could be improved, to say where the permission fails. Is it the PID file? The accounts directory? Something else?
Recently, there has been a lot of supply-chain attacks (1, 2, 3). While the most notorious are part of the JavaScript ecosystem, such an attack is conceivable in the Rust ecosystem.
Because ACMEd does not commit the Cargo.lock
file, it is vulnerable to such an attack from one of its dependencies. This issue will be closed when this is solved.
Locking the dependencies is not a trivial job. It comes with two major sources of work:
Currently, ACMEd is still in a development phase and subject to breaking changes. Most of its improvement comes with little to no warning, hence a little chaos.
My current and personal point of view on this matter is that ACMEd should only lock the dependencies once it reaches the first stable version. Therefore, all version 0.x.y will not include the Cargo.lock
file, but versions 1.0.0 and later will include it.
Those who whishes to package ACMEd for their system may want to enforce dependencies lock. While ACMEd is still in development, those people may create and include their own Cargo.lock
file. The counterpart of doing so is that you would have to maintain this file yourself, and therefore be responsible for all the subsequent work.
Good news: ACMEd is starting to be quite mature and therefore the first stable version should not be in an eternity.
Bad news: before declaring ACMEd stable, I really wish to refactor a few things, which may still take some time, depending on my personal life.
It seems that when running acmed in foreground using --foreground
, a PID file is still created. is this required?
(Furthermore, it appears that the PID file is not cleaned up when the process stops with an error on parsing the config file...)
It's pointless to require a pidfile with any modern process supervisor, or when just running acmed locally in a terminal for debug purposes, especially that it just serves as a lockfile with extra steps.
ACMEd already include a few unit tests within its code. Although that's a good thing, it's also insufficient. One way to improve it is to create functional/integration tests (I don't know/care about the exact differences).
Currently, I have a local directory containing a few configuration files for pebble and ACMEd and I run them once in a while in order to tests is everything works. However, there is no automation at all and those files have a few issues that requires me to comment/uncomment some parts in order to run some tests. It would be nice if a similar process would be included in the repository and, maybe automated.
A single command should run those tests. A bash or sh script should do.
All the outputs (certificates, keys, etc.) should be located in a directory, maybe something like /tmp/acmed-test
. This directory should be cleaned at the beginning of the tests so the files stays afterwards in order to be manually checked.
The goal is to test all possible ACMEd features (challenges, key types, internationalized domain name and so on), however most tests may be added after the script itself is done.
Bonus: run it as a GitHub action
RFC 2606 reserves a few top level DNS names for specific purposes and recommends to use the .test
TLD for testing. Therefore, all domains included in those tests should use the .test
TLD.
I would also recommend to use exclusively the acmed.test
domain name as well as any other subdomain that might help.
Testing ACMEd requires a few external tools:
acmed.test
domain name, I don't know if it's possible to dynamically add entries so it could help validating with the dns-01
challengehttp-01
challenge, however I don't know for tls-alpn-01
All daemons should be killed at the end of tests. This might require to store their respective PID.
As far as I know, Pebble can only be run with or without the external account requirement. Therefore, two Pebble instance should be run.
Pebble should always run in strict mode.
Currently, an easy way to run ACMEd for such tests is the following:
cargo run --bin acmed -- --config "/path/to/acmed-config.toml" --root-cert "/path/to/pebble/root_cert.pem" --no-pid-file --foreground --log-stderr --log-level trace
This issue tracks the criteria the template engine used to render hooks should have and which are eligible. At time of writing, none is eligible, hence the current one is kept. Please submit other crates if they are worth investigating.
The crate does not require to have the templates at compile time. Hooks are defined by the user, therefore their templates are not available when ACMEd is compiled.
I really like the Jinja2 style. Not all features are required, only the most basics (variables display, basic tests and loops).
See issue #1
Dependencies of dependencies are counted too.
Method to count dependencies:
cargo new --bin test_<lib_name>
Cargo.toml
filecargo build
cargo tree | grep -v '*' | wc -l | sed 's/$/-2/' | bc
The minus 2 is intended to remove the project and the library itself from the count.
ACMEd already uses nom and should not depend on a second parser.
Crate | Dynamic templates | Jinja2-like | Dependencies | No pest | Comment |
---|---|---|---|---|---|
askama | ❌ | ✔️ | 33 ❌ | ✔️ | Impossible to use in ACMEd |
tera | ✔️ | ✔️ | 89 ❌ | ❌ | Would use if fewer dependencies and pest wouldn't be one of them |
liquid | ✔️ | ✔️ | 115 ❌ | ❌ | Would use if fewer dependencies and pest wouldn't be one of them |
templar | ✔️ | ✔️ | 43 ❌ | ❌ | Current version (0.5) unusable because of debug print |
tinytemplate | ✔️ | 10 ✔️ | ✔️ | Quite simple and effective | |
ramhorns | ✔️ | ❌ | 19 |
✔️ | Would use if it was a Jinja2-like and had a little fewer dependencies |
handlebars | ✔️ | ❌ | 45 ❌ | ❌ | Currently used |
Splitting this out of #71.
Let's Encrypt recommends that renewal failures should not be treated as fatal errors, but that those should be gracefully retried with an exponential backoff. acmed
is pretty far off here (as far as I can tell, I'm not quite sure actually). As far as I can tell, acmed
will check each certificate for whether it needs renewal once per hour (at least that's the default) and not do any sort of exponential backoff. Some specific errors are also treated as recoverable, which are then retried 20 times with 1s delay in between. Whether retrying recoverable errors in such quick succession is in line with what Let's Encrypt wants aside, the more important thing would be to implement some for of exponential backoff. Their suggested schedule is retrying after 1 minute, then 10 minutes, then 100 minutes and each subsequent retry after 1 day. I think it'd be in the spirit of acmed
to have this as something that's configurable per endpoint, with defaulting to the schedule suggested by Let's Encrypt.
This again is something that's tricky to implement with the current architecture, but should become fairly trivial with the async rewrite.
A logo for ACMEd would be a nice to have. Because I don't have any graphical talent, all I can do is to write the specifications:
In addition to those requirements, it should be released under one of the following free licenses:
Currently, a new http client is created on every request. The method that handles this potentially reads files, and those reads shouldn't have to happen on each request. Not terrible, but not ideal.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.