Merge "Add ability to set user password in uamlite"

This commit is contained in:
Craig Anderson 2018-05-10 16:34:44 +00:00 committed by Gerrit Code Review
commit fcd97ef51c
6 changed files with 170 additions and 36 deletions

View File

@ -152,9 +152,9 @@ WantedBy=multi-user.target"
{{- range $iface, $unused := .Values.conf.ethtool }} {{- range $iface, $unused := .Values.conf.ethtool }}
{{- range $ethtool_key, $ethtool_val := . }} {{- range $ethtool_key, $ethtool_val := . }}
device={{ $iface | quote }} \ device={{ $iface | squote }} \
user_key={{ $ethtool_key | quote }} \ user_key={{ $ethtool_key | squote }} \
user_val={{ $ethtool_val | quote }} \ user_val={{ $ethtool_val | squote }} \
add_ethtool_param add_ethtool_param
{{- end }} {{- end }}
{{- end }} {{- end }}

View File

@ -107,7 +107,7 @@ WantedBy=local-fs.target"
{{- range .Values.conf.mounts }} {{- range .Values.conf.mounts }}
{{- range $key, $value := . }} {{- range $key, $value := . }}
{{ $key }}={{ $value | quote }} \ {{ $key }}={{ $value | squote }} \
{{- end }} {{- end }}
add_mounts_param add_mounts_param
{{- end }} {{- end }}

View File

@ -90,7 +90,7 @@ add_sysctl_param(){
} }
{{- range $key, $value := .Values.conf.sysctl }} {{- range $key, $value := .Values.conf.sysctl }}
add_sysctl_param {{ $key | quote }} {{ $value | quote }} add_sysctl_param {{ $key | squote }} {{ $value | squote }}
{{- end }} {{- end }}
# Revert any previously applied sysctl settings which are now absent # Revert any previously applied sysctl settings which are now absent

View File

@ -27,6 +27,7 @@ builtin_acct='ubuntu'
add_user(){ add_user(){
die_if_null "${user_name}" ", 'user_name' env var not initialized" die_if_null "${user_name}" ", 'user_name' env var not initialized"
: ${user_sudo:=false} : ${user_sudo:=false}
: ${user_crypt_passwd:=*}
# Create user if user does not already exist # Create user if user does not already exist
getent passwd ${user_name} && \ getent passwd ${user_name} && \
@ -41,10 +42,31 @@ add_user(){
log.INFO "User '${user_name}' has been unexpired" log.INFO "User '${user_name}' has been unexpired"
fi fi
# Exclude case where user should not have a password set
if [ "${user_crypt_passwd}" != '*' ]; then
local user_has_passwd=true
fi
# Set user password if current password does not match desired password
local crypt_passwd="$(getent shadow ${user_name} | cut -d':' -f2)"
if [ "${crypt_passwd}" != "${user_crypt_passwd}" ]; then
usermod -p "${user_crypt_passwd}" ${user_name}
if [ "${user_has_passwd}" = 'true' ]; then
log.INFO "User '${user_name}' password set successfully"
else
log.INFO "User '${user_name}' password removed successfully"
fi
else
if [ "${user_has_passwd}" = 'true' ]; then
log.INFO "No change required to password for user '${user_name}'"
else
log.INFO "User '${user_name}' has no password, and none was requested"
fi
fi
# Add sudoers entry if requested for user # Add sudoers entry if requested for user
if [ "${user_sudo}" = 'true' ]; then if [ "${user_sudo}" = 'true' ]; then
# Add sudoers entry if it does not already exist # Add sudoers entry if it does not already exist
user_sudo_file=/etc/sudoers.d/${keyword}-${user_name}-sudo local user_sudo_file=/etc/sudoers.d/${keyword}-${user_name}-sudo
if [ -f "${user_sudo_file}" ] ; then if [ -f "${user_sudo_file}" ] ; then
log.INFO "User '${user_name}' already added to sudoers: ${user_sudo_file}" log.INFO "User '${user_name}' already added to sudoers: ${user_sudo_file}"
else else
@ -56,15 +78,21 @@ add_user(){
log.INFO "User '${user_name}' was not requested sudo access" log.INFO "User '${user_name}' was not requested sudo access"
fi fi
if [ "${user_has_passwd}" = "true" ] && \
[ "${user_sudo}" = "true" ] && \
[ "${user_name}" != "${builtin_acct}" ]; then
expire_builtin_acct_passwd_vote=true
fi
curr_userlist="${curr_userlist}${user_name}"$'\n' curr_userlist="${curr_userlist}${user_name}"$'\n'
} }
add_sshkeys(){ add_sshkeys(){
die_if_null "${user_name}" ", 'user_name' env var not initialized" die_if_null "${user_name}" ", 'user_name' env var not initialized"
user_sshkeys="$@" local user_sshkeys="$@"
sshkey_dir="/home/${user_name}/.ssh" local sshkey_dir="/home/${user_name}/.ssh"
sshkey_file="${sshkey_dir}/authorized_keys" local sshkey_file="${sshkey_dir}/authorized_keys"
if [ -z "${user_sshkeys}" ]; then if [ -z "${user_sshkeys}" ]; then
log.INFO "User '${user_name}' has no SSH keys defined" log.INFO "User '${user_name}' has no SSH keys defined"
if [ -f "${sshkey_file}" ]; then if [ -f "${sshkey_file}" ]; then
@ -72,11 +100,11 @@ add_sshkeys(){
log.INFO "User '${user_name}' has had its authorized_keys file wiped" log.INFO "User '${user_name}' has had its authorized_keys file wiped"
fi fi
else else
sshkey_file_contents='# NOTE: This file is managed by divingbell'$'\n' local sshkey_file_contents='# NOTE: This file is managed by divingbell'$'\n'
for sshkey in "$@"; do for sshkey in "$@"; do
sshkey_file_contents="${sshkey_file_contents}${sshkey}"$'\n' sshkey_file_contents="${sshkey_file_contents}${sshkey}"$'\n'
done done
write_file=false local write_file=false
if [ -f "${sshkey_file}" ]; then if [ -f "${sshkey_file}" ]; then
if [ "$(cat "${sshkey_file}")" = \ if [ "$(cat "${sshkey_file}")" = \
"$(echo "${sshkey_file_contents}" | head -n-1)" ]; then "$(echo "${sshkey_file_contents}" | head -n-1)" ]; then
@ -98,33 +126,44 @@ add_sshkeys(){
# In the event that the user specifies ssh keys for the built-in account and # In the event that the user specifies ssh keys for the built-in account and
# no others, do not expire the built-in account # no others, do not expire the built-in account
if [ "${user_name}" != "${builtin_acct}" ]; then if [ "${user_sudo}" = "true" ] && \
expire_builtin_acct=true [ "${user_name}" != "${builtin_acct}" ]; then
expire_builtin_acct_ssh_vote=true
fi fi
fi fi
} }
{{- if hasKey .Values.conf "uamlite" }} {{- if hasKey .Values.conf "uamlite" }}
{{- if hasKey .Values.conf.uamlite "purge_expired_users" }} {{- if hasKey .Values.conf.uamlite "purge_expired_users" }}
purge_expired_users={{ .Values.conf.uamlite.purge_expired_users | quote }} purge_expired_users={{ .Values.conf.uamlite.purge_expired_users | squote }}
{{- end }} {{- end }}
{{- if hasKey .Values.conf.uamlite "users" }} {{- if hasKey .Values.conf.uamlite "users" }}
{{- range $item := .Values.conf.uamlite.users }} {{- range $item := .Values.conf.uamlite.users }}
{{- range $key, $value := . }} {{- range $key, $value := . }}
{{ $key }}={{ $value | quote }} \ {{- if eq $key "user_crypt_passwd" }}
{{/* supported crypt types are 2a (blowfish), 1 (md5), 5 (sha-256), and 6 (sha-512) */}}
{{- if not (or (regexMatch "\\$2a\\$.*\\$.*" $value) (regexMatch "\\$[156]\\$.*\\$.*" $value)) }}
{{- fail (print "BAD PASSWORD FOR '" $item.user_name "': The 'user_crypt_passwd' specified for '" $item.user_name "' does not pass regex checks. Ensure that the supplied user password is encoded per divingbell documentation at https://divingbell.readthedocs.io/en/latest/#uamlite") }}
{{- end }}
{{- end }}
{{ $key }}={{ $value | squote }} \
{{- end }} {{- end }}
add_user add_user
{{- range $key, $value := . }} {{- range $key, $value := . }}
{{ $key }}={{ $value | quote }} \ {{ $key }}={{ $value | squote }} \
{{- end }} {{- end }}
add_sshkeys {{ range $ssh_key := .user_sshkeys }}{{ $ssh_key | quote }} {{end}} {{- if hasKey . "user_sshkeys" }}
{{- if not (eq (first .user_sshkeys) "Unmanaged") }}
add_sshkeys {{ range $ssh_key := .user_sshkeys }}{{ if not (or (regexMatch "ssh-dss .*" $ssh_key) (regexMatch "ecdsa-.*" $ssh_key) (regexMatch "ssh-ed25519 .*" $ssh_key) (regexMatch "ssh-rsa .*" $ssh_key)) }}{{ fail (print "BAD SSH KEY FOR '" $item.user_name "': One of the 'user_sshkeys' specified for '" $item.user_name "' does not pass regex checks: '" $ssh_key "'. Ensure that the supplied user SSH keys are supported/formatted per divingbell documentation at https://divingbell.readthedocs.io/en/latest/#uamlite") }}{{ else }}{{ $ssh_key | squote }}{{ end }} {{ end }}
{{- end }}
{{- else }}
add_sshkeys
{{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}
# TODO: This should be done before applying new settings rather than after
# Expire any previously defined users that are no longer defined # Expire any previously defined users that are no longer defined
if [ -n "$(getent passwd | grep ${keyword} | cut -d':' -f1)" ]; then if [ -n "$(getent passwd | grep ${keyword} | cut -d':' -f1)" ]; then
users="$(getent passwd | grep ${keyword} | cut -d':' -f1)" users="$(getent passwd | grep ${keyword} | cut -d':' -f1)"
@ -163,7 +202,8 @@ fi
if [ -n "${builtin_acct}" ] && [ -n "$(getent passwd ${builtin_acct})" ]; then if [ -n "${builtin_acct}" ] && [ -n "$(getent passwd ${builtin_acct})" ]; then
# Disable built-in account as long as there was at least one account defined # Disable built-in account as long as there was at least one account defined
# in this chart with a ssh key present # in this chart with a ssh key present
if [ "${expire_builtin_acct}" = "true" ]; then if [ "${expire_builtin_acct_passwd_vote}" = "true" ] && \
[ "${expire_builtin_acct_ssh_vote}" = "true" ]; then
if [ "$(chage -l ${builtin_acct} | grep 'Account expires' | cut -d':' -f2 | if [ "$(chage -l ${builtin_acct} | grep 'Account expires' | cut -d':' -f2 |
tr -d '[:space:]')" = "never" ]; then tr -d '[:space:]')" = "never" ]; then
usermod --expiredate 1 ${builtin_acct} usermod --expiredate 1 ${builtin_acct}

View File

@ -41,6 +41,7 @@ USERNAME2_SUDO=false
USERNAME2_SSHKEY1="ssh-rsa xyz456 comment" USERNAME2_SSHKEY1="ssh-rsa xyz456 comment"
USERNAME2_SSHKEY2="ssh-rsa qwe789 comment" USERNAME2_SSHKEY2="ssh-rsa qwe789 comment"
USERNAME2_SSHKEY3="ssh-rsa rfv000 comment" USERNAME2_SSHKEY3="ssh-rsa rfv000 comment"
USERNAME2_CRYPT_PASSWD='$6$AF.NLpphOJjMVTYC$GD6wyUTy9vIgatoMbtTDYcVtEJqh/Mrx3BRetVstMsNodSyn3ZFIZOMRePpRpGbFArnAxgkL1PtQxsZHCgtFn/'
USERNAME3=userthree USERNAME3=userthree
USERNAME3_SUDO=true USERNAME3_SUDO=true
USERNAME4=userfour USERNAME4=userfour
@ -556,6 +557,16 @@ _test_ssh_keys(){
fi fi
} }
_test_user_passwd(){
username=$1
crypt_passwd="$2"
if [ "$crypt_passwd" != "$(getent shadow $username | cut -d':' -f2)" ]; then
echo "Error: User '$username' passwd did not match expected val '$crypt_passwd'"
return 1
fi
}
test_uamlite(){ test_uamlite(){
# Test the first set of values # Test the first set of values
local overrides_yaml=${LOGS_SUBDIR}/${FUNCNAME}-set1.yaml local overrides_yaml=${LOGS_SUBDIR}/${FUNCNAME}-set1.yaml
@ -568,6 +579,7 @@ test_uamlite(){
- ${USERNAME1_SSHKEY1} - ${USERNAME1_SSHKEY1}
- user_name: ${USERNAME2} - user_name: ${USERNAME2}
user_sudo: ${USERNAME2_SUDO} user_sudo: ${USERNAME2_SUDO}
user_crypt_passwd: ${USERNAME2_CRYPT_PASSWD}
user_sshkeys: user_sshkeys:
- ${USERNAME2_SSHKEY1} - ${USERNAME2_SSHKEY1}
- ${USERNAME2_SSHKEY2} - ${USERNAME2_SSHKEY2}
@ -580,17 +592,21 @@ test_uamlite(){
_test_user_enabled ${USERNAME1} true _test_user_enabled ${USERNAME1} true
_test_sudo_enabled ${USERNAME1} ${USERNAME1_SUDO} _test_sudo_enabled ${USERNAME1} ${USERNAME1_SUDO}
_test_ssh_keys ${USERNAME1} "${USERNAME1_SSHKEY1}" _test_ssh_keys ${USERNAME1} "${USERNAME1_SSHKEY1}"
_test_user_passwd ${USERNAME1} '*'
_test_user_enabled ${USERNAME2} true _test_user_enabled ${USERNAME2} true
_test_sudo_enabled ${USERNAME2} ${USERNAME2_SUDO} _test_sudo_enabled ${USERNAME2} ${USERNAME2_SUDO}
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY1}" _test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY1}"
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY2}" _test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY2}"
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY3}" _test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY3}"
_test_user_passwd ${USERNAME2} ${USERNAME2_CRYPT_PASSWD}
_test_user_enabled ${USERNAME3} true _test_user_enabled ${USERNAME3} true
_test_sudo_enabled ${USERNAME3} ${USERNAME3_SUDO} _test_sudo_enabled ${USERNAME3} ${USERNAME3_SUDO}
_test_ssh_keys ${USERNAME3} false _test_ssh_keys ${USERNAME3} false
_test_user_passwd ${USERNAME3} '*'
_test_user_enabled ${USERNAME4} true _test_user_enabled ${USERNAME4} true
_test_sudo_enabled ${USERNAME4} ${USERNAME4_SUDO} _test_sudo_enabled ${USERNAME4} ${USERNAME4_SUDO}
_test_ssh_keys ${USERNAME4} false _test_ssh_keys ${USERNAME4} false
_test_user_passwd ${USERNAME4} '*'
echo '[SUCCESS] uamlite test1 passed successfully' >> "${TEST_RESULTS}" echo '[SUCCESS] uamlite test1 passed successfully' >> "${TEST_RESULTS}"
# Test an updated set of values # Test an updated set of values
@ -619,17 +635,21 @@ test_uamlite(){
_test_user_enabled ${USERNAME1} true _test_user_enabled ${USERNAME1} true
_test_sudo_enabled ${USERNAME1} ${uname1_sudo} _test_sudo_enabled ${USERNAME1} ${uname1_sudo}
_test_ssh_keys ${USERNAME1} false _test_ssh_keys ${USERNAME1} false
_test_user_passwd ${USERNAME1} '*'
_test_user_enabled ${USERNAME2} true _test_user_enabled ${USERNAME2} true
_test_sudo_enabled ${USERNAME2} ${uname2_sudo} _test_sudo_enabled ${USERNAME2} ${uname2_sudo}
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY1}" _test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY1}"
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY2}" _test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY2}"
_test_user_passwd ${USERNAME2} '*'
_test_user_enabled ${USERNAME3} true _test_user_enabled ${USERNAME3} true
_test_sudo_enabled ${USERNAME3} ${uname3_sudo} _test_sudo_enabled ${USERNAME3} ${uname3_sudo}
_test_ssh_keys ${USERNAME3} "${USERNAME1_SSHKEY1}" _test_ssh_keys ${USERNAME3} "${USERNAME1_SSHKEY1}"
_test_ssh_keys ${USERNAME3} "${USERNAME2_SSHKEY3}" _test_ssh_keys ${USERNAME3} "${USERNAME2_SSHKEY3}"
_test_user_passwd ${USERNAME3} '*'
_test_user_enabled ${USERNAME4} true _test_user_enabled ${USERNAME4} true
_test_sudo_enabled ${USERNAME4} ${USERNAME4_SUDO} _test_sudo_enabled ${USERNAME4} ${USERNAME4_SUDO}
_test_ssh_keys ${USERNAME4} false _test_ssh_keys ${USERNAME4} false
_test_user_passwd ${USERNAME4} '*'
echo '[SUCCESS] uamlite test2 passed successfully' >> "${TEST_RESULTS}" echo '[SUCCESS] uamlite test2 passed successfully' >> "${TEST_RESULTS}"
# Test revert/rollback functionality # Test revert/rollback functionality
@ -646,7 +666,7 @@ test_uamlite(){
echo '[SUCCESS] uamlite test3 passed successfully' >> "${TEST_RESULTS}" echo '[SUCCESS] uamlite test3 passed successfully' >> "${TEST_RESULTS}"
# Test purge users flag # Test purge users flag
overrides_yaml=${LOGS_SUBDIR}/${FUNCNAME}-set3.yaml overrides_yaml=${LOGS_SUBDIR}/${FUNCNAME}-set4.yaml
echo "conf: echo "conf:
uamlite: uamlite:
purge_expired_users: true" > "${overrides_yaml}" purge_expired_users: true" > "${overrides_yaml}"
@ -657,6 +677,33 @@ test_uamlite(){
_test_user_purged ${USERNAME3} _test_user_purged ${USERNAME3}
_test_user_purged ${USERNAME4} _test_user_purged ${USERNAME4}
echo '[SUCCESS] uamlite test4 passed successfully' >> "${TEST_RESULTS}" echo '[SUCCESS] uamlite test4 passed successfully' >> "${TEST_RESULTS}"
# Test invalid password
overrides_yaml=${LOGS_SUBDIR}/${FUNCNAME}-set5.yaml
user2_crypt_passwd_invalid='plaintextPassword'
echo "conf:
uamlite:
users:
- user_name: ${USERNAME2}
user_crypt_passwd: ${user2_crypt_passwd_invalid}" > "${overrides_yaml}"
install_base "--values=${overrides_yaml}" 2>&1 | grep 'BAD PASSWORD' || \
(echo "[FAIL] uamlite test5 did not receive expected 'BAD PASSWORD' error" && exit 1)
echo '[SUCCESS] uamlite test5 passed successfully' >> "${TEST_RESULTS}"
# Test invalid SSH key
overrides_yaml=${LOGS_SUBDIR}/${FUNCNAME}-set6.yaml
user2_bad_sshkey='AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmT key-comment'
echo "conf:
uamlite:
users:
- user_name: ${USERNAME2}
user_sshkeys:
- ${USERNAME2_SSHKEY1}
- ${user2_bad_sshkey}
- ${USERNAME2_SSHKEY3}" > "${overrides_yaml}"
install_base "--values=${overrides_yaml}" 2>&1 | grep 'BAD SSH KEY' || \
(echo "[FAIL] uamlite test6 did not receive expected 'BAD SSH KEY' error" && exit 1)
echo '[SUCCESS] uamlite test6 passed successfully' >> "${TEST_RESULTS}"
} }
# test daemonset value overrides for hosts and labels # test daemonset value overrides for hosts and labels

View File

@ -17,9 +17,6 @@
Divingbell Divingbell
========== ==========
What is it?
-----------
Divingbell is a lightweight solution for: Divingbell is a lightweight solution for:
1. Bare metal configuration management for a few very targeted use cases 1. Bare metal configuration management for a few very targeted use cases
2. Bare metal package manager orchestration 2. Bare metal package manager orchestration
@ -66,10 +63,6 @@ fashion: the idempotent automation for each daemonset will only re-run when
Armada spawns/respawns the container, or if information relevant to the host Armada spawns/respawns the container, or if information relevant to the host
changes in the configmap. changes in the configmap.
For upgrades, a decision was taken not to use any of the built-in Kubernetes
update strategies such as RollingUpdate. Instead, we are putting this on
Armada to handle the orchestration of how to do upgrades (e.g., rack by rack).
Daemonset configs Daemonset configs
----------------- -----------------
@ -123,19 +116,73 @@ access. Ex::
purge_expired_users: false purge_expired_users: false
users: users:
- user_name: testuser - user_name: testuser
user_sudo: True user_crypt_passwd: $6$...
user_sudo: true
user_sshkeys: user_sshkeys:
- ssh-rsa AAAAB3N... key1-comment - ssh-rsa AAAAB3N... key1-comment
- ssh-rsa AAAAVY6... key2-comment - ssh-rsa AAAAVY6... key2-comment
An update to the chart with revmoed users will result in those user's accounts Setting user passwords
being expired, preventing those users any access through those accounts. This """"""""""""""""""""""
does not delete their home directory or any other files, and provides UID
consistency in the event the same account gets re-added later, and they regain
access to their files again.
However, if it is desired to purge expired and removed accounts and their home Including ``user_crypt_passwd`` to set a user password is optional.
directories, this may be done by the ``purge_expired_users`` option to ``true``.
If setting a password for the user, the chart expects the password to be
encrypted with SHA-512 and formatted in the way that ``crypt`` library expects.
Run the following command to generate the needed encrypted password from the
plaintext password::
python3 -c "from getpass import getpass; from crypt import *; p=getpass(); print('\n'+crypt(p, METHOD_SHA512)) if p==getpass('Please repeat: ') else print('\nPassword mismatch.')"
Use the output of the above command as the ``user_crypt_passwd`` for the user.
(Credit to `unix.stackexchange.com <https://unix.stackexchange.com/questions/81240/manually-generate-password-for-etc-shadow>`_.)
If the password is not formatted how crypt expects, the chart will throw an
error and fail to render.
At least one user must be defined with a password and sudo in order for the
built-in ``ubuntu`` account to be disabled. This is because in a situation where
network access is unavailable, console username/password access will be the only
login option.
Setting user sudo
"""""""""""""""""
Including ``user_sudo`` to set user sudo access is optional. The default value
is ``false``.
At least one user must be defined with sudo access in order for the built-in
``ubuntu`` account to be disabled.
SSH keys
""""""""
Including ``user_sshkeys`` for defining one or more user SSH keys is optional.
The chart will throw an error and fail to render if the SSH key is not one of
the following formats:
- dsa (ssh-dss ...)
- ecdsa (ecdsa-...)
- ed25519 (ssh-ed25519 ...)
- rsa (ssh-rsa ...)
Setting ``user_sshkeys`` to ``[ Unmanaged ]`` will instruct divingbell not to
manage the user's authorized_keys file.
At least one user must be defined with an SSH key and sudo in order for the
built-in ``ubuntu`` account to be disabled.
Purging expired users
"""""""""""""""""""""
Including the ``purge_expired_users`` key-value pair is optional. The default
value is ``false``.
This option must be set to ``true`` if it is desired to purge expired accounts
and remove their home directories. Otherwise, removed accounts are expired (so
users cannot login) but their home directories remain intact, in order to
maintain UID consistency (in the event the same accounts gets re-added later,
they regain access to their home directory files without UID mismatching).
Node specific configurations Node specific configurations
---------------------------- ----------------------------