From 916cfa191e1e5fac30766c9404a56d2f0d31a708 Mon Sep 17 00:00:00 2001 From: Michael Weiser Date: Fri, 1 Feb 2019 11:03:53 +0000 Subject: Handle empty password change timestamp LDAP attribute If the password change timestamp LDAP attribute is unset, e.g. because the host has been freshly added, the search will return an empty value which will cause an error message from datetime.py: Traceback (most recent call last): File "src/usr/share/laps/dependencies/datetime.py", line 47, in print action(float(timestamp)) ValueError: could not convert string to float: With this change we initialise ts_epoch to zero and leave it at that if the attribute is not set to avoid the error and cause an immediate password change. --- src/usr/share/laps/laps.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/usr/share/laps/laps.sh b/src/usr/share/laps/laps.sh index 15280d3..efdf76d 100755 --- a/src/usr/share/laps/laps.sh +++ b/src/usr/share/laps/laps.sh @@ -215,8 +215,11 @@ wrapper_get_timestamp_from_ldap() { ___wgtfl_krb5cc_tmpfile="${7}" ts_filetime="$( get_attrib_from_ldap "${___wgtfl_ldapsearch_bin}" "${___wgtfl_ldapsearch_flags}" "${___wgtfl_ldapsearch_filter}" "${___wgtfl_attrib}" "${___wgtfl_ldapconf}" "${___wgtfl_krb5cc_tmpfile}" )" - debuglev 3 && ferror "timestamp(FILETIME): ${ts_filetime}" - ts_epoch="$( "${___wgtfl_datetime_py}" -e "${ts_filetime}" )" + ts_epoch=0 + if test -n "$ts_filetime" ; then + debuglev 3 && ferror "timestamp(FILETIME): ${ts_filetime}" + ts_epoch="$( "${___wgtfl_datetime_py}" -e "${ts_filetime}" )" + fi debuglev 2 && ferror "timestamp(epoch): ${ts_epoch}" debuglev 1 && ferror "timestamp(UTC): $( date -u -d "@${ts_epoch}" "+%FT%TZ" )" -- cgit From 96e1dd2b17f74e59b93d193b15a9196da6232f46 Mon Sep 17 00:00:00 2001 From: Michael Weiser Date: Thu, 28 Feb 2019 13:08:26 +0000 Subject: Fix password change dash compatibility The echo builtin of dash has no -n option. Flatten echoing of the password to two consecutive standard echos in a group command so output can be piped to passwd in order to achive the required newlines in the output. --- src/usr/share/laps/laps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usr/share/laps/laps.sh b/src/usr/share/laps/laps.sh index efdf76d..1209f65 100755 --- a/src/usr/share/laps/laps.sh +++ b/src/usr/share/laps/laps.sh @@ -376,7 +376,7 @@ wrapper_change_password() { then echo "0" > "${LAPS_PASSWORD_STATUS_TMPFILE}" else - ___wcp_stdout="$( echo -e "$(echo ${___wcp_phrase})\n$(echo ${___wcp_phrase})" | "${___wcp_passwd_bin}" "${___wcp_user}" ; echo "$?" > "${LAPS_PASSWORD_STATUS_TMPFILE}" )" + ___wcp_stdout="$( { echo "${___wcp_phrase}" ; echo "${___wcp_phrase}" ; } | "${___wcp_passwd_bin}" "${___wcp_user}" ; echo "$?" > "${LAPS_PASSWORD_STATUS_TMPFILE}" )" fi ___wcp_passwd_result="$( cat "${LAPS_PASSWORD_STATUS_TMPFILE}" )" -- cgit From 4c38a4a985833aa50afb7f63746dab6d1f28d666 Mon Sep 17 00:00:00 2001 From: Michael Weiser Date: Thu, 28 Feb 2019 13:37:39 +0000 Subject: Do not trap SIGCHLD for dash compatibility dash honors our trapping of SIGCHLD and will cause the script to exit after the first external command finishes executing. Strangely enough, bash seems to ignore the same attempt to catch SIGCHLD. --- src/usr/share/laps/laps.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/usr/share/laps/laps.sh b/src/usr/share/laps/laps.sh index 1209f65..9780b9e 100755 --- a/src/usr/share/laps/laps.sh +++ b/src/usr/share/laps/laps.sh @@ -679,7 +679,9 @@ define_if_new LAPS_INTERACTIVE 0 # SET TRAPS #trap "CTRLC" 2 #trap "CTRLZ" 18 -trap '__ec=$? ; clean_laps ; trap "" 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ; exit ${__ec} ;' 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 +# do NOT catch SIGCHLD (17) because dash will actually exit then (bash seems to +# ignore our attempt to catch it) +trap '__ec=$? ; clean_laps ; trap "" 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 20 ; exit ${__ec} ;' 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 18 19 20 # DEBUG SIMPLECONF debuglev 5 && { -- cgit From 13ae94695852169a06207a9e1380f9f2ef836e21 Mon Sep 17 00:00:00 2001 From: Michael Weiser Date: Wed, 27 Feb 2019 13:07:54 +0000 Subject: Capture and handle ldapsearch error Since the value of $? survives command substitution and variable assignment, we can capture and evaluate it. The next hurdle is that by default only the return code of the last command in a pipe is returned which is an awk in our case that will always succeed because it'll just get no input if ldapsearch fails. This can be worked around using shell option pipefail but this is a bashism. Instead we go the route of writing it to a temporary file in a group command as elsewhere in the code. --- src/usr/share/laps/laps.sh | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/usr/share/laps/laps.sh b/src/usr/share/laps/laps.sh index 9780b9e..132e2e9 100755 --- a/src/usr/share/laps/laps.sh +++ b/src/usr/share/laps/laps.sh @@ -108,6 +108,7 @@ main_workflow() { # 2. fetch timestamp from ldap LAPS_epoch="$( wrapper_get_timestamp_from_ldap "${LAPS_LDAPSEARCH_BIN}" "${LAPS_LDAPSEARCH_FLAGS}" "${LAPS_LDAPSEARCH_FILTER}" "${LAPS_ATTRIB_TIME}" "${LAPS_LDAPCONF}" "${LAPS_DATETIME_PY}" "${LAPS_KRB5CC_TMPFILE}" )" + test $? -eq 0 || return 1 # 3. check timestamp to see if close to expiration check_ts_against_expiration_threshold "${LAPS_THRESHOLD}" "${LAPS_epoch}" "${LAPS_FORCE}" @@ -178,26 +179,42 @@ get_attrib_from_ldap() { # execute to check for ldap or kerberos errors ___gtfl_stderr="$( KRB5CCNAME="${___gtfl_krb5cc_tmpfile}" LDAPCONF="${___gtfl_ldapconf}" "${___gtfl_ldapsearch_bin}" ${___gtfl_ldapsearch_flags} "${___gtfl_ldapsearch_filter}" "${___gtfl_attrib}" 2>&1 1>/dev/null )" + if test "$?" -ne 0 ; then if echo "${___gtfl_stderr}" | grep -qiE 'Ticket expired' ; then ferror "Kerberos ticket expired. Any values from ldap will be garbage." + return 1; elif echo "${___gtfl_stderr}" | grep -qi -e 'SASL(-1): generic failure: GSSAPI Error: An invalid name was supplied (Success)' ; then ferror "GSSAPI Error: Invalid name (Success). Try using \"SASL_NOCANON on\" in lapsldap.conf. Any values from ldap will be garbage." + return 1; elif echo "${___gtfl_stderr}" | grep -qi -e 'TLS: hostname does not match CN in peer certificate' ; then ferror "TLS: hostname does not match CN. Try using \"TLS_REQCERT allow\" in lapsldap.conf. Any values from ldap will be garbage." + return 1; else { echo "other ldap error:" echo "${___gtfl_stderr}" } | debuglevoutput 9 + return 1; + fi fi # execute for actually fetching the value - ___gtfl_attrib="$( KRB5CCNAME="${___gtfl_krb5cc_tmpfile}" LDAPCONF="${___gtfl_ldapconf}" "${___gtfl_ldapsearch_bin}" ${___gtfl_ldapsearch_flags} "${___gtfl_ldapsearch_filter}" "${___gtfl_attrib}" 2>/dev/null | sed -r -e 's/^#.*$//;' -e '/^\s*$/d' | grep -iE -e "^${___gtfl_attrib}:" | awk '{print $2}' )" + ___gtfl_attrib="$( { KRB5CCNAME="${___gtfl_krb5cc_tmpfile}" LDAPCONF="${___gtfl_ldapconf}" \ + "${___gtfl_ldapsearch_bin}" ${___gtfl_ldapsearch_flags} "${___gtfl_ldapsearch_filter}" \ + "${___gtfl_attrib}" 2>/dev/null ; \ + echo "$?" > "${LAPS_LDAPSEARCH_STATUS_TMPFILE}" ; \ + } | sed -r -e 's/^#.*$//;' -e '/^\s*$/d' | grep -iE -e "^${___gtfl_attrib}:" | awk '{print $2}' )" + ___gtfl_ldap_success="$( cat "${LAPS_LDAPSEARCH_STATUS_TMPFILE}" )" + if test "$___gtfl_ldap_success" -ne 0 ; then + ferror "LDAP lookup failed" + return 1 + fi - # no value means either the ldap connection malfunctioned or there was no attribute by that name defined. + # here we can be sure that an empty value means there was no attribute by + # that name defined or it had an actual empty value. echo "${___gtfl_attrib}" @@ -215,6 +232,8 @@ wrapper_get_timestamp_from_ldap() { ___wgtfl_krb5cc_tmpfile="${7}" ts_filetime="$( get_attrib_from_ldap "${___wgtfl_ldapsearch_bin}" "${___wgtfl_ldapsearch_flags}" "${___wgtfl_ldapsearch_filter}" "${___wgtfl_attrib}" "${___wgtfl_ldapconf}" "${___wgtfl_krb5cc_tmpfile}" )" + test "$?" -eq 0 || return 1 + ts_epoch=0 if test -n "$ts_filetime" ; then debuglev 3 && ferror "timestamp(FILETIME): ${ts_filetime}" @@ -637,6 +656,7 @@ test -z "${LAPS_TMPDIR}" && LAPS_TMPDIR="$( mktemp -d /tmp/laps.XXXXXXXXXX )" test -z "${LAPS_KRB5CC_TMPFILE}" && LAPS_KRB5CC_TMPFILE="$( TMPDIR="${LAPS_TMPDIR}" mktemp )" test -z "${LAPS_LDIF_TMPFILE}" && LAPS_LDIF_TMPFILE="$( TMPDIR="${LAPS_TMPDIR}" mktemp )" test -z "${LAPS_LDAPMODIFY_STATUS_TMPFILE}" && LAPS_LDAPMODIFY_STATUS_TMPFILE="$( TMPDIR="${LAPS_TMPDIR}" mktemp )" +test -z "${LAPS_LDAPSEARCH_STATUS_TMPFILE}" && LAPS_LDAPSEARCH_STATUS_TMPFILE="$( TMPDIR="${LAPS_TMPDIR}" mktemp )" test -z "${LAPS_PASSWORD_STATUS_TMPFILE}" && LAPS_PASSWORD_STATUS_TMPFILE="$( TMPDIR="${LAPS_TMPDIR}" mktemp )" define_if_new LAPS_KINIT_HOST_SCRIPT "/usr/share/bgscripts/work/kinit-host.sh" define_if_new LAPS_KINIT_HOST_SCRIPT_DEFAULT "/usr/share/bgscripts/work/kinit-host.sh" -- cgit From c4dbd6d6c318002150c329abcdc663f8aecbf0e4 Mon Sep 17 00:00:00 2001 From: B Stack Date: Fri, 1 Mar 2019 16:38:57 -0500 Subject: fix style, document changes, and bump version fix #5 document the recommended use of "-f" for first run --- laps.spec | 6 ++-- src/usr/share/doc/laps/README.md | 4 +++ src/usr/share/doc/laps/changes | 4 +++ src/usr/share/doc/laps/version.txt | 2 +- src/usr/share/laps/laps.sh | 61 ++++++++++++++++++++------------------ 5 files changed, 44 insertions(+), 33 deletions(-) diff --git a/laps.spec b/laps.spec index da57c47..303a66d 100644 --- a/laps.spec +++ b/laps.spec @@ -1,6 +1,6 @@ %define debug_package %{nil} Name: laps -Version: 0.0.2 +Version: 0.0.3 Release: 1 Summary: local administrator password solution @@ -44,5 +44,5 @@ cp -pr %{name}*/src/* "%{buildroot}" %{_datadir}/%{name} %changelog -* Wed Oct 24 2018 B Stack 0.0.2-1 -- initial rpm built +* Fri Mar 1 2019 B Stack 0.0.3-1 +- rpm built diff --git a/src/usr/share/doc/laps/README.md b/src/usr/share/doc/laps/README.md index 204b97d..7b5c95f 100644 --- a/src/usr/share/doc/laps/README.md +++ b/src/usr/share/doc/laps/README.md @@ -12,6 +12,10 @@ See /etc/laps/laps.conf.example for how to configure the client. The administrator needs to write **/etc/laps/laps.conf** and **/etc/laps/lapsldap.conf**. Copying and modifying the example config files is the recommended way to provide the configs. +For first use, use the -f flag to force the password change so the timestamp is initialized. + + /usr/share/laps/laps.sh -f + # Prepare the domain The OU where the Linux systems are placed in the domain will need some ACLS set up, which are identical to what the LAPS documentation describes. For a brief summary: diff --git a/src/usr/share/doc/laps/changes b/src/usr/share/doc/laps/changes index 9566fdc..51a0c15 100644 --- a/src/usr/share/doc/laps/changes +++ b/src/usr/share/doc/laps/changes @@ -2,3 +2,7 @@ - fix $2 read action should provide date of expiration on -d 1 - fix #3 add readme.md to front directory - fix #4 laps does not recognize expired kerberos tickets + +* Mar 1 2019 B Stack 0.0.3-1 +- fix #5 document the recommended use of "-f" for first run +- merge !1 handle empty password change timestamp LDAP attribute diff --git a/src/usr/share/doc/laps/version.txt b/src/usr/share/doc/laps/version.txt index 4e379d2..bcab45a 100644 --- a/src/usr/share/doc/laps/version.txt +++ b/src/usr/share/doc/laps/version.txt @@ -1 +1 @@ -0.0.2 +0.0.3 diff --git a/src/usr/share/laps/laps.sh b/src/usr/share/laps/laps.sh index 132e2e9..c81e7a4 100755 --- a/src/usr/share/laps/laps.sh +++ b/src/usr/share/laps/laps.sh @@ -6,7 +6,7 @@ # Title: Local Administrator Password Solution for Linux # Purpose: LAPS Equivalent for GNU/Linux # Package: laps -# History: +# History: see upstream project at https://gitlab.com/bgstack15/laps # Usage: # Reference: ftemplate.sh 2018-09-12a; framework.sh 2018-09-12a # Improve: @@ -23,7 +23,7 @@ # sed (sed) # awk (gawk) fiversion="2018-09-12a" -lapsversion="2018-10-24b" +lapsversion="2019-03-01a" usage() { ${PAGER:-/usr/bin/less -F} >&2 <&1 1>/dev/null )" - if test "$?" -ne 0 ; then - if echo "${___gtfl_stderr}" | grep -qiE 'Ticket expired' ; + if test "$?" -ne 0 ; then - ferror "Kerberos ticket expired. Any values from ldap will be garbage." - return 1; - elif echo "${___gtfl_stderr}" | grep -qi -e 'SASL(-1): generic failure: GSSAPI Error: An invalid name was supplied (Success)' ; - then - ferror "GSSAPI Error: Invalid name (Success). Try using \"SASL_NOCANON on\" in lapsldap.conf. Any values from ldap will be garbage." - return 1; - elif echo "${___gtfl_stderr}" | grep -qi -e 'TLS: hostname does not match CN in peer certificate' ; - then - ferror "TLS: hostname does not match CN. Try using \"TLS_REQCERT allow\" in lapsldap.conf. Any values from ldap will be garbage." - return 1; - else - { - echo "other ldap error:" - echo "${___gtfl_stderr}" - } | debuglevoutput 9 - return 1; - fi + if echo "${___gtfl_stderr}" | grep -qiE 'Ticket expired' ; + then + ferror "Fatal: Kerberos ticket expired." + return 1; + elif echo "${___gtfl_stderr}" | grep -qi -e 'SASL(-1): generic failure: GSSAPI Error: An invalid name was supplied (Success)' ; + then + ferror "Fatal: GSSAPI Error: Invalid name (Success). Try using \"SASL_NOCANON on\" in lapsldap.conf." + return 1; + elif echo "${___gtfl_stderr}" | grep -qi -e 'TLS: hostname does not match CN in peer certificate' ; + then + ferror "Fatal: TLS: hostname does not match CN. Try using \"TLS_REQCERT allow\" in lapsldap.conf." + return 1; + else + { + echo "Fatal: other ldap error:" + echo "${___gtfl_stderr}" + } | debuglevoutput 9 + return 1; + fi fi # execute for actually fetching the value @@ -207,9 +208,10 @@ get_attrib_from_ldap() { "${___gtfl_attrib}" 2>/dev/null ; \ echo "$?" > "${LAPS_LDAPSEARCH_STATUS_TMPFILE}" ; \ } | sed -r -e 's/^#.*$//;' -e '/^\s*$/d' | grep -iE -e "^${___gtfl_attrib}:" | awk '{print $2}' )" - ___gtfl_ldap_success="$( cat "${LAPS_LDAPSEARCH_STATUS_TMPFILE}" )" - if test "$___gtfl_ldap_success" -ne 0 ; then - ferror "LDAP lookup failed" + ___gtfl_ldap_success="$( { cat "${LAPS_LDAPSEARCH_STATUS_TMPFILE}" 2>/dev/null ; echo "1" ; } | head -n1 )" + if test "${___gtfl_ldap_success}" != "0" ; + then + ferror "Fatal: LDAP lookup failed" return 1 fi @@ -235,7 +237,8 @@ wrapper_get_timestamp_from_ldap() { test "$?" -eq 0 || return 1 ts_epoch=0 - if test -n "$ts_filetime" ; then + if test -n "$ts_filetime" ; + then debuglev 3 && ferror "timestamp(FILETIME): ${ts_filetime}" ts_epoch="$( "${___wgtfl_datetime_py}" -e "${ts_filetime}" )" fi @@ -395,7 +398,7 @@ wrapper_change_password() { then echo "0" > "${LAPS_PASSWORD_STATUS_TMPFILE}" else - ___wcp_stdout="$( { echo "${___wcp_phrase}" ; echo "${___wcp_phrase}" ; } | "${___wcp_passwd_bin}" "${___wcp_user}" ; echo "$?" > "${LAPS_PASSWORD_STATUS_TMPFILE}" )" + ___wcp_stdout="$( printf "%s\n%s\n" "${___wcp_phrase}" "${___wcp_phrase}" | "${___wcp_passwd_bin}" "${___wcp_user}" ; echo "$?" > "${LAPS_PASSWORD_STATUS_TMPFILE}" )" fi ___wcp_passwd_result="$( cat "${LAPS_PASSWORD_STATUS_TMPFILE}" )" @@ -405,7 +408,7 @@ wrapper_change_password() { debuglev 4 && ferror "${___wcp_stdout}" ;; *) - # successful operation + # failed operation ferror "${scriptfile}: 8 fatal! Unable to change password for ${___wcp_user}:\n${___wcp_stdout}" exit 8 ;; @@ -526,8 +529,8 @@ clean_laps() { # Delayed cleanup if test -z "${LAPS_NO_CLEAN}" ; then - nohup /bin/bash </dev/null 2>&1 & -sleep "${LAPS_CLEANUP_SEC:-300}" ; /bin/rm -r "${LAPS_TMPDIR:-NOTHINGTODELETE}" 1>/dev/null 2>&1 ; + nohup /bin/sh </dev/null 2>&1 & +sleep "${LAPS_CLEANUP_SEC:-3}" ; /bin/rm -r "${LAPS_TMPDIR:-NOTHINGTODELETE}" 1>/dev/null 2>&1 ; EOF fi } -- cgit