Debian GNU/Linux · Accounts & authentication

Apply the password aging policy to existing accounts

When it fails: Existing accounts keep passwords that never expire. Changing login.defs only affects new accounts; existing ones keep their old aging settings.

CB-DEB-AUTH-015lowactiveautomatedautomated fix, lockout riskprofile core

As released in v0.1.0 beta · pre-release. Current state.

Description

Each account has its own aging fields in /etc/shadow (minimum and maximum age, warning period, inactivity period, date of last change), copied from the defaults when it was created. This control applies the policy of CB-DEB-AUTH-014 to every existing account that has a password, and checks that no last change date lies in the future.

Rationale

Without this step, the aging policy only protects accounts created after it was set. A last change date in the future would postpone expiry indefinitely.

Impact

An account whose password is older than {{ max_days }} days is asked to change it at its next password login, and is disabled {{ inactive_days }} days later. SSH key logins of such an account are also refused until the password is changed: check service and automation accounts before applying.

Parameters

ParameterCoreServerServer hardenedDesktopDesktop hardenedBounds
max_daysinteger, days. Maximum password age in days.3653653653653651 to 99999
min_daysinteger, days. Minimum days between password changes.001010 to 7
inactive_daysinteger, days. Days after password expiry before the account is disabled.45454545450 to 365

The value used is your override (tailoring file), else the profile's, else the one of the profile it extends, else the default. Bounds limit what an override may set.

Audit

For every account with a password hash in /etc/shadow (field 2 starting with $), check with chage -l <user>: maximum <= {{ max_days }}, minimum >= {{ min_days }}, warning >= 7, inactive between 0 and {{ inactive_days }}, and a last change date that is not in the future.

Automated: every check must pass. Scanners report each check by its id.

accounts

Aging fields of every account with a password.

script: |
  today=$(( $(date +%s) / 86400 ))
  bad=$(awk -F: -v max="$CB_MAX_DAYS" -v min="$CB_MIN_DAYS" -v inact="$CB_INACTIVE_DAYS" -v today="$today" '
    $2 ~ /^\$/ {
      r = ""
      if ($5 == "" || $5 > max) r = r " max=" $5
      if ($4 == "" || $4 < min) r = r " min=" $4
      if ($6 == "" || $6 < 7) r = r " warn=" $6
      if ($7 == "" || $7 < 0 || $7 > inact) r = r " inactive=" $7
      if ($3 != "" && $3 > today) r = r " last_change_in_future"
      if (r != "") print $1 ":" r
    }' /etc/shadow)
  [ -z "$bad" ] && { echo "all accounts follow the policy"; exit 0; }
  printf '%s\n' "$bad"
  exit 1

Remediation

For each account with a password: chage --maxdays {{ max_days }} --mindays {{ min_days }} --warndays 7 --inactive {{ inactive_days }} <user>. Fix last change dates in the future with chage --lastday <YYYY-MM-DD> <user>.

Restartnone
Disruptionlow
Lockout riskyes: can cut off administrative access. Off by default in generated scripts and playbooks, opt in through a tailoring file.

Rollback

Restore /etc/shadow from the backup, or reset each account with chage.

chage

cb_backup /etc/shadow
awk -F: '$2 ~ /^\$/ {print $1}' /etc/shadow | while read -r u; do
  chage --maxdays "$CB_MAX_DAYS" --mindays "$CB_MIN_DAYS" --warndays 7 --inactive "$CB_INACTIVE_DAYS" "$u"
done

Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.

References

Mappings

FrameworkIDRelation
NIST SP 800-53 Rev. 55.1.1AC-2(3)partial

How this control relates to the other framework's: equivalent, stricter, looser, partial or related.

Threats mitigated

  • requires CB-DEB-AUTH-014 Set password aging and hashing defaults for new accounts

People and lifecycle

Authors
Verified2026-10-18 by Sn0wAlice on 12, Proxmox VM, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. Generated check, fix and check again for profiles core and server-hardened; state kept after reboot.
Verified2026-10-18 by Sn0wAlice on 13, Proxmox VM, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. Generated check, fix and check again for profiles core and server-hardened; state kept after reboot.
Introduced inv0.1.0
Created, updated2026-10-18, 2026-10-18
Translationsfr