diff --git a/acme-tiny-4.1.0.tar.gz b/acme-tiny-4.1.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..cbc032f1fb2b62154e5637ca84f0110b9a549aaf Binary files /dev/null and b/acme-tiny-4.1.0.tar.gz differ diff --git a/acme-tiny-sign.sh b/acme-tiny-sign.sh new file mode 100644 index 0000000000000000000000000000000000000000..edceb37883450bc855faf7ae597d80549432e68d --- /dev/null +++ b/acme-tiny-sign.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +if test "$(id -u)" -eq 0; then + echo "Do not run as root!" + exit 2 +fi + +DAYS="${1:-7}" + +cd /var/lib/acme + +if ! test -s private/account.key; then + touch private/account.key + chmod 0600 private/account.key + openssl genrsa 4096 >private/account.key +fi + +rc="0" +for csr in csr/*.csr; do + test -s "$csr" || continue + test -r "$csr" || continue + 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 + continue + fi + if test -w "$crt" || test ! -e "$crt"; then + echo acme_tiny --account-key private/account.key --csr "$csr" \ + --acme-dir /var/www/challenges/ --out "$crt" + else + echo "Can't write to $crt" + rc="1" + continue + fi + + if /usr/sbin/acme_tiny --account-key private/account.key --csr "$csr" \ + --acme-dir /var/www/challenges/ > "$tmp"; then + mv "$tmp" "$crt" || exit 1 + else + test -e "$tmp" && test ! -s "$tmp" && rm "$tmp" + fi + # append intermediate certs + #cat *.pem >>"$crt" +done +exit "$rc" diff --git a/acme-tiny.service b/acme-tiny.service new file mode 100644 index 0000000000000000000000000000000000000000..a9bedfc25b12395f8ccada3ec7a0368f7462b88e --- /dev/null +++ b/acme-tiny.service @@ -0,0 +1,15 @@ +[Unit] +Description=Check for acme certs about to expire + +[Service] +Type=oneshot +Nice=19 +ProtectHome=true +ProtectSystem=true +User=acme +Group=acme +SyslogIdentifier=acme-tiny +ExecStart=/usr/libexec/acme-tiny/sign 7 + +[Install] +Also=acme-tiny.timer diff --git a/acme-tiny.spec b/acme-tiny.spec new file mode 100644 index 0000000000000000000000000000000000000000..6cdc68d19dc1d98e51a77c4ed9cb1806b0a9fb2f --- /dev/null +++ b/acme-tiny.spec @@ -0,0 +1,106 @@ +Name: acme-tiny +Version: 4.1.0 +Release: 1 +Summary: Tiny auditable script to issue, renew Let's Encrypt certificates +License: MIT +URL: https://github.com/diafygi/acme-tiny +Source0: https://github.com/diafygi/acme-tiny/archive/refs/tags/4.1.0.tar.gz +Source1: acme-tiny-sign.sh +Source2: cert-check.py +Source3: acme.conf +Source4: acme-tiny.timer +Source5: acme-tiny.service +Source6: notify.sh + +BuildRequires: systemd +Requires(pre): shadow-utils +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +Requires: acme-tiny-core = 4.1.0-1 +BuildArch: noarch + +%description +This is a tiny, auditable script that you can throw on your server to issue and +renew Let's Encrypt certificates. Since it has to be run on your server and +have access to your private Let's Encrypt account key, I tried to make it as +tiny as possible (currently less than 200 lines). The only prerequisites are +python and openssl. + +%package core +Summary: core python module of acme-tiny +Requires: openssl +BuildArch: noarch + +%description core +Includes only the core acme_tiny.py script and its dependencies. +Alternate frameworks that use acme_tiny.py can install this to avoid pulling in +unneeded packages. + +%prep +%setup -q -n acme-tiny-4.1.0 +cp -p %{SOURCE1} %{SOURCE2} . +sed -i.orig -e '1,1 s,^.*python$,#!/usr/bin/python,' acme_tiny.py +sed -i.old -e '1,1 s/python$/python3/' *.py + +%build + +%install +mkdir -p %{buildroot}/var/www/challenges +mkdir -p %{buildroot}%{_sysconfdir}/httpd/conf.d +mkdir -p %{buildroot}%{_sbindir} +mkdir -p %{buildroot}%{_libexecdir}/acme-tiny +mkdir -p %{buildroot}%{_sharedstatedir}/acme/{private,csr,certs} +mkdir -p %{buildroot}%{_sysconfdir}/acme-tiny/notify.d +chmod 0700 %{buildroot}%{_sharedstatedir}/acme/private + +install -m 0755 acme-tiny-sign.sh %{buildroot}%{_libexecdir}/acme-tiny/sign +install -m 0755 acme_tiny.py %{buildroot}%{_sbindir}/acme_tiny +ln -sf acme_tiny %{buildroot}%{_sbindir}/acme-tiny +ln -sf %{_libexecdir}/acme-tiny/sign %{buildroot}%{_sbindir}/acme-tiny-sign +install -m 0755 cert-check.py %{buildroot}%{_sbindir}/cert-check +install -m 0644 %{SOURCE3} %{buildroot}%{_sysconfdir}/httpd/conf.d +install -m 0755 %{SOURCE6} %{buildroot}%{_sysconfdir}/acme-tiny +mkdir -p %{buildroot}%{_unitdir} +install -pm 644 %{SOURCE4} %{buildroot}%{_unitdir} +install -pm 644 %{SOURCE5} %{buildroot}%{_unitdir} + +%pre +getent group acme > /dev/null || groupadd -r acme +getent passwd acme > /dev/null || /usr/sbin/useradd -g acme \ + -c "Tiny Auditable ACME Client" \ + -r -d %{_sharedstatedir}/acme -s /sbin/nologin acme +exit 0 + + +%post +%systemd_post acme-tiny.service acme-tiny.timer + +%postun +%systemd_postun_with_restart acme-tiny.service acme-tiny.timer + +%preun +%systemd_preun acme-tiny.service acme-tiny.timer + + +%files +%{!?_licensedir:%global license %%doc} +%license LICENSE +%attr(0755,acme,acme) /var/www/challenges +%attr(-,acme,acme) %{_sharedstatedir}/acme +%{_libexecdir}/acme-tiny +%config(noreplace) %{_sysconfdir}/httpd/conf.d/acme.conf +%{_unitdir}/* +%{_sbindir}/acme-tiny-sign +%{_sbindir}/cert-check +%{_sbindir}/acme-tiny +%{_sysconfdir}/acme-tiny + +%files core +%license LICENSE +%doc README.md +%{_sbindir}/acme_tiny + +%changelog +* Tue Jul 13 2021 wangziliang - 4.1.0-1 +- Package init. diff --git a/acme-tiny.timer b/acme-tiny.timer new file mode 100644 index 0000000000000000000000000000000000000000..687c8204915c36a48fbb8b1d65bc737e6f591623 --- /dev/null +++ b/acme-tiny.timer @@ -0,0 +1,13 @@ +[Unit] +Description=check for acme certs about to expire and renew them +ConditionKernelCommandLine=!rd.live.image +After=network-online.target +After=httpd.service nginx.service + +[Timer] +OnBootSec=20min +OnUnitInactiveSec=24h +Unit=acme-tiny.service + +[Install] +WantedBy=timers.target diff --git a/acme.conf b/acme.conf new file mode 100644 index 0000000000000000000000000000000000000000..3768c5cd3e02b36a3e27e72a9f2c57f4c9fbb407 --- /dev/null +++ b/acme.conf @@ -0,0 +1,17 @@ +Alias /.well-known/acme-challenge/ "/var/www/challenges/" + +# Note, blocking access to in a will override +# these global permissions. You will need to modify those domains +# to allow access to /.well-known/, or just copy the from below. +# See: http://httpd.apache.org/docs/2.2/sections.html + + + Options -Indexes + Order allow,deny + Allow from all + + + Options -Indexes + Order allow,deny + Allow from all + diff --git a/cert-check.py b/cert-check.py new file mode 100644 index 0000000000000000000000000000000000000000..89f545ce49b62ffcbb366388991bc8ce74dd6065 --- /dev/null +++ b/cert-check.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +from __future__ import print_function +from sys import stderr + +import subprocess, time, calendar, os, getopt + +def usage(): + print("""Usage: cert-check [options] files ... + -h,--help this message + -q,--quiet do not print cert files needing (re)newing + -d n,--days=n days before expiration to renew (default 7) +Succeeds only if all certs exist and are more than from expiration.""", + file=stderr) + return 2 + +def main(argv): + days = 7 # days ahead to + quiet = False + + try: + opts,args = getopt.getopt(argv,'hqd:',['days=','quiet','help']) + except getopt.GetoptError as err: + # print help information and exit: + print(err,file=stderr) # prints something like "option -a not recognized" + return usage() + + for opt,val in opts: + if opt in ('-h','--help'): + return usage() + if opt in ('-q','--quiet'): + quiet = True + if opt in ('-d','--days'): + try: + days = int(val) + except: + return usage() + + now = time.time() + soon = now + days * 24 * 60 * 60 + rc = 0 + + for fn in args: + try: + size = os.path.getsize(fn) + except: + size = 0 + if size == 0: + if not quiet: print(fn) + rc += 1 + continue + proc = subprocess.Popen( + ["openssl", "x509", "-in", fn, "-noout", "-enddate"], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + if proc.returncode != 0: + raise IOError("{1}: OpenSSL Error: {0}".format(err,fn)) + t = time.strptime(out.decode(),'notAfter=%b %d %H:%M:%S %Y GMT\n') + t = calendar.timegm(t) + if soon > t: + if not quiet: print(fn) + rc += 1 + return rc > 0 + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv[1:])) diff --git a/notify.sh b/notify.sh new file mode 100755 index 0000000000000000000000000000000000000000..2d0401150908fb0595e86ed9b875305c3b0bbe27 --- /dev/null +++ b/notify.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +cert="$1" +name="${cert##*/}" +script="/etc/acme-tiny/notify.d/${name%.crt}.sh" + +# kick apache if cert is mentioned +if grep "$cert" /etc/httpd/conf.d/*.conf >/dev/null 2>&1; then + apachectl graceful +fi + +# 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 + +# run any dropin extension +if test -x "$script"; then + "$script" "$cert" +fi