From 4b51b40a44beb5629a9e8a8d5e78284afc2382e3 Mon Sep 17 00:00:00 2001 From: rollniak Date: Thu, 16 Apr 2020 18:24:37 +0200 Subject: [PATCH 01/12] `Make install` now work with the busybox toolchain. --- CHANGELOG.md | 5 +++++ Makefile | 18 +++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39ffbbe..9f261d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +## Fixed +- `make install` now work with the busybox toolchain. + ## [0.7.0] - 2020-03-12 ### Added diff --git a/Makefile b/Makefile index 8898179..dec4bef 100644 --- a/Makefile +++ b/Makefile @@ -35,15 +35,15 @@ update: cargo update install: - install -D --mode=0755 $(TARGET_DIR)/acmed $(DESTDIR)$(BINDIR)/acmed - install -D --mode=0755 $(TARGET_DIR)/tacd $(DESTDIR)$(BINDIR)/tacd - install -D --mode=0644 $(TARGET_DIR)/man/acmed.8.gz $(DESTDIR)$(DATADIR)/man/man8/acmed.8.gz - install -D --mode=0644 $(TARGET_DIR)/man/acmed.toml.5.gz $(DESTDIR)$(DATADIR)/man/man5/acmed.toml.5.gz - install -D --mode=0644 $(TARGET_DIR)/man/tacd.8.gz $(DESTDIR)$(DATADIR)/man/man8/tacd.8.gz - install -D --mode=0644 acmed/config/acmed.toml $(DESTDIR)$(SYSCONFDIR)/acmed/acmed.toml - install -D --mode=0644 acmed/config/default_hooks.toml $(DESTDIR)$(SYSCONFDIR)/acmed/default_hooks.toml - install -d --mode=0700 $(DESTDIR)$(SYSCONFDIR)/acmed/accounts - install -d --mode=0755 $(DESTDIR)$(SYSCONFDIR)/acmed/certs + install -D -m 0755 $(TARGET_DIR)/acmed $(DESTDIR)$(BINDIR)/acmed + install -D -m 0755 $(TARGET_DIR)/tacd $(DESTDIR)$(BINDIR)/tacd + install -D -m 0644 $(TARGET_DIR)/man/acmed.8.gz $(DESTDIR)$(DATADIR)/man/man8/acmed.8.gz + install -D -m 0644 $(TARGET_DIR)/man/acmed.toml.5.gz $(DESTDIR)$(DATADIR)/man/man5/acmed.toml.5.gz + install -D -m 0644 $(TARGET_DIR)/man/tacd.8.gz $(DESTDIR)$(DATADIR)/man/man8/tacd.8.gz + install -D -m 0644 acmed/config/acmed.toml $(DESTDIR)$(SYSCONFDIR)/acmed/acmed.toml + install -D -m 0644 acmed/config/default_hooks.toml $(DESTDIR)$(SYSCONFDIR)/acmed/default_hooks.toml + install -d -m 0700 $(DESTDIR)$(SYSCONFDIR)/acmed/accounts + install -d -m 0755 $(DESTDIR)$(SYSCONFDIR)/acmed/certs clean: cargo clean From 43127c8f8008cebd96eb4ffa4b06daaa0385eaa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Br=C3=A9ard?= Date: Tue, 12 May 2020 11:11:26 +0200 Subject: [PATCH 02/12] Update the Alpine Linux build instructions ref #21 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a305f01..1d4f840 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ ACMEd depends OpenSSL 1.1.0 or higher. On systems based on Debian/Ubuntu, you may need to install the `libssl-dev`, `build-essential` and `pkg-config` packages. -On Alpine Linux, you may need to install the `openssl-dev` and `alpine-sdk` packages. Also, you should use the `rust` and `cargo` packages in the community repository: installing Rust using rustup will result in compilation errors. +On Alpine Linux, you may need to install the `openssl-dev` and `alpine-sdk` packages. Since Alpine Linux 3.11 you can use the `rust` and `cargo` packages from the community repository. Older versions of Alpine Linux will require you to install Rust 1.44 or later using rustup. ``` $ make From 2cec2e15949d5708767d7b0b62a975044dee02e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Br=C3=A9ard?= Date: Tue, 12 May 2020 11:12:37 +0200 Subject: [PATCH 03/12] Update the travis-ci configuration --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 43c0d20..3ed0147 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ rust: - "1.39.0" - "1.40.0" - "1.41.1" + - "1.42.0" + - "1.43.1" - "stable" - "beta" - "nightly" From 78683c593297d4bff4bf11a853babad95e2b3a4e Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Fri, 29 May 2020 01:02:02 +0200 Subject: [PATCH 04/12] acmed.toml(5): Fix config key: hook_type -> type --- man/en/acmed.toml.5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/en/acmed.toml.5 b/man/en/acmed.toml.5 index 6ab9438..4783b06 100644 --- a/man/en/acmed.toml.5 +++ b/man/en/acmed.toml.5 @@ -99,7 +99,7 @@ for more details. Defines if an error return value for this hook is allowed or not. If not allowed, a failure in this hook will fail the whole certificate request process. Default is false. .It Cm name Ar string The name the hook is registered under. Must be unique. -.It Cm hook_type Ar array +.It Cm type Ar array Array of strings. Possible types are: .Bl -dash -compact .It From bd3b7cc0861221ce19180c12943607f2a57c3fc9 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Fri, 29 May 2020 01:02:19 +0200 Subject: [PATCH 05/12] acmed.toml(5): Grammar fixes --- man/en/acmed.toml.5 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/man/en/acmed.toml.5 b/man/en/acmed.toml.5 index 4783b06..93ae22b 100644 --- a/man/en/acmed.toml.5 +++ b/man/en/acmed.toml.5 @@ -204,15 +204,15 @@ Path to the directory where certificates and their associated private keys are s Names of hooks that will be called when requesting a new certificate. The hooks are guaranteed to be called sequentially in the declaration order. .El .Sh WRITING A HOOK -When requesting a certificate to a CA using ACME, there is three steps that are hard to automatize. The first one is solving challenges in order to prove the ownership of every domains to be included: it requires to interact with the configuration of other services, hence depends on how the infrastructure works. The second one is restarting all the services that uses a given certificate, for the same reason. The last one is archiving: although several default methods can be implemented, sometimes admins wants or are required to do it in a different way. +When requesting a certificate from a CA using ACME, there are three steps that are hard to automatize. The first one is solving challenges in order to prove the ownership of every domains to be included: It requires to interact with the configuration of other services, hence depends on how the infrastructure works. The second one is restarting all the services that use a given certificate, for the same reason. The last one is archiving: Although several default methods can be implemented, sometimes admins wants or are required to do it in a different way. .Pp -In order to allow a full automation of the three above steps without imposing arbitrary restrictions or methods, +In order to allow full automation of the three above steps without imposing arbitrary restrictions or methods, .Xr acmed 8 -uses hooks. Fundamentally, a hook is a command line template that will be called at a specific time of the process. Such approach allows admins to use any executable script or program located on the machine to customize the process. +uses hooks. Fundamentally, a hook is a command line template that will be called at a specific time of the process. Such an approach allows admins to use any executable script or program located on the machine to customize the process. .Pp For a given certificate, hooks are guaranteed to be called sequentially in the declaration order. It is therefore possible to have a hook that depends on another one. Nevertheless, several certificates may be renewed at the same time. Hence, hooks shall not use globing or any other action that may disrupt hooks called by a different certificate. .Pp -A hook have a type that will influence both the moment it is called and the available template variables. It is possible to declare several types. In such a case, the hook will be invoked whenever one of its type request it. When called, the hook only have access to template variable for the current type. If a hook uses a template variable that does not exists for the current type it is invoked for, the variable is empty. +A hook has a type that will influence both the moment it is called and the available template variables. It is possible to declare several types. In such a case, the hook will be invoked whenever one of its type request it. When called, the hook only have access to template variable for the current type. If a hook uses a template variable that does not exists for the current type it is invoked for, the variable is empty. .Pp When writing a hook, the values of .Em args , From 972dd4d4be5d3b9be6328680487feef90772dfad Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Fri, 29 May 2020 01:14:10 +0200 Subject: [PATCH 06/12] Log certificate domains before and after renewal Right now only the id is logged as a prefix (e.g. crt-3), so it's not possible to easily determine *which* certificate was renewed, or failed to renew. --- acmed/src/acme_proto.rs | 2 +- acmed/src/certificate.rs | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/acmed/src/acme_proto.rs b/acmed/src/acme_proto.rs index 6e98341..f0d2155 100644 --- a/acmed/src/acme_proto.rs +++ b/acmed/src/acme_proto.rs @@ -210,6 +210,6 @@ pub fn request_certificate(cert: &Certificate, root_certs: &[String]) -> Result< let (crt, _) = http::get_certificate(cert, root_certs, &crt_url, &data_builder, &nonce)?; storage::write_certificate(cert, &crt.as_bytes())?; - cert.info("Certificate renewed"); + cert.info(&format!("Certificate renewed (domains: {})", cert.domain_list())); Ok(()) } diff --git a/acmed/src/certificate.rs b/acmed/src/certificate.rs index b871100..4ea6089 100644 --- a/acmed/src/certificate.rs +++ b/acmed/src/certificate.rs @@ -105,7 +105,7 @@ impl Certificate { fn is_expiring(&self, cert: &X509Certificate) -> Result { let expires_in = cert.expires_in()?; - self.debug(&format!("expires in {} days", expires_in.as_secs() / 86400)); + self.debug(&format!("Certificate expires in {} days", expires_in.as_secs() / 86400)); // TODO: allow a custom duration (using time-parse ?) // 1814400 is 3 weeks (3 * 7 * 24 * 60 * 60) let renewal_time = Duration::new(1_814_400, 0); @@ -134,7 +134,17 @@ impl Certificate { has_miss } + /// Return a comma-separated list of the domains this certificate is valid for. + pub fn domain_list(&self) -> String { + self.domains + .iter() + .map(|domain| &*domain.dns) + .collect::>() + .join(",") + } + pub fn should_renew(&self) -> Result { + self.debug(&format!("Checking for renewal (domains: {})", self.domain_list())); if !certificate_files_exists(&self) { self.debug("certificate does not exist: requesting one"); return Ok(true); From 3e49c938ea059555974cc822d7ec25cc5cfbbb23 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Fri, 29 May 2020 01:29:09 +0200 Subject: [PATCH 07/12] Remove trailing period from logs Some logs contain a trailing period, some don't. Since they don't add any information, I removed them for more consistency. --- acmed/src/acme_proto/account.rs | 6 ++---- acmed/src/certificate.rs | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/acmed/src/acme_proto/account.rs b/acmed/src/acme_proto/account.rs index 4068264..9752657 100644 --- a/acmed/src/acme_proto/account.rs +++ b/acmed/src/acme_proto/account.rs @@ -50,13 +50,11 @@ pub fn init_account(cert: &Certificate) -> Result<(), Error> { let sign_alg = SignatureAlgorithm::from_str(crate::DEFAULT_JWS_SIGN_ALGO)?; let key_pair = sign_alg.gen_key_pair()?; storage::set_account_keypair(cert, &key_pair)?; - let msg = format!("Account {} created.", &cert.account.name); - cert.info(&msg) + cert.info(&format!("Account {} created", &cert.account.name)); } else { // TODO: check if the keys are suitable for the specified signature algorithm // and, if not, initiate a key rollover. - let msg = format!("Account {} already exists.", &cert.account.name); - cert.debug(&msg) + cert.debug(&format!("Account {} already exists", &cert.account.name)); } Ok(()) } diff --git a/acmed/src/certificate.rs b/acmed/src/certificate.rs index 4ea6089..4991427 100644 --- a/acmed/src/certificate.rs +++ b/acmed/src/certificate.rs @@ -155,9 +155,9 @@ impl Certificate { let renew = renew || self.is_expiring(&cert)?; if renew { - self.debug("The certificate will be renewed now."); + self.debug("The certificate will be renewed now"); } else { - self.debug("The certificate will not be renewed now."); + self.debug("The certificate will not be renewed now"); } Ok(renew) } From 70d013254ffee353693ebfcfff80850a02e505dd Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Fri, 29 May 2020 01:38:56 +0200 Subject: [PATCH 08/12] Document how certificates are identified --- man/en/acmed.toml.5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/man/en/acmed.toml.5 b/man/en/acmed.toml.5 index 6ab9438..c6d5cb3 100644 --- a/man/en/acmed.toml.5 +++ b/man/en/acmed.toml.5 @@ -158,6 +158,8 @@ The email address used to contact the account's holder. .El .It Ic certificate Array of table representing a certificate that will be requested to a CA. +.Pp +Note that certificates are identified by the first domain in the list of domains. That means that if you reorder the domains so that a different domain is at the first position, a new certificate with a new name will be issued. .Bl -tag .It Ic account Ar string Name of the account to use. From fad514c1eec606ea71a66add5cd2b67d00445d80 Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Fri, 29 May 2020 03:08:48 +0200 Subject: [PATCH 09/12] Add example systemd service file --- README.md | 5 ++++- acmed.service.example | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 acmed.service.example diff --git a/README.md b/README.md index 1d4f840..82c8b3c 100644 --- a/README.md +++ b/README.md @@ -97,11 +97,14 @@ Running ACMEd as root is the simplest configuration since you do not have to wor However, if you are concerned with safety, you should create a dedicated user for ACMEd. Before doing so, please consider the following points: "Will your services be able to read both the private key and the certificate?" and "Will the ACMEd user be able to execute the hooks?". The later could be achieved using sudo or Polkit. - ### Why is there no option to run ACMEd as a specific user or group? The reason some services has such an option is because at startup they may have to load data only accessible by root, hence they have to change the user themselves after those data are loaded. For example, this is wildly used in web servers so they load a private key, which should only be accessible by root. Since ACMEd does not have such requirement, it should be run directly as the correct user. +### How can I run ACMEd with systemd? + +An example service file is provided (see `acmed.service.example`). The file might need adjustments in order to work on your system (e.g. binary path, user, group, directories...), but it's probably a good starting point. + ### Is it suitable for beginners? It depends on your definition of a beginner. This software is intended to be used by system administrator with a certain knowledge of their environment. Furthermore, it is also expected to know the bases of the ACME protocol. Let's Encrypt wrote a nice article about [how it works](https://letsencrypt.org/how-it-works/). diff --git a/acmed.service.example b/acmed.service.example new file mode 100644 index 0000000..96c2bb9 --- /dev/null +++ b/acmed.service.example @@ -0,0 +1,29 @@ +# systemd example unit file. Please adjust. + +[Unit] +Description=ACME client daemon +After=network.target + +[Service] +User=acmed +Group=acmed + +# Working directory +WorkingDirectory=/etc/acmed + +# Starting, stopping, timeouts +ExecStart=/usr/local/bin/acmed --foreground --pid-file /etc/acmed/acmed.pid --log-level debug --log-stderr +TimeoutStartSec=3 +TimeoutStopSec=5 +Restart=on-failure +KillSignal=SIGINT + +# Sandboxing, reduce privileges, only allow write access to working directory +NoNewPrivileges=yes +PrivateTmp=yes +PrivateUsers=yes +ProtectSystem=strict +ReadWritePaths=/etc/acmed/ + +[Install] +WantedBy=multi-user.target From eccac72460be1a7c9e050091b1dfeba76b0e02e7 Mon Sep 17 00:00:00 2001 From: Rodolphe Breard Date: Fri, 29 May 2020 12:48:29 +0200 Subject: [PATCH 10/12] Fix the default PID file name --- acmed/src/main.rs | 2 +- tacd/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acmed/src/main.rs b/acmed/src/main.rs index fd351e0..01c3ccb 100644 --- a/acmed/src/main.rs +++ b/acmed/src/main.rs @@ -14,7 +14,7 @@ mod storage; pub const APP_NAME: &str = "ACMEd"; pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const DEFAULT_PID_FILE: &str = "/var/run/admed.pid"; +pub const DEFAULT_PID_FILE: &str = "/var/run/acmed.pid"; pub const DEFAULT_CONFIG_FILE: &str = "/etc/acmed/acmed.toml"; pub const DEFAULT_ACCOUNTS_DIR: &str = "/etc/acmed/accounts"; pub const DEFAULT_CERT_DIR: &str = "/etc/acmed/certs"; diff --git a/tacd/src/main.rs b/tacd/src/main.rs index ce57bd3..b0d63eb 100644 --- a/tacd/src/main.rs +++ b/tacd/src/main.rs @@ -11,7 +11,7 @@ use std::io::{self, Read}; const APP_NAME: &str = env!("CARGO_PKG_NAME"); const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); -const DEFAULT_PID_FILE: &str = "/var/run/admed.pid"; +const DEFAULT_PID_FILE: &str = "/var/run/tacd.pid"; const DEFAULT_LISTEN_ADDR: &str = "127.0.0.1:5001"; const ALPN_ACME_PROTO_NAME: &[u8] = b"\x0aacme-tls/1"; From 70db8e6dd9fc8805fe9e8b9522246b07bf37f935 Mon Sep 17 00:00:00 2001 From: Rodolphe Breard Date: Fri, 29 May 2020 13:00:20 +0200 Subject: [PATCH 11/12] Prevent unnecessary creation of a PID file When running in foreground, a PID file should be created only if the `--pid-file` option is specified. Rel #25 --- acme_common/src/lib.rs | 8 ++++---- acmed/src/main.rs | 3 ++- tacd/src/main.rs | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/acme_common/src/lib.rs b/acme_common/src/lib.rs index 0e6b3aa..8578322 100644 --- a/acme_common/src/lib.rs +++ b/acme_common/src/lib.rs @@ -40,12 +40,12 @@ pub fn b64_encode>(input: &T) -> String { base64::encode_config(input, base64::URL_SAFE_NO_PAD) } -pub fn init_server(foreground: bool, pid_file: &str) { +pub fn init_server(foreground: bool, pid_file: Option<&str>, default_pid_file: &str) { if !foreground { - let daemonize = Daemonize::new().pid_file(pid_file); + let daemonize = Daemonize::new().pid_file(pid_file.unwrap_or(default_pid_file)); exit_match!(daemonize.start()); - } else { - exit_match!(write_pid_file(pid_file)); + } else if let Some(f) = pid_file { + exit_match!(write_pid_file(f)); } } diff --git a/acmed/src/main.rs b/acmed/src/main.rs index 01c3ccb..87c4660 100644 --- a/acmed/src/main.rs +++ b/acmed/src/main.rs @@ -117,7 +117,8 @@ fn main() { init_server( matches.is_present("foreground"), - matches.value_of("pid-file").unwrap_or(DEFAULT_PID_FILE), + matches.value_of("pid-file"), + DEFAULT_PID_FILE, ); let config_file = matches.value_of("config").unwrap_or(DEFAULT_CONFIG_FILE); diff --git a/tacd/src/main.rs b/tacd/src/main.rs index b0d63eb..1f59982 100644 --- a/tacd/src/main.rs +++ b/tacd/src/main.rs @@ -42,7 +42,8 @@ fn get_acme_value(cnf: &ArgMatches, opt: &str, opt_file: &str) -> Result Result<(), Error> { acme_common::init_server( cnf.is_present("foreground"), - cnf.value_of("pid-file").unwrap_or(DEFAULT_PID_FILE), + cnf.value_of("pid-file"), + DEFAULT_PID_FILE, ); let domain = get_acme_value(cnf, "domain", "domain-file")?; let domain = to_idna(&domain)?; From 52973b4b9e313d87b6148a55a11ccacfdaeb8a3d Mon Sep 17 00:00:00 2001 From: Rodolphe Breard Date: Fri, 29 May 2020 13:12:30 +0200 Subject: [PATCH 12/12] Remove the PID file after exit Fix #25 --- acme_common/src/lib.rs | 9 ++++++++- acmed/src/main.rs | 3 ++- tacd/src/main.rs | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/acme_common/src/lib.rs b/acme_common/src/lib.rs index 8578322..147945d 100644 --- a/acme_common/src/lib.rs +++ b/acme_common/src/lib.rs @@ -1,7 +1,7 @@ use daemonize::Daemonize; use std::fs::File; use std::io::prelude::*; -use std::process; +use std::{fs, process}; pub mod crypto; pub mod error; @@ -57,6 +57,13 @@ fn write_pid_file(pid_file: &str) -> Result<(), error::Error> { Ok(()) } +pub fn clean_pid_file(pid_file: Option<&str>) -> Result<(), error::Error> { + if let Some(f) = pid_file { + fs::remove_file(f)?; + } + Ok(()) +} + #[cfg(test)] mod tests { use super::to_idna; diff --git a/acmed/src/main.rs b/acmed/src/main.rs index 87c4660..1a5c657 100644 --- a/acmed/src/main.rs +++ b/acmed/src/main.rs @@ -1,5 +1,5 @@ use crate::main_event_loop::MainEventLoop; -use acme_common::init_server; +use acme_common::{clean_pid_file, init_server}; use clap::{App, Arg}; use log::error; @@ -126,6 +126,7 @@ fn main() { Ok(s) => s, Err(e) => { error!("{}", e); + let _ = clean_pid_file(matches.value_of("pid-file")); std::process::exit(1); } }; diff --git a/tacd/src/main.rs b/tacd/src/main.rs index 1f59982..cc772aa 100644 --- a/tacd/src/main.rs +++ b/tacd/src/main.rs @@ -3,7 +3,7 @@ mod openssl_server; use crate::openssl_server::start as server_start; use acme_common::crypto::X509Certificate; use acme_common::error::Error; -use acme_common::to_idna; +use acme_common::{clean_pid_file, to_idna}; use clap::{App, Arg, ArgMatches}; use log::{debug, error, info}; use std::fs::File; @@ -151,6 +151,7 @@ fn main() { Ok(_) => {} Err(e) => { error!("{}", e); + let _ = clean_pid_file(matches.value_of("pid-file")); std::process::exit(1); } };