diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 2c8db7efde8a..b984a0cc8378 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -225,6 +225,15 @@ See . - Wine has been updated to the 11.0 branch. Please check the [upstream announcement](https://gitlab.winehq.org/wine/wine/-/releases/wine-11.0) for more details. +- `security.acme` now defaults to a dynamic renewal duration, if + [security.acme.defaults.validMinDays](#opt-security.acme.defaults.validMinDays) + remains unset. This accomodates certificates with different ACME profile: + - For certificates with a total validity at or above 10 days renewal will happen + after two thirds of the lifetime has passed (e.g. a certificate valid for 90 + days renews once the validity falls below 30 days) + - For shortlived certificates with a total validty below 10 days renewal + will happen after half of the total lifetime has passed + - Cinnamon has been updated to 6.6, please check the [upstream announcement](https://www.linuxmint.com/rel_zena_whatsnew.php) for more details. - Budgie has been updated to 10.10, please check the [upstream announcement](https://buddiesofbudgie.org/blog/budgie-10-10-released) for more details. diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix index 4ba53d2afb4b..21e92469a38e 100644 --- a/nixos/modules/security/acme/default.nix +++ b/nixos/modules/security/acme/default.nix @@ -523,20 +523,42 @@ let [[ -e $pem ]] - expiration_line="$( - set -euxo pipefail - openssl x509 -noout -enddate <"$pem" \ - | grep notAfter \ - | sed -e 's/^notAfter=//' - )" - [[ -n "$expiration_line" ]] + # Read certificate valid duration + not_before=$(openssl x509 -in "$pem" -noout -startdate | cut -d= -f2) + not_after=$(openssl x509 -in "$pem" -noout -enddate | cut -d= -f2) - expiration_date="$(date -d "$expiration_line" +%s)" - now="$(date +%s)" - expiration_s=$((expiration_date - now)) - expiration_days=$((expiration_s / (3600 * 24))) # rounds down + # Convert timestamp to seconds since epoch + not_before_epoch=$(date -d "$not_before" +%s) + not_after_epoch=$(date -d "$not_after" +%s) + now_epoch=$(date +%s) - [[ $expiration_days -gt ${toString data.validMinDays} ]] + # Determine total and remaining duration + total_duration=$((not_after_epoch - not_before_epoch)) + remaining_duration=$((not_after_epoch - now_epoch)) + + ${ + if (data.validMinDays != null) then + '' + # Convert to days and round down + total_days=$((total_duration / 86400)) + remaining_days=$((remaining_duration / 86400)) + + [[ $remaining_days -gt ${toString data.validMinDays} ]] + '' + else + '' + # Recreate --dynamic logic from lego + if (( total_duration < 864000 )); then + # 1/2 for shortlived certificates with total lifetime < 10 days + threshold=$(( total_duration / 2)) + else + # 1/3 for longer validity durations with total lifetime >= 10 days + threshold=$(( total_duration / 3)) + fi + + [[ $remaining_duration -gt $threshold ]] + '' + } } echo '${domainHash}' > domainhash.txt @@ -548,9 +570,11 @@ let # Even if a cert is not expired, it may be revoked by the CA. # Try to renew, and silently fail if the cert is not expired. # Avoids #85794 and resolves #129838 - if ! lego ${renewOpts} --days ${toString data.validMinDays}; then + if ! lego ${renewOpts} ${ + if data.validMinDays != null then "--days ${toString data.validMinDays}" else "--dynamic" + }; then if is_expiration_skippable out/full.pem; then - echo 1>&2 "nixos-acme: Ignoring failed renewal because expiration isn't within the coming ${toString data.validMinDays} days" + echo 1>&2 "nixos-acme: Ignoring failed renewal because expiration isn't due yet" else # High number to avoid Systemd reserved codes. exit 11 @@ -626,9 +650,15 @@ let options = { validMinDays = lib.mkOption { - type = lib.types.int; - inherit (defaultAndText "validMinDays" 30) default defaultText; - description = "Minimum remaining validity before renewal in days."; + type = lib.types.nullOr lib.types.int; + default = null; + description = '' + Minimum remaining validity before renewal in days. + + If unset, the renewal time is calculated dynamically: + - for regular certificates, renewal occurs when less than one-third of the lifetime remains + - for short-lived certificates, renewal occurs when less than half of the lifetime remains + ''; }; renewInterval = lib.mkOption {