diff --git a/.gitignore b/.gitignore index 8d64d85..c48d8bb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ /*.src.rpm /.build-* /acme-tiny-4.1.0.tar.gz +/results_acme-tiny +/acme-tiny-4.1.1.tar.gz +/acme-tiny-5.0.1.tar.gz diff --git a/README-fedora.md b/README-fedora.md index 775e524..7a010fc 100644 --- a/README-fedora.md +++ b/README-fedora.md @@ -1,3 +1,8 @@ +# acme-tiny-core Fedora package + +This package contains only the upstream python script - for those +who prefer their own framework for cert maintenance. + # acme-tiny Fedora package The Fedora package for acme-tiny adds a tiny framework to make issuing @@ -74,7 +79,7 @@ Use systemctl start acme-tiny ``` to run the service now. The certificate should appear in `/var/lib/acme/certs`, -and errors will be in journalctl. Alternatively (and on EL6), run +and errors will be in journalctl. Alternatively, run `/usr/libexec/acme-tiny/sign` as the acme user, and errors will go to your terminal. @@ -99,34 +104,38 @@ and other apps. Sendmail is a special problem - it insists that any certificates it loads be only writable by root. This is at odds with the privilege separation of the acme user. (Obviously, the private key must be accessible only by root.) You -can, of course, copy the crt file to /etc/pki/tls/certs as root and change the -mode. But this has to be done every time the cert is renewed. You can -install `incron` to do this. After installing, create `/etc/incron.d/acme` -with the line -``` -/var/lib/acme/certs/mail.crt IN_MOVED_TO cp $@ /etc/pki/tls/certs -``` -where `mail.crt` is the certificate sendmail will use. Sendmail -can then load it from /etc/pki/tls/certs and be happy. This also -solves the file context problem if you add lines for other certificates. -You might wonder why we don't simply supply an acme incrontab as part -of the package with a wildcard, for example: -``` -/var/lib/acme/certs/*.crt IN_MOVED_TO cp $@ /etc/pki/tls/certs -``` -The answer is that incron is insecure, and very nasty things can be -done by putting shell meta characters (including semicolon and quote!) in -filenames that then become part of a command run as root. The first example -above uses a fixed filename, so that is safe. Complain to incron -upstream - they need an option to use a simple execvpe instead of -using the shell. Then it would at least be possible to carefully -handle [malicious names](https://www.xkcd.com/327/). +can, of course, copy the crt file to `/etc/pki/tls/certs` as root and change +the mode. But this has to be done every time the cert is renewed. +The systemd `acme-tiny.service` runs `acme-tiny-notify.service` +afterward which executes as root. It calls +`/usr/libexec/acme-tiny/notify` for each nenewed cert. (This used +to be `/etc/acme-tiny/notify.sh` - which is now a symlink.) + +Suppose `/var/lib/acme/certs/mail.crt` is renewed, where `mail.crt` is the +certificate sendmail will use. The notify script +sees the reference to `/etc/pki/tls/certs/mail.crt` in `/etc/mail/*.cf`, and +copies the renewed cert to `/etc/pki/tls/certs`. Sendmail can then load it +from there and be happy. This also solves the file context problem. + +The notify script has built in support for the httpd, sendmail, and dovecot +packages for Fedora. To avoid restarting services multiple times, +the notify script records in `/var/lib/acme/.notify` +when it last restarted each service. + +By dropping scripts with names ending in `.sh` in `/etc/acme-tiny/notify.d`, +additional packages (e.g. nginx) can be supported. + +This new system should be compatible with older incrond configs, +but the additional `/etc/acme-tiny/notify.sh` calls from incrond are redundant. +Making them redundant, incidentally fixes +[BZ#1839904](https://bugzilla.redhat.com/show_bug.cgi?id=1839904). There +could still be a race condition missed in testing, so I would disable your old +acme incrontab. ## Logging and Error Reporting -On EL6, cron will email the acme user when certs are signed or errors -are encountered. Under systemd, errors and certs signed are logged -with the acme-tiny syslog identifier. +Under systemd, errors and certs signed are logged with the acme-tiny +syslog identifier. ## Virtual Hosts @@ -134,6 +143,10 @@ Most web servers can handle multiple logical web hosts - configuring that is beyond the scope of this document. Each virtual host may need to have its own certificate for SSL. They can all share the same key file (see above for how to use an existing key for certificate requests), or use different keys. -Put all the CSRs in /var/lib/acme/csr and the acme-tiny service will keep them -all renewed. This also works for certificates used by other SSL applications, -such as dovecot, sendmail, jabberd, or znc. +Note that apache can load certs directly from `/var/lib/acme/certs`, and +so notify.sh simply does `apachectl graceful`. + +Put all the CSRs in `/var/lib/acme/csr` and the acme-tiny service will keep +them all renewed. This also works for certificates used by other SSL +applications, such as dovecot, sendmail, jabberd, or znc. + diff --git a/acme-tiny-chain.patch b/acme-tiny-chain.patch deleted file mode 100644 index 5e681d9..0000000 --- a/acme-tiny-chain.patch +++ /dev/null @@ -1,118 +0,0 @@ -diff -up ./acme_tiny.py.chain ./acme_tiny.py ---- ./acme_tiny.py.chain 2017-05-16 03:57:46.000000000 -0400 -+++ ./acme_tiny.py 2017-11-22 12:18:56.963653336 -0500 -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/python - import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging - try: - from urllib.request import urlopen # Python 3 -@@ -12,7 +12,7 @@ LOGGER = logging.getLogger(__name__) - LOGGER.addHandler(logging.StreamHandler()) - LOGGER.setLevel(logging.INFO) - --def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): -+def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA, chain=False): - # helper function base64 encode for jose spec - def _b64(b): - return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "") -@@ -57,9 +57,9 @@ def get_crt(account_key, csr, acme_dir, - }) - try: - resp = urlopen(url, data.encode('utf8')) -- return resp.getcode(), resp.read() -+ return resp.getcode(), resp.read(), resp.info() - except IOError as e: -- return getattr(e, "code", None), getattr(e, "read", e.__str__)() -+ return getattr(e, "code", None), getattr(e, "read", e.__str__)(), None - - # find domains - log.info("Parsing CSR...") -@@ -80,9 +80,9 @@ def get_crt(account_key, csr, acme_dir, - - # get the certificate domains and expiration - log.info("Registering account...") -- code, result = _send_signed_request(CA + "/acme/new-reg", { -+ code, result, headers = _send_signed_request(CA + "/acme/new-reg", { - "resource": "new-reg", -- "agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf", -+ "agreement": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf", - }) - if code == 201: - log.info("Registered!") -@@ -96,7 +96,7 @@ def get_crt(account_key, csr, acme_dir, - log.info("Verifying {0}...".format(domain)) - - # get new challenge -- code, result = _send_signed_request(CA + "/acme/new-authz", { -+ code, result, headers = _send_signed_request(CA + "/acme/new-authz", { - "resource": "new-authz", - "identifier": {"type": "dns", "value": domain}, - }) -@@ -123,7 +123,7 @@ def get_crt(account_key, csr, acme_dir, - wellknown_path, wellknown_url)) - - # notify challenge are met -- code, result = _send_signed_request(challenge['uri'], { -+ code, result, headers = _send_signed_request(challenge['uri'], { - "resource": "challenge", - "keyAuthorization": keyauthorization, - }) -@@ -153,17 +153,32 @@ def get_crt(account_key, csr, acme_dir, - proc = subprocess.Popen(["openssl", "req", "-in", csr, "-outform", "DER"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - csr_der, err = proc.communicate() -- code, result = _send_signed_request(CA + "/acme/new-cert", { -+ code, result, headers = _send_signed_request(CA + "/acme/new-cert", { - "resource": "new-cert", - "csr": _b64(csr_der), - }) - if code != 201: - raise ValueError("Error signing certificate: {0} {1}".format(code, result)) - -+ certchain = [result] -+ if chain: -+ def parse_link_header(line): -+ m = re.search(r"^<([^>]*)>(?:\s*;\s*(.*))?$", line) -+ return (m.group(1), dict([(a[0],a[1].strip('"')) -+ for a in [attr.split("=") -+ for attr in m.group(2).split("\s*;\s*")]])) -+ -+ up = [ -+ link for link, attr in [ -+ parse_link_header(l) for l in headers.get_all("Link") -+ ] if attr['rel'] == 'up' -+ ] -+ certchain += [urlopen(url).read() for url in up] -+ - # return signed certificate! - log.info("Certificate signed!") -- return """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format( -- "\n".join(textwrap.wrap(base64.b64encode(result).decode('utf8'), 64))) -+ return "".join(["""-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format( -+ "\n".join(textwrap.wrap(base64.b64encode(cert).decode('utf8'), 64))) for cert in certchain]) - - def main(argv): - parser = argparse.ArgumentParser( -@@ -188,11 +203,19 @@ def main(argv): - parser.add_argument("--acme-dir", required=True, help="path to the .well-known/acme-challenge/ directory") - parser.add_argument("--quiet", action="store_const", const=logging.ERROR, help="suppress output except for errors") - parser.add_argument("--ca", default=DEFAULT_CA, help="certificate authority, default is Let's Encrypt") -+ parser.add_argument("--chain", action="store_true", -+ help="fetch and append intermediate certs to output") - - args = parser.parse_args(argv) - LOGGER.setLevel(args.quiet or LOGGER.level) -- signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, log=LOGGER, CA=args.ca) -- sys.stdout.write(signed_crt) -+ try: -+ signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, -+ log=LOGGER, CA=args.ca, chain=args.chain) -+ sys.stdout.write(signed_crt) -+ except Exception as e: -+ #if not args.quiet: raise e -+ LOGGER.error(e) -+ sys.exit(1) - - if __name__ == "__main__": # pragma: no cover - main(sys.argv[1:]) diff --git a/acme-tiny-chain2.patch b/acme-tiny-chain2.patch deleted file mode 100644 index 1c46c7d..0000000 --- a/acme-tiny-chain2.patch +++ /dev/null @@ -1,118 +0,0 @@ -diff -up ./acme_tiny.py.chain ./acme_tiny.py ---- ./acme_tiny.py.chain 2017-05-16 03:57:46.000000000 -0400 -+++ ./acme_tiny.py 2017-11-22 15:14:19.270485351 -0500 -@@ -1,4 +1,4 @@ --#!/usr/bin/env python -+#!/usr/bin/python - import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging - try: - from urllib.request import urlopen # Python 3 -@@ -12,7 +12,7 @@ LOGGER = logging.getLogger(__name__) - LOGGER.addHandler(logging.StreamHandler()) - LOGGER.setLevel(logging.INFO) - --def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA): -+def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA, chain=False): - # helper function base64 encode for jose spec - def _b64(b): - return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "") -@@ -57,9 +57,9 @@ def get_crt(account_key, csr, acme_dir, - }) - try: - resp = urlopen(url, data.encode('utf8')) -- return resp.getcode(), resp.read() -+ return resp.getcode(), resp.read(), resp.info() - except IOError as e: -- return getattr(e, "code", None), getattr(e, "read", e.__str__)() -+ return getattr(e, "code", None), getattr(e, "read", e.__str__)(), None - - # find domains - log.info("Parsing CSR...") -@@ -80,9 +80,9 @@ def get_crt(account_key, csr, acme_dir, - - # get the certificate domains and expiration - log.info("Registering account...") -- code, result = _send_signed_request(CA + "/acme/new-reg", { -+ code, result, headers = _send_signed_request(CA + "/acme/new-reg", { - "resource": "new-reg", -- "agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf", -+ "agreement": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf", - }) - if code == 201: - log.info("Registered!") -@@ -96,7 +96,7 @@ def get_crt(account_key, csr, acme_dir, - log.info("Verifying {0}...".format(domain)) - - # get new challenge -- code, result = _send_signed_request(CA + "/acme/new-authz", { -+ code, result, headers = _send_signed_request(CA + "/acme/new-authz", { - "resource": "new-authz", - "identifier": {"type": "dns", "value": domain}, - }) -@@ -123,7 +123,7 @@ def get_crt(account_key, csr, acme_dir, - wellknown_path, wellknown_url)) - - # notify challenge are met -- code, result = _send_signed_request(challenge['uri'], { -+ code, result, headers = _send_signed_request(challenge['uri'], { - "resource": "challenge", - "keyAuthorization": keyauthorization, - }) -@@ -153,17 +153,32 @@ def get_crt(account_key, csr, acme_dir, - proc = subprocess.Popen(["openssl", "req", "-in", csr, "-outform", "DER"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - csr_der, err = proc.communicate() -- code, result = _send_signed_request(CA + "/acme/new-cert", { -+ code, result, headers = _send_signed_request(CA + "/acme/new-cert", { - "resource": "new-cert", - "csr": _b64(csr_der), - }) - if code != 201: - raise ValueError("Error signing certificate: {0} {1}".format(code, result)) - -+ certchain = [result] -+ if chain: -+ def parse_link_header(line): -+ m = re.search(r"^Link:\s*<([^>]*)>(?:\s*;\s*(.*))?\r\n$", line) -+ return (m.group(1), dict([(a[0],a[1].strip('"')) -+ for a in [attr.split("=") -+ for attr in m.group(2).split("\s*;\s*")]])) -+ -+ up = [ -+ link for link, attr in [ -+ parse_link_header(l) for l in headers.getallmatchingheaders("Link") -+ ] if attr['rel'] == 'up' -+ ] -+ certchain += [urlopen(url).read() for url in up] -+ - # return signed certificate! - log.info("Certificate signed!") -- return """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format( -- "\n".join(textwrap.wrap(base64.b64encode(result).decode('utf8'), 64))) -+ return "".join(["""-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format( -+ "\n".join(textwrap.wrap(base64.b64encode(cert).decode('utf8'), 64))) for cert in certchain]) - - def main(argv): - parser = argparse.ArgumentParser( -@@ -188,11 +203,19 @@ def main(argv): - parser.add_argument("--acme-dir", required=True, help="path to the .well-known/acme-challenge/ directory") - parser.add_argument("--quiet", action="store_const", const=logging.ERROR, help="suppress output except for errors") - parser.add_argument("--ca", default=DEFAULT_CA, help="certificate authority, default is Let's Encrypt") -+ parser.add_argument("--chain", action="store_true", -+ help="fetch and append intermediate certs to output") - - args = parser.parse_args(argv) - LOGGER.setLevel(args.quiet or LOGGER.level) -- signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, log=LOGGER, CA=args.ca) -- sys.stdout.write(signed_crt) -+ try: -+ signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, -+ log=LOGGER, CA=args.ca, chain=args.chain) -+ sys.stdout.write(signed_crt) -+ except Exception as e: -+ #if not args.quiet: raise e -+ LOGGER.error(e) -+ sys.exit(1) - - if __name__ == "__main__": # pragma: no cover - main(sys.argv[1:]) diff --git a/acme-tiny-notify.service b/acme-tiny-notify.service new file mode 100644 index 0000000..bcd93b7 --- /dev/null +++ b/acme-tiny-notify.service @@ -0,0 +1,8 @@ +[Unit] +Description=Notify services of updates to acme certs + +[Service] +Type=oneshot +Nice=19 +SyslogIdentifier=acme-tiny +ExecStart=/usr/libexec/acme-tiny/notify --scan diff --git a/acme-tiny-sign.sh b/acme-tiny-sign.sh old mode 100644 new mode 100755 index edceb37..9999005 --- a/acme-tiny-sign.sh +++ b/acme-tiny-sign.sh @@ -1,11 +1,20 @@ -#!/bin/sh +#!/bin/bash if test "$(id -u)" -eq 0; then echo "Do not run as root!" exit 2 fi -DAYS="${1:-7}" +. /etc/sysconfig/acme-tiny +DAYS="${1:-$DAYS}" +test -n "$DAYS" || DAYS="7" +if [[ "$DAYS" =~ ^[0-9]+$ ]]; then + echo "Days before expiration: $DAYS" + secs=$(( $DAYS * 24 * 60 * 60 )) +else + echo "Invalid number of days: $DAYS" + exit 1 +fi cd /var/lib/acme @@ -22,7 +31,7 @@ for csr in csr/*.csr; do crt="${csr%%.csr}" tmp="certs/${crt##csr/}.tmp" crt="certs/${crt##csr/}.crt" - if test -s "$crt" && /usr/sbin/cert-check --days="$DAYS" "$crt"; then + if test -s "$crt" && openssl x509 -in "$crt" -noout -checkend "$secs"; then continue fi if test -w "$crt" || test ! -e "$crt"; then diff --git a/acme-tiny.conf b/acme-tiny.conf new file mode 100644 index 0000000..28e81fe --- /dev/null +++ b/acme-tiny.conf @@ -0,0 +1,4 @@ +# Default settings for acme-tiny wrapper script + +# Number of days before expiration to renew a certificate +DAYS=7 diff --git a/acme-tiny.cron b/acme-tiny.cron deleted file mode 100644 index 206fd39..0000000 --- a/acme-tiny.cron +++ /dev/null @@ -1,3 +0,0 @@ -# Check daily for csrs in /var/lib/acme/csr that need signing or renewing -# within 7 days. -31 2 * * * acme /usr/libexec/acme-tiny/sign 7 diff --git a/acme-tiny.service b/acme-tiny.service index a9bedfc..c00fc5d 100644 --- a/acme-tiny.service +++ b/acme-tiny.service @@ -1,5 +1,7 @@ [Unit] Description=Check for acme certs about to expire +Wants=acme-tiny-notify.service +Before=acme-tiny-notify.service [Service] Type=oneshot @@ -9,7 +11,7 @@ ProtectSystem=true User=acme Group=acme SyslogIdentifier=acme-tiny -ExecStart=/usr/libexec/acme-tiny/sign 7 +ExecStart=/usr/libexec/acme-tiny/sign [Install] Also=acme-tiny.timer diff --git a/acme-tiny.spec b/acme-tiny.spec index dd7b091..c4e4387 100644 --- a/acme-tiny.spec +++ b/acme-tiny.spec @@ -1,10 +1,4 @@ -%if 0%{?rhel} >= 5 && 0%{?rhel} < 7 -%global use_systemd 0 -%else -%global use_systemd 1 -%endif - %if 0%{?fedora} || 0%{?rhel} > 7 # Explicity require python3 on Fedora to help track which packages # no longer need python2. @@ -14,8 +8,8 @@ %endif Name: acme-tiny -Version: 4.1.0 -Release: 1%{?dist} +Version: 5.0.1 +Release: 13%{?dist} Summary: Tiny auditable script to issue, renew Let's Encrypt certificates License: MIT @@ -24,26 +18,17 @@ Source0: https://github.com/diafygi/%{name}/archive/%{version}.tar.gz#/%{name}-% Source1: acme-tiny-sign.sh Source2: cert-check.py Source3: acme.conf -Source4: lets-encrypt-x3-cross-signed.pem -Source5: acme-tiny.cron Source6: acme-tiny.timer Source7: acme-tiny.service Source8: README-fedora.md # simple script hook to kick services when cert is updated Source9: notify.sh -# Fetch and include intermediate cert(s), too. -Patch0: acme-tiny-chain.patch -# Python3 broke getallmatchingheaders() and the fix breaks python2 -Patch1: acme-tiny-chain2.patch +Source10: acme-tiny-notify.service +Source11: acme-tiny.conf Requires(pre): shadow-utils -%if %{use_systemd} -# systemd macros are not defined unless systemd is present -BuildRequires: systemd +BuildRequires: systemd-rpm-macros %{?systemd_requires} -%else -Requires: cronie -%endif Requires: %{name}-core = %{version}-%{release} BuildArch: noarch %if 0%{?fedora} @@ -63,7 +48,7 @@ This package adds a simple directory layout and timer service that runs acme_tiny on installed CSRs as the acme user for privilege separation. %package core -Summary: core python module of acme-tiny +Summary: Core python module of acme-tiny Requires: openssl %if 0%{?rhel} >= 5 && 0%{?rhel} < 7 # EL6 uses python2.6, which does not include argparse @@ -81,12 +66,11 @@ unneeded packages. cp -p %{SOURCE1} %{SOURCE2} %{SOURCE8} . sed -i.orig -e '1,1 s,^.*python$,#!/usr/bin/python,' acme_tiny.py %if %{use_python3} -#patch0 -p1 -b .chain sed -i.old -e '1,1 s/python$/python3/' *.py -#else -#patch1 -p1 -b .chain2 %endif +echo 'u acme - "Tiny Auditable ACME Client" %{_sharedstatedir}/acme' >acme.sysusers.conf + %build %install @@ -94,26 +78,25 @@ mkdir -p %{buildroot}/var/www/challenges mkdir -p %{buildroot}%{_sysconfdir}/httpd/conf.d mkdir -p %{buildroot}%{_sbindir} mkdir -p %{buildroot}%{_libexecdir}/%{name} -mkdir -p %{buildroot}%{_sharedstatedir}/acme/{private,csr,certs} +mkdir -p %{buildroot}%{_sharedstatedir}/acme/{private,csr,certs,.notify} mkdir -p %{buildroot}%{_sysconfdir}/%{name}/notify.d +mkdir -p %{buildroot}%{_sysconfdir}/sysconfig chmod 0700 %{buildroot}%{_sharedstatedir}/acme/private install -m 0755 acme-tiny-sign.sh %{buildroot}%{_libexecdir}/%{name}/sign +install -m 0755 %{SOURCE9} %{buildroot}%{_libexecdir}/%{name}/notify install -m 0755 acme_tiny.py %{buildroot}%{_sbindir}/acme_tiny ln -sf acme_tiny %{buildroot}%{_sbindir}/%{name} ln -sf %{_libexecdir}/%{name}/sign %{buildroot}%{_sbindir}/acme-tiny-sign +ln -sf %{_libexecdir}/%{name}/notify %{buildroot}%{_sysconfdir}/%{name}/notify.sh install -m 0755 cert-check.py %{buildroot}%{_sbindir}/cert-check install -m 0644 %{SOURCE3} %{buildroot}%{_sysconfdir}/httpd/conf.d -install -m 0644 %{SOURCE4} %{buildroot}%{_sharedstatedir}/acme -install -m 0755 %{SOURCE9} %{buildroot}%{_sysconfdir}/%{name} -%if %{use_systemd} mkdir -p %{buildroot}%{_unitdir} install -pm 644 %{SOURCE6} %{buildroot}%{_unitdir} install -pm 644 %{SOURCE7} %{buildroot}%{_unitdir} -%else -mkdir -p %{buildroot}%{_sysconfdir}/cron.d -install -m 0644 %{SOURCE5} %{buildroot}%{_sysconfdir}/cron.d/acme-tiny -%endif +install -pm 644 %{SOURCE10} %{buildroot}%{_unitdir} +install -m 0644 %{SOURCE11} %{buildroot}%{_sysconfdir}/sysconfig/%{name} +install -m 0644 -D acme.sysusers.conf %{buildroot}%{_sysusersdir}/acme.conf %pre getent group acme > /dev/null || groupadd -r acme @@ -122,18 +105,14 @@ getent passwd acme > /dev/null || /usr/sbin/useradd -g acme \ -r -d %{_sharedstatedir}/acme -s /sbin/nologin acme exit 0 -%if %{use_systemd} - %post -%systemd_post acme-tiny.service acme-tiny.timer +%systemd_post acme-tiny.service acme-tiny-notice.service acme-tiny.timer %postun -%systemd_postun_with_restart acme-tiny.service acme-tiny.timer +%systemd_postun_with_restart acme-tiny.service acme-tiny-notice.service acme-tiny.timer %preun -%systemd_preun acme-tiny.service acme-tiny.timer - -%endif +%systemd_preun acme-tiny.service acme-tiny-notice.service acme-tiny.timer %files %{!?_licensedir:%global license %%doc} @@ -143,15 +122,13 @@ exit 0 %attr(-,acme,acme) %{_sharedstatedir}/acme %{_libexecdir}/%{name} %config(noreplace) %{_sysconfdir}/httpd/conf.d/acme.conf -%if %{use_systemd} +%config(noreplace) %{_sysconfdir}/sysconfig/%{name} %{_unitdir}/* -%else -%config(noreplace) %{_sysconfdir}/cron.d/%{name} -%endif %{_sbindir}/acme-tiny-sign %{_sbindir}/cert-check %{_sbindir}/%{name} %{_sysconfdir}/%{name} +%{_sysusersdir}/acme.conf %files core %license LICENSE @@ -159,7 +136,80 @@ exit 0 %{_sbindir}/acme_tiny %changelog -* Fri Oct 11 2019 Tim Jackson - 5.0.1-13 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_44_Mass_Rebuild + +* Wed Jul 23 2025 Fedora Release Engineering - 5.0.1-12 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild + +* Thu Jan 16 2025 Zbigniew Jędrzejewski-Szmek - 5.0.1-11 +- Add sysusers.d config file + +* Thu Jan 16 2025 Fedora Release Engineering - 5.0.1-10 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild + +* Wed Jul 17 2024 Fedora Release Engineering - 5.0.1-9 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild + +* Mon Jan 22 2024 Fedora Release Engineering - 5.0.1-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Fri Jan 19 2024 Fedora Release Engineering - 5.0.1-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Wed Jul 19 2023 Fedora Release Engineering - 5.0.1-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Tue Mar 28 2023 Stuart D. Gathman - 5.0.1-5 +- Verified SPDX license + +* Wed Jan 18 2023 Fedora Release Engineering - 5.0.1-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild + +* Wed Jul 20 2022 Fedora Release Engineering - 5.0.1-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild + +* Wed Jan 19 2022 Fedora Release Engineering - 5.0.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild + +* Thu Oct 28 2021 Stuart D. Gathman 5.0.1-1 +- New upstream release + +* Wed Sep 8 2021 Stuart D. Gathman 4.1.1-2 +- Remove CLI override in acme-tiny.service (uses /etc/sysconfig/acme-tiny now) + +* Tue Sep 7 2021 Stuart D. Gathman 4.1.1-1 +- New upstream release +- Set days before expiration in /etc/sysconfig + +* Wed Jul 21 2021 Fedora Release Engineering - 4.1.0-8 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild + +* Thu May 27 2021 Stuart D. Gathman 4.1.0-7 +- Fix BZ#1839904 +- enhance notify after cert update, incrond no longer needed + +* Tue Mar 02 2021 Zbigniew Jędrzejewski-Szmek - 4.1.0-6 +- Rebuilt for updated systemd-rpm-macros + See https://pagure.io/fesco/issue/2583. + +* Mon Jan 25 2021 Fedora Release Engineering - 4.1.0-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Mon Jul 27 2020 Fedora Release Engineering - 4.1.0-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Thu Apr 9 2020 Stuart D. Gathman 4.1.0-3 +- Update README-fedora.md to describe notify.sh +- Apply selected changes from Marcel Metz : +- Use openssl x509 -checkend parameter to determine certificate expiration +- Remove Let's Encrypt intermediate certificate +- Remove cron job used on non systemd systems + +* Tue Jan 28 2020 Fedora Release Engineering - 4.1.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Fri Oct 11 2019 Tim Jackson - 4.1.0-1 - Update to 4.1.0 * Fri Oct 11 2019 Stuart D. Gathman 4.0.4-5 diff --git a/lets-encrypt-x3-cross-signed.pem b/lets-encrypt-x3-cross-signed.pem deleted file mode 100644 index 0002462..0000000 --- a/lets-encrypt-x3-cross-signed.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow -SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT -GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF -q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 -SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 -Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA -a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj -/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T -AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG -CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv -bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k -c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw -VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC -ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz -MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu -Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF -AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo -uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ -wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu -X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG -PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 -KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== ------END CERTIFICATE----- diff --git a/notify.sh b/notify.sh index 2d04011..f3ca76b 100755 --- a/notify.sh +++ b/notify.sh @@ -1,20 +1,58 @@ -#!/bin/sh +#!/usr/bin/sh -cert="$1" -name="${cert##*/}" -script="/etc/acme-tiny/notify.d/${name%.crt}.sh" +acmedir="/var/lib/acme" +#acmedir="test" +notify="${acmedir}/.notify" +verbose="n" +mkdir -p "$notify" -# kick apache if cert is mentioned -if grep "$cert" /etc/httpd/conf.d/*.conf >/dev/null 2>&1; then - apachectl graceful -fi +scancerts() { + if test -e "${notify}/notify"; then + find "${acmedir}/certs" -name '*.crt' -newer "${notify}/notify" -print0 + else + find "${acmedir}/certs" -name '*.crt' -print0 + fi | xargs -0 /usr/libexec/acme-tiny/notify -v + touch "${notify}/notify" +} -# kick sendmail if cert is mentioned -if grep "/etc/pki/tls/certs/$name" /etc/mail/*.cf >/dev/null 2>&1; then - cp "$cert" /etc/pki/tls/certs && systemctl restart sendmail -fi +for cert in "$@"; do + case "$cert" in + -v|--verbose) verbose="y"; continue;; + -s|--scan) scancerts; continue;; + -*) echo "Invalid option $cert"; exit 2;; + esac + name="${cert##*/}" + script="/etc/acme-tiny/notify.d/${name%.crt}.sh" -# run any dropin extension -if test -x "$script"; then - "$script" "$cert" -fi + # kick apache if cert is mentioned + if test "$cert" -nt "${notify}/httpd"; then + if grep "$cert" /etc/httpd/conf.d/*.conf >/dev/null 2>&1; then + apachectl graceful && touch "${notify}/httpd" && \ + [ "$verbose" = "y" ] && echo "Httpd reloaded" + fi + fi + + # kick sendmail if cert is mentioned + if test "$cert" -nt "${notify}/sendmail"; then + if grep "/etc/pki/tls/certs/$name" /etc/mail/*.cf >/dev/null 2>&1; then + cp "$cert" /etc/pki/tls/certs && systemctl restart sendmail \ + && touch "${notify}/sendmail" && \ + [ "$verbose" = "y" ] && echo "Sendmail reloaded" + fi + fi + + # kick dovecot if cert is mentioned + if test "$cert" -nt "${notify}/dovecot"; then + if grep "/etc/pki/dovecot/certs/$name" /etc/dovecot/conf.d/10-ssl.conf >/dev/null 2>&1; then + cp "$cert" /etc/pki/dovecot/certs && systemctl restart dovecot \ + && touch "${notify}/dovecot" && \ + [ "$verbose" = "y" ] && echo "Dovecot reloaded" + fi + fi + + # run any dropin extension + if test -x "$script"; then + [ "$verbose" = "y" ] && echo "Running $script $cert" + ACMEDIR="$acmedir" NOTIFY="$notify" VERBOSE="$verbose" "$script" "$cert" + fi +done diff --git a/sources b/sources index a5fdfcf..f4629f8 100644 --- a/sources +++ b/sources @@ -1 +1,2 @@ -SHA512 (acme-tiny-4.1.0.tar.gz) = 31d69a5031c019acbc23b3f06041eae8e261766396d4a7420fd70a71cfa16de953bea4c0c2ad0c6a6e793ed61ab5331f40145352ffce69f4f062f35dd0db7519 +SHA512 (acme-tiny-4.1.1.tar.gz) = 9e1aac03f3aa744061b8b03bb7bb6ede52ccf1a72d729775f106eb0fef786ee495dedd4f44c672e4ee2a8fc385477366bf164ab5e78d85e0a031558cde68f4b1 +SHA512 (acme-tiny-5.0.1.tar.gz) = 6e0619917b31a5795c2c7d8aa811b46231b81fc6b57227f611f7f4b9f73eb3de669676482563c33d935a4a0812498677bcbe974663a561af61abb441a880947e