[US367408] Add support for user & ssh key mgmt

Change-Id: I0ef68dfd80194e6da289fbf86f5cd2ee5c7edad8
This commit is contained in:
Craig Anderson 2018-03-15 06:13:56 +00:00
parent b4c7160aa6
commit 9e7028416e
6 changed files with 461 additions and 10 deletions

View File

@ -15,8 +15,9 @@
HELM := helm
TASK := build
EXCLUDES := helm-toolkit doc tests tools logs
EXCLUDES := helm-toolkit docs tests tools logs
CHARTS := helm-toolkit $(filter-out $(EXCLUDES), $(patsubst %/.,%,$(wildcard */.)))
CHART := divingbell
all: $(CHARTS)
@ -42,3 +43,8 @@ clean:
rm -rf */templates/_globals.tpl
.PHONY: $(EXCLUDES) $(CHARTS)
.PHONY: charts
charts: clean
$(HELM) dep up $(CHART)
$(HELM) package $(CHART)

View File

@ -0,0 +1,181 @@
#!/bin/bash
{{/*
# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*/}}
set -e
cat <<'EOF' > {{ .Values.conf.chroot_mnt_path | quote }}/tmp/uamlite_host.sh
{{ include "divingbell.shcommon" . }}
keyword='divingbell'
builtin_acct='ubuntu'
add_user(){
die_if_null "${user_name}" ", 'user_name' env var not initialized"
: ${user_sudo:=false}
# Create user if user does not already exist
getent passwd ${user_name} && \
log.INFO "User '${user_name}' already exists" || \
(useradd --create-home --shell /bin/bash --comment ${keyword} ${user_name} && \
log.INFO "User '${user_name}' successfully created")
# Unexpire the user (if user had been previously expired)
if [ "$(chage -l ${user_name} | grep 'Account expires' | cut -d':' -f2 |
tr -d '[:space:]')" != "never" ]; then
usermod --expiredate "" ${user_name}
log.INFO "User '${user_name}' has been unexpired"
fi
# Add sudoers entry if requested for user
if [ "${user_sudo}" = 'true' ]; then
# Add sudoers entry if it does not already exist
user_sudo_file=/etc/sudoers.d/${keyword}-${user_name}-sudo
if [ -f "${user_sudo_file}" ] ; then
log.INFO "User '${user_name}' already added to sudoers: ${user_sudo_file}"
else
echo "${user_name} ALL=(ALL) NOPASSWD:ALL" > "${user_sudo_file}"
log.INFO "User '${user_name}' added to sudoers: ${user_sudo_file}"
fi
curr_sudoers="${curr_sudoers}${user_sudo_file}"$'\n'
else
log.INFO "User '${user_name}' was not requested sudo access"
fi
curr_userlist="${curr_userlist}${user_name}"$'\n'
}
add_sshkeys(){
die_if_null "${user_name}" ", 'user_name' env var not initialized"
user_sshkeys="$@"
sshkey_dir="/home/${user_name}/.ssh"
sshkey_file="${sshkey_dir}/authorized_keys"
if [ -z "${user_sshkeys}" ]; then
log.INFO "User '${user_name}' has no SSH keys defined"
if [ -f "${sshkey_file}" ]; then
rm "${sshkey_file}"
log.INFO "User '${user_name}' has had its authorized_keys file wiped"
fi
else
sshkey_file_contents='# NOTE: This file is managed by divingbell'$'\n'
for sshkey in "$@"; do
sshkey_file_contents="${sshkey_file_contents}${sshkey}"$'\n'
done
write_file=false
if [ -f "${sshkey_file}" ]; then
if [ "$(cat "${sshkey_file}")" = \
"$(echo "${sshkey_file_contents}" | head -n-1)" ]; then
log.INFO "User '${user_name}' has no new SSH keys"
else
write_file=true
fi
else
write_file=true
fi
if [ "${write_file}" = "true" ]; then
mkdir -p "${sshkey_dir}"
chmod 700 "${sshkey_dir}"
echo -e "${sshkey_file_contents}" > "${sshkey_file}"
chown -R ${user_name}:${user_name} "${sshkey_dir}" || \
(rm "${sshkey_file}" && die "Error setting ownership on ${sshkey_dir}")
log.INFO "User '${user_name}' has had SSH keys deployed: ${user_sshkeys}"
fi
custom_sshkeys_present=true
fi
}
{{- if hasKey .Values.conf "uamlite" }}
{{- if hasKey .Values.conf.uamlite "users" }}
{{- range $item := .Values.conf.uamlite.users }}
{{- range $key, $value := . }}
{{ $key }}={{ $value | quote }} \
{{- end }}
add_user
{{- range $key, $value := . }}
{{ $key }}={{ $value | quote }} \
{{- end }}
add_sshkeys {{ range $ssh_key := .user_sshkeys }}{{ $ssh_key | quote }} {{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
users="$(getent passwd | grep ${keyword} | cut -d':' -f1)"
echo "$users" | sort > /tmp/prev_users
echo "$curr_userlist" | sort > /tmp/curr_users
revert_list="$(comm -23 /tmp/prev_users /tmp/curr_users)"
IFS=$'\n'
for user in ${revert_list}; do
# We expire rather than delete the user to maintain local UID FS consistency
usermod --expiredate 1 ${user}
log.INFO "User '${user}' has been disabled (expired)"
done
# Delete any previous user sudo access that is no longer defined
sudoers="$(find /etc/sudoers.d | grep ${keyword})"
echo "$sudoers" | sort > /tmp/prev_sudoers
echo "$curr_sudoers" | sort > /tmp/curr_sudoers
revert_list="$(comm -23 /tmp/prev_sudoers /tmp/curr_sudoers)"
IFS=$'\n'
for sudo_file in ${revert_list}; do
rm "${sudo_file}"
log.INFO "Sudoers file '${sudo_file}' has been deleted"
done
if [ -n "${builtin_acct}" ] && [ -n "$(getent passwd ${builtin_acct})" ]; then
# Disable built-in account as long as there was at least one account defined
# in this chart with a ssh key present
if [ "${custom_sshkeys_present}" = "true" ]; then
if [ "$(chage -l ${builtin_acct} | grep 'Account expires' | cut -d':' -f2 |
tr -d '[:space:]')" = "never" ]; then
usermod --expiredate 1 ${builtin_acct}
fi
# Re-enable built-in account as a fallback in the event that are no other
# accounts defined in this chart with a ssh key present
else
if [ "$(chage -l ${builtin_acct} | grep 'Account expires' | cut -d':' -f2 |
tr -d '[:space:]')" != "never" ]; then
usermod --expiredate "" ${builtin_acct}
fi
fi
fi
if [ -n "${curr_userlist}" ]; then
log.INFO 'All uamlite data successfully validated on this node.'
else
log.WARN 'No uamlite overrides defined for this node.'
fi
exit 0
EOF
chmod 755 {{ .Values.conf.chroot_mnt_path | quote }}/tmp/uamlite_host.sh
chroot {{ .Values.conf.chroot_mnt_path | quote }} /tmp/uamlite_host.sh
sleep 1
echo 'INFO Putting the daemon to sleep.'
while [ 1 ]; do
sleep 300
done
exit 0

View File

@ -0,0 +1,30 @@
{{/*
Copyright 2018 AT&T Intellectual Property. All other rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/}}
{{- define "divingbell.configmap.uamlite" }}
{{- $configMapName := index . 0 }}
{{- $envAll := index . 1 }}
{{- with $envAll }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ $configMapName }}
data:
uamlite: |+
{{ tuple "bin/_uamlite.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,65 @@
{{/*
# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
*/}}
{{- define "divingbell.daemonset.uamlite" }}
{{- $daemonset := index . 0 }}
{{- $configMapName := index . 1 }}
{{- $envAll := index . 2 }}
{{- with $envAll }}
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: {{ $daemonset }}
spec:
template:
metadata:
labels:
{{ list $envAll .Chart.Name $daemonset | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 8 }}
spec:
hostNetwork: true
hostPID: true
hostIPC: true
containers:
- name: {{ $daemonset }}
image: {{ .Values.images.divingbell }}
imagePullPolicy: {{ .Values.images.pull_policy }}
command:
- /tmp/{{ $daemonset }}.sh
volumeMounts:
- name: rootfs-{{ $daemonset }}
mountPath: {{ .Values.conf.chroot_mnt_path }}
- name: {{ $configMapName }}
mountPath: /tmp/{{ $daemonset }}.sh
subPath: {{ $daemonset }}
readOnly: true
securityContext:
privileged: true
volumes:
- name: rootfs-{{ $daemonset }}
hostPath:
path: /
- name: {{ $configMapName }}
configMap:
name: {{ $configMapName }}
defaultMode: 0555
{{- end }}
{{- end }}
{{- $daemonset := "uamlite" }}
{{- $configMapName := "divingbell-uamlite" }}
{{- $daemonset_yaml := list $daemonset $configMapName . | include "divingbell.daemonset.uamlite" | toString | fromYaml }}
{{- $configmap_include := "divingbell.configmap.uamlite" }}
{{- list $daemonset $daemonset_yaml $configmap_include $configMapName . | include "helm-toolkit.utils.daemonset_overrides" }}

View File

@ -33,6 +33,18 @@ ETHTOOL_KEY4=tx-nocache-copy
ETHTOOL_VAL4_DEFAULT=off
ETHTOOL_KEY5=tx-checksum-ip-generic
ETHTOOL_VAL5_DEFAULT=on
USERNAME1=userone
USERNAME1_SUDO=true
USERNAME1_SSHKEY1="ssh-rsa abc123 comment"
USERNAME2=usertwo
USERNAME2_SUDO=false
USERNAME2_SSHKEY1="ssh-rsa xyz456 comment"
USERNAME2_SSHKEY2="ssh-rsa qwe789 comment"
USERNAME2_SSHKEY3="ssh-rsa rfv000 comment"
USERNAME3=userthree
USERNAME3_SUDO=true
USERNAME4=userfour
USERNAME4_SUDO=false
nic_info="$(lshw -class network)"
physical_nic=''
IFS=$'\n'
@ -96,6 +108,14 @@ _write_ethtool(){
fi
}
_reset_account(){
if [ -n "$1" ]; then
sudo deluser $1 >& /dev/null || true
sudo rm -r /home/$1 >& /dev/null || true
sudo rm /etc/sudoers.d/*$1* >& /dev/null || true
fi
}
init_default_state(){
if [ "${1}" = 'make' ]; then
(cd ../../../; make)
@ -112,6 +132,11 @@ init_default_state(){
_write_ethtool ${DEVICE} ${ETHTOOL_KEY3} ${ETHTOOL_VAL3_DEFAULT}
_write_ethtool ${DEVICE} ${ETHTOOL_KEY4} ${ETHTOOL_VAL4_DEFAULT}
_write_ethtool ${DEVICE} ${ETHTOOL_KEY5} ${ETHTOOL_VAL5_DEFAULT}
# Remove any created accounts, SSH keys
_reset_account ${USERNAME1}
_reset_account ${USERNAME2}
_reset_account ${USERNAME3}
_reset_account ${USERNAME4}
}
install(){
@ -134,9 +159,9 @@ get_container_status(){
local log_connect_sleep_interval=2
local wait_time=0
while : ; do
kubectl logs "${container}" --namespace="${NAME}" > /dev/null && break ||
echo "Waiting for container logs..." &&
wait_time=$((${wait_time} + ${log_connect_sleep_interval})) &&
kubectl logs "${container}" --namespace="${NAME}" > /dev/null && break || \
echo "Waiting for container logs..." && \
wait_time=$((${wait_time} + ${log_connect_sleep_interval})) && \
sleep ${log_connect_sleep_interval}
if [ ${wait_time} -ge ${log_connect_timeout} ]; then
echo "Hit timeout while waiting for container logs to become available."
@ -149,7 +174,8 @@ get_container_status(){
while : ; do
CLOGS="$(kubectl logs --namespace="${NAME}" "${container}" 2>&1)"
local status="$(echo "${CLOGS}" | tail -1)"
if [[ ${status} = *ERROR* ]] || [[ ${status} = *TRACE* ]]; then
if [[ $(echo -e ${status} | tr -d '[:cntrl:]') = *ERROR* ]] ||
[[ $(echo -e ${status} | tr -d '[:cntrl:]') = *TRACE* ]]; then
if [ "${2}" = 'expect_failure' ]; then
echo 'Pod exited as expected'
break
@ -159,8 +185,8 @@ get_container_status(){
echo "${CLOGS}"
exit 1
fi
elif [ "${status}" = 'INFO Putting the daemon to sleep.' ] ||
[ "${status}" = 'DEBUG + exit 0' ]; then
elif [[ $(echo -e ${status} | tr -d '[:cntrl:]') = *'INFO Putting the daemon to sleep.'* ]] ||
[[ $(echo -e ${status} | tr -d '[:cntrl:]') = *'DEBUG + exit 0'* ]]; then
if [ "${2}" = 'expect_failure' ]; then
echo 'Expected pod to die with error, but pod completed successfully'
echo 'pod logs:'
@ -475,6 +501,138 @@ test_ethtool(){
echo '[SUCCESS] ethtool test7 passed successfully' >> "${TEST_RESULTS}"
}
_test_user_enabled(){
username=$1
user_enabled=$2
if [ "${user_enabled}" = "true" ]; then
# verify the user is there and not set to expire
getent passwd $username >& /dev/null
test "$(chage -l ${username} | grep 'Account expires' | cut -d':' -f2 |
tr -d '[:space:]')" = "never"
else
# If the user exists, verify it's not non-expiring
if [ -n "$(getent passwd $username)" ]; then
test "$(chage -l ${username} | grep 'Account expires' | cut -d':' -f2 |
tr -d '[:space:]')" != "never"
fi
fi
}
_test_sudo_enabled(){
username=$1
sudo_enable=$2
sudoers_file=/etc/sudoers.d/*$username*
if [ "${sudo_enable}" = "true" ]; then
test -f $sudoers_file
else
test ! -f $sudoers_file
fi
}
_test_ssh_keys(){
username=$1
sshkey=$2
ssh_file=/home/$username/.ssh/authorized_keys
if [ "$sshkey" = "false" ]; then
test ! -f "${ssh_file}"
else
grep "$sshkey" "${ssh_file}"
fi
}
test_uamlite(){
# Test the first set of values
local overrides_yaml=${LOGS_SUBDIR}/${FUNCNAME}-set1.yaml
echo "conf:
uamlite:
users:
- user_name: ${USERNAME1}
user_sudo: ${USERNAME1_SUDO}
user_sshkeys:
- ${USERNAME1_SSHKEY1}
- user_name: ${USERNAME2}
user_sudo: ${USERNAME2_SUDO}
user_sshkeys:
- ${USERNAME2_SSHKEY1}
- ${USERNAME2_SSHKEY2}
- ${USERNAME2_SSHKEY3}
- user_name: ${USERNAME3}
user_sudo: ${USERNAME3_SUDO}
- user_name: ${USERNAME4}" > "${overrides_yaml}"
install_base "--values=${overrides_yaml}"
get_container_status uamlite
_test_user_enabled ${USERNAME1} true
_test_sudo_enabled ${USERNAME1} ${USERNAME1_SUDO}
_test_ssh_keys ${USERNAME1} "${USERNAME1_SSHKEY1}"
_test_user_enabled ${USERNAME2} true
_test_sudo_enabled ${USERNAME2} ${USERNAME2_SUDO}
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY1}"
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY2}"
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY3}"
_test_user_enabled ${USERNAME3} true
_test_sudo_enabled ${USERNAME3} ${USERNAME3_SUDO}
_test_ssh_keys ${USERNAME3} false
_test_user_enabled ${USERNAME4} true
_test_sudo_enabled ${USERNAME4} ${USERNAME4_SUDO}
_test_ssh_keys ${USERNAME4} false
echo '[SUCCESS] uamlite test1 passed successfully' >> "${TEST_RESULTS}"
# Test an updated set of values
overrides_yaml=${LOGS_SUBDIR}/${FUNCNAME}-set2.yaml
uname1_sudo=false
uname2_sudo=true
uname3_sudo=false
echo "conf:
uamlite:
users:
- user_name: ${USERNAME1}
user_sudo: ${uname1_sudo}
- user_name: ${USERNAME2}
user_sudo: ${uname2_sudo}
user_sshkeys:
- ${USERNAME2_SSHKEY1}
- ${USERNAME2_SSHKEY2}
- user_name: ${USERNAME3}
user_sudo: ${uname3_sudo}
user_sshkeys:
- ${USERNAME1_SSHKEY1}
- ${USERNAME2_SSHKEY3}
- user_name: ${USERNAME4}" > "${overrides_yaml}"
install_base "--values=${overrides_yaml}"
get_container_status uamlite
_test_user_enabled ${USERNAME1} true
_test_sudo_enabled ${USERNAME1} ${uname1_sudo}
_test_ssh_keys ${USERNAME1} false
_test_user_enabled ${USERNAME2} true
_test_sudo_enabled ${USERNAME2} ${uname2_sudo}
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY1}"
_test_ssh_keys ${USERNAME2} "${USERNAME2_SSHKEY2}"
_test_user_enabled ${USERNAME3} true
_test_sudo_enabled ${USERNAME3} ${uname3_sudo}
_test_ssh_keys ${USERNAME3} "${USERNAME1_SSHKEY1}"
_test_ssh_keys ${USERNAME3} "${USERNAME2_SSHKEY3}"
_test_user_enabled ${USERNAME4} true
_test_sudo_enabled ${USERNAME4} ${USERNAME4_SUDO}
_test_ssh_keys ${USERNAME4} false
echo '[SUCCESS] uamlite test2 passed successfully' >> "${TEST_RESULTS}"
# Test revert/rollback functionality
install_base
get_container_status uamlite
_test_user_enabled ${USERNAME1} false
_test_sudo_enabled ${USERNAME1} false
_test_user_enabled ${USERNAME2} false
_test_sudo_enabled ${USERNAME2} false
_test_user_enabled ${USERNAME3} false
_test_sudo_enabled ${USERNAME3} false
_test_user_enabled ${USERNAME4} false
_test_sudo_enabled ${USERNAME4} false
echo '[SUCCESS] uamlite test3 passed successfully' >> "${TEST_RESULTS}"
}
# test daemonset value overrides for hosts and labels
test_overrides(){
overrides_yaml=${LOGS_SUBDIR}/${FUNCNAME}-dryrun.yaml
@ -752,6 +910,7 @@ install_base
test_sysctl
test_mounts
test_ethtool
test_uamlite
purge_containers
test_overrides

View File

@ -112,10 +112,20 @@ packages
Not implemented
users
^^^^^
uamlite
^^^^^^^
Not implemented
Used to manage host level local user accounts, their SSH keys, and their sudo
access. Ex::
conf:
uamlite:
users:
- user_name: testuser
user_sudo: True
user_sshkeys:
- ssh-rsa AAAAB3N... key1-comment
- ssh-rsa AAAAVY6... key2-comment
Node specific configurations
----------------------------