diff --git a/usermngr/Config.in b/usermngr/Config.in new file mode 100644 index 000000000..4fada3b54 --- /dev/null +++ b/usermngr/Config.in @@ -0,0 +1,8 @@ +if PACKAGE_usermngr + +config USERMNGR_SECURITY_HARDENING + bool "Security hardening mechanisms" + default y + help + Enable this option to use PAM based faillock, passwdqc, faildelay for security hardening. +endif diff --git a/usermngr/Makefile b/usermngr/Makefile index b4629d721..86810820d 100644 --- a/usermngr/Makefile +++ b/usermngr/Makefile @@ -32,8 +32,9 @@ define Package/usermngr DEPENDS+=+libbbfdm-api +libbbfdm-ubus +bbfdmd DEPENDS+=+@BUSYBOX_CONFIG_ADDUSER +@BUSYBOX_CONFIG_DELUSER +@BUSYBOX_CONFIG_ADDGROUP +@BUSYBOX_CONFIG_DELGROUP +shadow-usermod DEPENDS+=+@SHADOW_UTILS_USE_PAM - DEPENDS+=+linux-pam - DEPENDS+=+passwdqc + DEPENDS+=+@USERMNGR_SECURITY_HARDENING:BUSYBOX_CONFIG_PAM + DEPENDS+=+USERMNGR_SECURITY_HARDENING:linux-pam + DEPENDS+=+USERMNGR_SECURITY_HARDENING:passwdqc TITLE:=Package to add Device.Users. datamodel support endef @@ -41,6 +42,10 @@ define Package/usermngr/description Package to add Device.Users. datamodel support endef +define Package/$(PKG_NAME)/config + source "$(SOURCE)/Config.in" +endef + ifeq ($(LOCAL_DEV),1) define Build/Prepare $(CP) -rf ~/git/usermngr/* $(PKG_BUILD_DIR)/ @@ -55,6 +60,10 @@ define Package/usermngr/install $(INSTALL_DIR) $(1)/usr/sbin $(INSTALL_BIN) ./files/etc/uci-defaults/91-sync-shells $(1)/etc/uci-defaults/ $(INSTALL_BIN) ./files/etc/uci-defaults/91-sync-roles $(1)/etc/uci-defaults/ +ifeq ($(CONFIG_USERMNGR_SECURITY_HARDENING),y) + $(INSTALL_BIN) ./files/etc/uci-defaults/91-security-hardening $(1)/etc/uci-defaults/ + $(INSTALL_BIN) ./files/etc/uci-defaults/91-set-ssh-pam $(1)/etc/uci-defaults/ +endif $(INSTALL_BIN) ./files/etc/init.d/users $(1)/etc/init.d/users $(INSTALL_BIN) ./files/etc/config/users $(1)/etc/config/users $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/usermngr $(1)/usr/sbin/usermngr diff --git a/usermngr/files/etc/init.d/users b/usermngr/files/etc/init.d/users index 793f4c052..6dd23c5ac 100755 --- a/usermngr/files/etc/init.d/users +++ b/usermngr/files/etc/init.d/users @@ -6,11 +6,140 @@ USE_PROCD=1 PROG=/usr/sbin/usermngr +# List of required .so files +REQUIRED_MODULES=" +/usr/lib/security/pam_faildelay.so +/usr/lib/security/pam_faillock.so +/usr/lib/security/pam_unix.so +/usr/lib/security/pam_deny.so +/usr/lib/security/pam_permit.so +/usr/lib/security/pam_passwdqc.so +" + +check_required_modules() { + for mod in $REQUIRED_MODULES; do + if [ ! -f "$mod" ]; then + logger -p err -t usermngr "ERROR: Cannot setup security policy, missing PAM module: $mod" + return 1 + fi + done + + return 0 +} + +write_line() { + local filepath="$1" + local line="$2" + + echo "$line" >> "$filepath" +} + +compare_and_replace() { + local src dst + src="$1" + dst="$2" + + if [ ! -f "$dst" ] || ! cmp -s "$src" "$dst"; then + cp "$src" "$dst" + logger -t pam_policy_setup "Updated $dst" + fi +} + +update_auth() { + # Write /etc/pam.d/common-auth + local tmp_file pam_file + tmp_file="/tmp/common-auth" + pam_file="/etc/pam.d/common-auth" + + rm -f "$tmp_file" + touch "$tmp_file" + + write_line "$tmp_file" "auth optional pam_faildelay.so delay=$faildelay_usec" + write_line "$tmp_file" "auth required pam_faillock.so preauth deny=$faillock_attempts even_deny_root unlock_time=$faillock_lockout_time" + write_line "$tmp_file" "auth sufficient pam_unix.so nullok_secure" + write_line "$tmp_file" "auth [default=die] pam_faillock.so authfail audit deny=$faillock_attempts even_deny_root unlock_time=$faillock_lockout_time" + write_line "$tmp_file" "" + write_line "$tmp_file" "auth requisite pam_deny.so" + write_line "$tmp_file" "auth required pam_permit.so" + + compare_and_replace "$tmp_file" "$pam_file" +} + +update_password() { + # Write /etc/pam.d/common-password + local tmp_file pam_file + tmp_file="/tmp/common-password" + pam_file="/etc/pam.d/common-password" + + rm -f "$tmp_file" + touch "$tmp_file" + + # for some reason setting to 8 makes passwdqc accept minimum 12 letter password with this configuration + # if we set it to 12 then we need atleast 16 characters and so on + # passphrase = 0 means no space separated words + # rest can be figured out from passwdqc man page + write_line "$tmp_file" "password requisite pam_passwdqc.so min=disabled,disabled,disabled,disabled,8 max=20 passphrase=0 retry=3 enforce=everyone" + write_line "$tmp_file" "password [success=1 default=ignore] pam_unix.so obscure sha512" + write_line "$tmp_file" "" + write_line "$tmp_file" "password requisite pam_deny.so" + write_line "$tmp_file" "password required pam_permit.so" + + compare_and_replace "$tmp_file" "$pam_file" +} + +update_account() { + # Write /etc/pam.d/common-account + local tmp_file pam_file + tmp_file="/tmp/common-account" + pam_file="/etc/pam.d/common-account" + + rm -f "$tmp_file" + touch "$tmp_file" + + write_line "$tmp_file" "account required pam_faillock.so" + write_line "$tmp_file" "account [success=1 new_authtok_reqd=done default=ignore] pam_unix.so" + write_line "$tmp_file" "" + write_line "$tmp_file" "account requisite pam_deny.so" + write_line "$tmp_file" "account required pam_permit.so" + + compare_and_replace "$tmp_file" "$pam_file" +} + +handle_security_policy() { + local enable faildelay faillock_lockout_time faillock_attempts faildelay_usec + + # Read UCI values + enable="$(uci -q get users.security_policy.enable)" + faildelay="$(uci -q get users.security_policy.fail_delay)" + faillock_lockout_time="$(uci -q get users.security_policy.faillock_lockout_time)" + faillock_attempts="$(uci -q get users.security_policy.faillock_attempts)" + + # if it is not enabled we do not touch the pam files + [ "$enable" = "1" ] || return + + # if any .so files are missing, then we cannot setup security + if ! check_required_modules; then + return + fi + + [ -n "$faildelay" ] || faildelay=3 + [ -n "$faillock_attempts" ] || faillock_attempts=6 + [ -n "$faillock_lockout_time" ] || faillock_lockout_time=300 + # Convert seconds to microseconds for pam_faildelay + faildelay_usec=$((faildelay * 1000000)) + + update_auth + update_account + update_password +} + start_service() { local loglevel loglevel="$(uci -q get users.users.loglevel)" + handle_security_policy + procd_open_instance usermngr procd_set_param command $PROG diff --git a/usermngr/files/etc/uci-defaults/91-security-hardening b/usermngr/files/etc/uci-defaults/91-security-hardening new file mode 100644 index 000000000..cdf209b69 --- /dev/null +++ b/usermngr/files/etc/uci-defaults/91-security-hardening @@ -0,0 +1,11 @@ +#!/bin/sh + +if ! uci -q get users.security_policy; then + uci -q set users.security_policy='security_policy' + uci -q set users.security_policy.enable='1' + uci -q set users.security_policy.fail_delay='3' + uci -q set users.security_policy.faillock_attempts='6' + uci -q set users.security_policy.faillock_lockout_time='300' +fi + +exit 0 diff --git a/usermngr/files/etc/uci-defaults/91-set-ssh-pam b/usermngr/files/etc/uci-defaults/91-set-ssh-pam new file mode 100644 index 000000000..6755041f6 --- /dev/null +++ b/usermngr/files/etc/uci-defaults/91-set-ssh-pam @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ -f /etc/config/sshd ]; then + uci -q set sshd.@sshd[0].UsePAM=1 +fi + +exit 0