securing oracle linux 9 article 20 configuring password parameters shadow password suite

In the previous part, Configuring Authentication Modules Arguments (PAM Arguments) was completed.

1. CIS Benchmark Requirements (Section 5.4.1)

  1. 5.4.1 Configure shadow password suite parameters
    • 5.4.1.1 Ensure password expiration is configured (Automated)
    • 5.4.1.2 Ensure minimum password days is configured (Manual)
    • 5.4.1.3 Ensure password expiration warning days is configured (Automated)
    • 5.4.1.4 Ensure strong password hashing algorithm is configured (Automated)
    • 5.4.1.5 Ensure inactive password lock is configured (Automated)
    • 5.4.1.6 Ensure all users last password change date is in the past (Automated)

2. Concept & Rationale

  • PASS_MAX_DAYS: The maximum number of days a password remains valid (usually 365 days).
  • PASS_MIN_DAYS: The minimum number of days that must pass after a password change before the user can change it again (usually 1 day, to prevent circumvention of the password history).
  • PASS_WARN_AGE: The number of days before expiration that the user is warned (usually 7 days).
  • ENCRYPT_METHOD: Use of strong algorithms like YESCRYPT or SHA512 for password hashing.
  • INACTIVE: Deactivation of the user account after a specified number of days (e.g., 30 days) from the password expiration date.

3. Oracle Database Compatibility (RAC, ASM, Grid)

Conflict Level: Very High (Critical)

Oracle Requirements and Explanation:

  • Service User Password Expiration Crisis: Setting a password expiration policy for service users like oracle, grid, and opc can cause severe disruptions. If the passwords for these users expire, background processes (Cron jobs, SSH key-based communication in RAC environments, and monitoring agents) will face authentication failures and stop, leading to a service outage.
  • Solution: The values applied in /etc/login.defs only apply to new users. For existing system and Oracle service users (which are created beforehand or later), the expiration policy must be explicitly disabled using commands like chage -M 99999 oracle to ensure cluster and database stability.

4. How to Audit (Audit Script)

GitHub link for this script: modules/audit_20_Shadow_Password_Suite.sh

If you are not familiar with Bash scripting, you can refer to the training published for database administrators on the site: Bash for Oracle DBAs.

#!/bin/bash
# Script: audit20.sh
# Purpose: Audit Script for Password Expiration & Aging (CIS Section 20)

echo "=========================================================================="
echo " CIS Requirement: Password Aging Policies (/etc/login.defs & Shadow)"
echo " - Ensure password expiration is 365 days or less (PASS_MAX_DAYS)."
echo " - Ensure minimum days between changes is 1 or more (PASS_MIN_DAYS)."
echo " - Ensure inactive password lock is 30 days or less (INACTIVE)."
echo " - Ensure all passwords have a valid change date (not in the future)."
echo " Oracle Context:"
echo " - WARNING: Enforcing expiration (PASS_MAX_DAYS) on database service"
echo "   accounts ('oracle', 'grid') will cause critical cluster and database"
echo "   outages. Service accounts must be explicitly excluded from aging."
echo "=========================================================================="

if [ "$EUID" -ne 0 ]; then
  echo -e "\e[31m[-] Please run as root\e[0m"
  exit 1
fi

FAIL_COUNT=0

echo -e "\n[*] Checking /etc/login.defs parameters..."
check_login_defs() {
    local param=$1
    local expected=$2
    local current=$(grep -E "^\s*${param}\b" /etc/login.defs | awk '{print $2}')
    
    if [ "$current" == "$expected" ]; then
        echo -e "  \e[32m[PASS]\e[0m $param is $current"
    else
        echo -e "  \e[31m[FAIL]\e[0m $param is $current (Expected: $expected)"
        FAIL_COUNT=$((FAIL_COUNT + 1))
    fi
}

check_login_defs "PASS_MAX_DAYS" "365"
check_login_defs "PASS_MIN_DAYS" "1"
check_login_defs "PASS_WARN_AGE" "7"
check_login_defs "ENCRYPT_METHOD" "YESCRYPT"

echo -e "\n[*] Checking Default Inactive Lock..."
inactive_val=$(useradd -D | grep INACTIVE | cut -d= -f2)
if [ "$inactive_val" == "30" ]; then
    echo -e "  \e[32m[PASS]\e[0m INACTIVE is set to $inactive_val"
else
    echo -e "  \e[31m[FAIL]\e[0m INACTIVE is set to $inactive_val (Expected: 30)"
    FAIL_COUNT=$((FAIL_COUNT + 1))
fi

echo -e "\n[*] Checking for passwords changed in the future (/etc/shadow)..."
# Fix for the broken awk command in the original script
CURRENT_EPOCH=$(date +%s)
CURRENT_DAYS=$((CURRENT_EPOCH / 86400))
FUTURE_USERS=$(awk -v now="$CURRENT_DAYS" -F: '($2 != "!" && $2 != "*" && $2 != "" && $3 > now) {print $1}' /etc/shadow)

if [ -z "$FUTURE_USERS" ]; then
    echo -e "  \e[32m[PASS]\e[0m No accounts have a password change date in the future."
else
    echo -e "  \e[31m[FAIL]\e[0m Accounts with future password change dates: $(echo $FUTURE_USERS | tr '\n' ' ')"
    FAIL_COUNT=$((FAIL_COUNT + 1))
fi

if [ "$FAIL_COUNT" -eq 0 ]; then
    echo -e "\n\e[32m[+] AUDIT RESULT: PASS\e[0m"
else
    echo -e "\n\e[31m[-] AUDIT RESULT: FAIL ($FAIL_COUNT issues found)\e[0m"
fi

5. How to Remediate with Bash (Remediation Script)

GitHub link for this script: modules/remediate_20_Shadow_Password_Suite.sh

#!/bin/bash

# 1. Update /etc/login.defs
sed -i -E 's/^\s*PASS_MAX_DAYS.*/PASS_MAX_DAYS   365/' /etc/login.defs
sed -i -E 's/^\s*PASS_MIN_DAYS.*/PASS_MIN_DAYS   1/' /etc/login.defs
sed -i -E 's/^\s*PASS_WARN_AGE.*/PASS_WARN_AGE   7/' /etc/login.defs
sed -i -E 's/^\s*ENCRYPT_METHOD.*/ENCRYPT_METHOD YESCRYPT/' /etc/login.defs

# 2. Update inactive password lock (useradd default)
useradd -D -f 30

# 3. Apply changes to existing users (excluding oracle, grid, root, opc)
for user in $(awk -F: '($3 == "" || $3 > 0) && $1 != "root" && $1 != "oracle" && $1 != "grid" && $1 != "opc" {print $1}' /etc/shadow); do
    chage --maxdays 365 --mindays 1 --warndays 7 --inactive 30 "$user"
done

# Fix any users with future password change dates to today
CURRENT_DAY=$(($(date +%s) / 86400))
for user in $(awk -F: -v today="$CURRENT_DAY" '$3 > today {print $1}' /etc/shadow); do
    chage --lastday "$CURRENT_DAY" "$user"
done

echo "Configuration applied successfully. Note: 'oracle' and 'grid' users were explicitly excluded from password aging."

In the next part, we will cover System User Accounts, the Root User, and the Default Environment.