← Rules Catalog
mediumaccess-controlverified rollback-safe

Set permissions on SSH private host keys

ssh-private-key-permissions · RHEL ≥ 8 · 1 impl

Description

SSH private host key files must have restrictive permissions to prevent unauthorized read access. The private keys under /etc/ssh/ are used to prove the server's identity to connecting clients and must be protected from disclosure.

Rationale

If an attacker can read a server's SSH private host key, they can impersonate the server in man-in-the-middle attacks, intercepting all traffic between clients and the legitimate server. Restricting file permissions to owner root with group ssh_keys ensures only the SSH daemon's privilege separation process can access the keys.

Check → Remediate

Checkcommand
# STIG (rhel8/9) requires mode 0640-or-less-permissive; CIS accepts either
# root:ssh_keys with mode <=0640 or root:root with mode <=0600. Enforce the
# coupled ownership/mode logic that satisfies both (no other access; owner
# root with no execute; group read only permitted for the ssh_keys group).
fail=0
for k in /etc/ssh/ssh_host_*_key; do
  [ -e "$k" ] || continue
  owner=$(stat -c '%U' "$k" 2>/dev/null)
  group=$(stat -c '%G' "$k" 2>/dev/null)
  perm=$(stat -c '%a' "$k" 2>/dev/null)
  # Take the last three octal digits (owner/group/other), ignoring any
  # setuid/setgid/sticky high bit. Prepend zeros then trim — never run the
  # value through printf %d, which would reinterpret e.g. "040" as octal.
  m=$(printf '00%s' "$perm" | sed 's/.*\(...\)$/\1/')
  o=$(printf '%s' "$m" | cut -c1); g=$(printf '%s' "$m" | cut -c2); t=$(printf '%s' "$m" | cut -c3)
  if [ "$owner" != "root" ]; then echo "FAIL: $k owner=$owner (must be root)"; fail=1; continue; fi
  if [ "$t" != "0" ]; then echo "FAIL: $k is accessible by others (mode $perm)"; fail=1; continue; fi
  case "$o" in 0|4|6) ;; *) echo "FAIL: $k owner has execute (mode $perm)"; fail=1; continue ;; esac
  if [ "$group" = "ssh_keys" ]; then
    case "$g" in 0|4) ;; *) echo "FAIL: $k group ssh_keys but mode $perm > 0640"; fail=1 ;; esac
  elif [ "$group" = "root" ]; then
    case "$g" in 0) ;; *) echo "FAIL: $k group root but mode $perm > 0600"; fail=1 ;; esac
  else
    echo "FAIL: $k group=$group (must be root or ssh_keys)"; fail=1
  fi
done
if [ "$fail" -eq 0 ]; then
  echo "OK: all SSH private host keys have compliant ownership and mode"
  exit 0
fi
exit 1
expected_exit:
0
Remediatefile_permissions
path:
/etc/ssh/ssh_host_*_key
owner:
root
group:
ssh_keys
mode:
0640
glob:
ssh_host_*_key

Framework references

CIS

rhel10 5.1.2rhel8 5.1.4rhel9 5.1.2

STIG

V-258000 / RHEL-09-255120V-230287 / RHEL-08-010490

NIST 800-53

AC-6SC-28

Live verification

rhel10:checkrhel8:checkrhel9:check
#ssh#file-permissions#host-keys