How can I use Expect to automate an AWS job? [closed] - bash

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed last year.
Improve this question
I have a very large copy to my local machine from an S3 bucket, which iterates through a file of identifiers and copies matching files. It all works well… except that the copy is so large that it keeps timing out and asking me for my password (every 2 - 3 hours). All told, it took a fortnight to run last time I ran it - but would have been much faster if I'd entered the password immediately that I was requested for it (which was never going to happen because of meals / sleep / other).
There are two scripts. One does the actual work (the core script) and one does the reading of the file of identifiers and calls the core script.
I'd like to automate this task, but I'm having trouble working out how to exactly.
This is the core of the working (but slow, because it asks for the password) script…
#!/bin/bash
function errorexit() {
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
exit 1
}
​
FILEID=$(echo $1)
​
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
​
aws-adfs login --profile=default --adfs-host=myhost.com --role-arn=myroledeets --region=eu-west-1 && export AWS_PROFILE=default
if [ $? -ne 0 ]; then
exit 1
fi
​
ASSUMED_ROLE=$(aws sts assume-role --role-arn myroledeets --role-session-name seshname)
export AWS_SECRET_ACCESS_KEY=$(echo $ASSUMED_ROLE | jq -r .Credentials.SecretAccessKey)
export AWS_ACCESS_KEY_ID=$(echo $ASSUMED_ROLE | jq -r .Credentials.AccessKeyId)
export AWS_SESSION_TOKEN=$(echo $ASSUMED_ROLE | jq -r .Credentials.SessionToken)
​
if [ ${#AWS_ACCESS_KEY_ID} -lt 16 ] || [ ${#AWS_ACCESS_KEY_ID} -gt 128 ]; then
echo "Access Key Failure" >&2
errorexit
fi
​
if [ ${#AWS_SECRET_ACCESS_KEY} -lt 16 ] || [ ${#AWS_SECRET_ACCESS_KEY} -gt 128 ]; then
echo "Secret Access Key Failure" >&2
errorexit
fi
​
if [ ${#AWS_SESSION_TOKEN} -lt 128 ]; then
echo "Session Token Failure" >&2
errorexit
fi
​
AWSPATH="s3://mypath/$FILEID"
aws s3 ls $AWSPATH | cut -c 32- | while IFS= read -r line; do
COPYPATH="s3://mypath/$FILEID/$line"
echo $COPYPATH
aws s3 cp $COPYPATH ./$FILEID/$line --recursive
done
if [ $? -ne 0 ]; then
errorexit
fi
​
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
​
exit 0
I thought it would be a good idea to use Expect to sort this issue. So I did the following (this doesn't work!):
#!/bin/bash
function errorexit() {
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
exit 1
}
​
ACCESSPW=$(echo $1)
FILEID=$(echo $2)
​
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
​
expect <<EOD
spawn aws-adfs login --profile=default --adfs-host=myhost.com --role-arn=myroledeets --region=eu-west-1
set timeout 5
expect -r {
"Password: $" {
send "$ACCESSPW\r"
}
}
EOD
​
if [ $? -ne 0 ]; then
exit 1
fi
​
export AWS_PROFILE=default
ASSUMED_ROLE=$(aws sts assume-role --role-arn myroledeets --role-session-name seshname)
export AWS_SECRET_ACCESS_KEY=$(echo $ASSUMED_ROLE | jq -r .Credentials.SecretAccessKey)
export AWS_ACCESS_KEY_ID=$(echo $ASSUMED_ROLE | jq -r .Credentials.AccessKeyId)
export AWS_SESSION_TOKEN=$(echo $ASSUMED_ROLE | jq -r .Credentials.SessionToken)
​
if [ ${#AWS_ACCESS_KEY_ID} -lt 16 ] || [ ${#AWS_ACCESS_KEY_ID} -gt 128 ]; then
echo "Access Key Failure" >&2
errorexit
fi
​
if [ ${#AWS_SECRET_ACCESS_KEY} -lt 16 ] || [ ${#AWS_SECRET_ACCESS_KEY} -gt 128 ]; then
echo "Secret Access Key Failure" >&2
errorexit
fi
​
if [ ${#AWS_SESSION_TOKEN} -lt 128 ]; then
echo "Session Token Failure" >&2
errorexit
fi
​
AWSPATH="s3://mypath/$FILEID"
aws s3 ls $AWSPATH | cut -c 32- | while IFS= read -r line; do
COPYPATH="s3://mypath/$FILEID/$line"
echo $COPYPATH
aws s3 cp $COPYPATH ./$FILEID/$line --recursive
done
if [ $? -ne 0 ]; then
errorexit
fi
​
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
​
exit 0
So what happens? Well, when I try to run this Expect automated version I get…
./awscopy.sh <password> <file>
spawn aws-adfs login --profile=default --adfs-host=myhost.com --role-arn=myroledeets --region=eu-west-1
2022-02-14 11:09:29,372 [authenticator authenticator.py:authenticate] [43954-MainProcess] [6536770074-MainThread] - ERROR: Cannot extract saml assertion. Re-authentication needed?
Password:
An error occurred (ExpiredToken) when calling the AssumeRole operation: The security token included in the request is expired
Access Key Failure
As you may have guessed, this is the first time I've tried to use Expect. What have I done wrong?

Related

How to add predefined users to a specific group in linux/bash script

I have been trying to implement a code that makes a predefined user created, be put into a specific groups (first 5 in MyMembers, next 5 in MyGroup, and last 5 to MyMinions), but I always got lost in coding it.
So far this is my code in creating predefined user.
#!/bin/bash
#This script adds a user with a hidden password to your #system.
ans=yes
while [[ "$ans" = yes ]] ;
do
if [ $(id -u) -eq 0 ];
then
read -p "Enter username: " username
read -s -p "Enter password: " password
egrep "^$username" /etc/passwd >/dev/null
if [ $? -eq 0 ];
then
echo "$username already exists!"
exit 1
else
pass=$(perl -e 'print crypt ($ARGV[0], "password")' $password)
useradd -m -p $pass $username
[ $? -eq 0 ] && echo -e "\nUser has been added to your system!" || echo "\nFailed to add the user!"
fi
else
echo "Only root may add a user to the system"
exit 2
fi
echo -e "\nDo you still want to add more users?. \nType yes to continue adding. \nType yes or any key to exit"
read ans
done
exit

How to wait in bash script until AWS instance creation is complete

I'm using a bash script to create an AWS instance via CLI and a cloudformation template. I want my script to wait until the instance creation is complete before I move on in my script. Right now, I'm using a while loop to "describe-stacks" every 5 seconds, and breaking out of the loop when the status = "CREATE_COMPLETE" or some failure status. Does anyone know of a more elegant way to do this?
stackStatus="CREATE_IN_PROGRESS"
while [[ 1 ]]; do
echo "${AWS_CLI_PATH}" cloudformation describe-stacks --region "${CfnStackRegion}" --stack-name "${CfnStackName}"
response=$("${AWS_CLI_PATH}" cloudformation describe-stacks --region "${CfnStackRegion}" --stack-name "${CfnStackName}" 2>&1)
responseOrig="$response"
response=$(echo "$response" | tr '\n' ' ' | tr -s " " | sed -e 's/^ *//' -e 's/ *$//')
if [[ "$response" != *"StackStatus"* ]]
then
echo "Error occurred creating AWS CloudFormation stack. Error:"
echo " $responseOrig"
exit -1
fi
stackStatus=$(echo $response | sed -e 's/^.*"StackStatus"[ ]*:[ ]*"//' -e 's/".*//')
echo " StackStatus: $stackStatus"
if [[ "$stackStatus" == "ROLLBACK_IN_PROGRESS" ]] || [[ "$stackStatus" == "ROLLBACK_COMPLETE" ]] || [[ "$stackStatus" == "DELETE_IN_PROGRESS" ]] || [[ "$stackStatus" == "DELETE_COMPLETE" ]]; then
echo "Error occurred creating AWS CloudFormation stack and returned status code ROLLBACK_IN_PROGRESS. Details:"
echo "$responseOrig"
exit -1
elif [[ "$stackStatus" == "CREATE_COMPLETE" ]]; then
break
fi
# Sleep for 5 seconds, if stack creation in progress
sleep 5
done
The aws cli provides a wait subcommand for most of the commands that create resources. For your scenario, you can use the wait subcommand to wait for the stack-create-complete event:
aws cloudformation wait stack-create-complete --stack-name myStackName
That is how I did it. After start of instance I wait for public IP:
INSTANCE_ID="$(aws ec2 run-instances --cli-input-json "${LAUNCH_SPEC}" | jq -r '.Instances[0].InstanceId')"
echo "Instance id ${INSTANCE_ID}"
while true; do
PUBLIC_IP="$(aws ec2 describe-instances --instance-ids ${INSTANCE_ID} | jq -r '.Reservations[0].Instances[0].PublicIpAddress')"
if [[ "${PUBLIC_IP}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then break; fi
sleep 1
echo -n '.'
done
LAUNCH_SPEC defined previously
Ec2-wait-instance-exists seems like what you need:
http://docs.aws.amazon.com/cli/latest/reference/ec2/wait/instance-exists.html
The below is a general "check_status" function. Useful for multiple action such as checking whether stack has been deployed or an EKS cluster is up and whether nodes were added to it.
check_status() {
max_tries="$1"
test_command="$2"
required_value="$3"
error="$4"
ok="false"
for i in `seq 1 $max_tries`; do
return_value=$(eval ${test_command})
if [ "$return_value" == "$required_value" ]; then
ok="true"
break
else
echo -n "."
fi
sleep 5
done
if [ "$ok" == "false" ]; then
printf "\n\e[31mERROR:\e[0m $error\n"
exit 1
fi
}
check_vpc() {
echo "Waiting for stack status..."
vpc_stack_status="aws cloudformation describe-stacks --stack-
name=${vpc_stack_name} --query 'Stacks[0].StackStatus' --output text"
msg="Stack creation failed - giving up"
check_status "100" "$vpc_stack_status" "CREATE_COMPLETE" "$msg"
[[ $? == "0" ]] && echo "VPC stack deployed successfully"
}

Cannot install NPM using curl

Receiving this error when attempting to install NPM. Any help on this error is greatly appreciated.
$ curl http://npmjs.org/install.sh | sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 85 0 85 0 0 2226 0 --:--:-- --:--:-- --:--:-- 4473
sh: line 1: syntax error near unexpected token `newline'
sh: line 1: `<html>Moved: https://npmjs.org/install.sh'
Changed the command from http://... --> https:// and still no luck.
Other info:
OS: Mac OSX 10.8.2
CURL: curl 7.24.0 (x86_64-apple-darwin12.0)
So I attempted the install as a superuser and was promptly met with this mountain of text. I'm completely lost here.
$ sudo curl https://npmjs.org/install.sh | sh
Password:
#!/bin/sh
# A word about this shell script:
#
# It must work everywhere, including on systems that lack
# a /bin/bash, map 'sh' to ksh, ksh97, bash, ash, or zsh,
# and potentially have either a posix shell or bourne
# shell living at /bin/sh.
#
# See this helpful document on writing portable shell scripts:
# http://www.gnu.org/s/hello/manual/autoconf/Portable-Shell.html
#
# The only shell it won't ever work on is cmd.exe.
if [ "x$0" = "xsh" ]; then
# run as curl | sh
# on some systems, you can just do cat>npm-install.sh
# which is a bit cuter. But on others, &1 is already closed,
# so catting to another script file won't do anything.
curl -s https://npmjs.org/install.sh > npm-install-$$.sh
sh npm-install-$$.sh
ret=$?
rm npm-install-$$.sh
exit $ret
fi
# See what "npm_config_*" things there are in the env,
# and make them permanent.
# If this fails, it's not such a big deal.
configures="`env | grep 'npm_config_' | sed -e 's|^npm_config_||g'`"
npm_config_loglevel="error"
if [ "x$npm_debug" = "x" ]; then
(exit 0)
else
echo "Running in debug mode."
echo "Note that this requires bash or zsh."
set -o xtrace
set -o pipefail
npm_config_loglevel="verbose"
fi
export npm_config_loglevel
# make sure that node exists
node=`which node 2>&1`
ret=$?
if [ $ret -eq 0 ] && [ -x "$node" ]; then
(exit 0)
else
echo "npm cannot be installed without nodejs." >&2
echo "Install node first, and then try again." >&2
echo "" >&2
echo "Maybe node is installed, but not in the PATH?" >&2
echo "Note that running as sudo can change envs." >&2
echo ""
echo "PATH=$PATH" >&2
exit $ret
fi
# set the temp dir
TMP="${TMPDIR}"
if [ "x$TMP" = "x" ]; then
TMP="/tmp"
fi
TMP="${TMP}/npm.$$"
rm -rf "$TMP" || true
mkdir "$TMP"
if [ $? -ne 0 ]; then
echo "failed to mkdir $TMP" >&2
exit 1
fi
BACK="$PWD"
ret=0
tar="${TAR}"
if [ -z "$tar" ]; then
tar="${npm_config_tar}"
fi
if [ -z "$tar" ]; then
tar=`which tar 2>&1`
ret=$?
fi
if [ $ret -eq 0 ] && [ -x "$tar" ]; then
echo "tar=$tar"
echo "version:"
$tar --version
ret=$?
fi
if [ $ret -eq 0 ]; then
(exit 0)
else
echo "No suitable tar program found."
exit 1
fi
# Try to find a suitable make
# If the MAKE environment var is set, use that.
# otherwise, try to find gmake, and then make.
# If no make is found, then just execute the necessary commands.
# XXX For some reason, make is building all the docs every time. This
# is an annoying source of bugs. Figure out why this happens.
MAKE=NOMAKE
if [ "x$MAKE" = "x" ]; then
make=`which gmake 2>&1`
if [ $? -eq 0 ] && [ -x $make ]; then
(exit 0)
else
make=`which make 2>&1`
if [ $? -eq 0 ] && [ -x $make ]; then
(exit 0)
else
make=NOMAKE
fi
fi
else
make="$MAKE"
fi
if [ -x "$make" ]; then
(exit 0)
else
# echo "Installing without make. This may fail." >&2
make=NOMAKE
fi
# If there's no bash, then don't even try to clean
if [ -x "/bin/bash" ]; then
(exit 0)
else
clean="no"
fi
node_version=`"$node" --version 2>&1`
ret=$?
if [ $ret -ne 0 ]; then
echo "You need node to run this program." >&2
echo "node --version reports: $node_version" >&2
echo "with exit code = $ret" >&2
echo "Please install node before continuing." >&2
exit $ret
fi
t="${npm_install}"
if [ -z "$t" ]; then
# switch based on node version.
# note that we can only use strict sh-compatible patterns here.
case $node_version in
0.[0123].* | v0.[0123].*)
echo "You are using an outdated and unsupported version of" >&2
echo "node ($node_version). Please update node and try again." >&2
exit 99
;;
v0.[45].* | 0.[45].*)
echo "install npm#1.0"
t=1.0
;;
v0.[678].* | 0.[678].*)
echo "install npm#1.1"
t=1.1
;;
*)
echo "install npm#latest"
t="latest"
;;
esac
fi
# the npmca cert
cacert='
-----BEGIN CERTIFICATE-----
MIIChzCCAfACCQDauvz/KHp8ejANBgkqhkiG9w0BAQUFADCBhzELMAkGA1UEBhMC
VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMQwwCgYDVQQKEwNucG0x
IjAgBgNVBAsTGW5wbSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxDjAMBgNVBAMTBW5w
bUNBMRcwFQYJKoZIhvcNAQkBFghpQGl6cy5tZTAeFw0xMTA5MDUwMTQ3MTdaFw0y
MTA5MDIwMTQ3MTdaMIGHMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEDAOBgNV
BAcTB09ha2xhbmQxDDAKBgNVBAoTA25wbTEiMCAGA1UECxMZbnBtIENlcnRpZmlj
YXRlIEF1dGhvcml0eTEOMAwGA1UEAxMFbnBtQ0ExFzAVBgkqhkiG9w0BCQEWCGlA
aXpzLm1lMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLI4tIqPpRW+ACw9GE
OgBlJZwK5f8nnKCLK629Pv5yJpQKs3DENExAyOgDcyaF0HD0zk8zTp+ZsLaNdKOz
Gn2U181KGprGKAXP6DU6ByOJDWmTlY6+Ad1laYT0m64fERSpHw/hjD3D+iX4aMOl
y0HdbT5m1ZGh6SJz3ZqxavhHLQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAC4ySDbC
l7W1WpLmtLGEQ/yuMLUf6Jy/vr+CRp4h+UzL+IQpCv8FfxsYE7dhf/bmWTEupBkv
yNL18lipt2jSvR3v6oAHAReotvdjqhxddpe5Holns6EQd1/xEZ7sB1YhQKJtvUrl
ZNufy1Jf1r0ldEGeA+0ISck7s+xSh9rQD2Op
-----END CERTIFICATE-----
'
echo "$cacert" > "$TMP/cafile.crt"
cacert="$TMP/cafile.crt"
# need to echo "" after, because Posix sed doesn't treat EOF
# as an implied end of line.
url=`(curl -SsL --cacert "$cacert" https://registry.npmjs.org/npm/$t; echo "") \
| sed -e 's/^.*tarball":"//' \
| sed -e 's/".*$//'`
ret=$?
if [ "x$url" = "x" ]; then
ret=125
# try without the -e arg to sed.
url=`(curl -SsL --cacert "$cacert" https://registry.npmjs.org/npm/$t; echo "") \
| sed 's/^.*tarball":"//' \
| sed 's/".*$//'`
ret=$?
if [ "x$url" = "x" ]; then
ret=125
fi
fi
if [ $ret -ne 0 ]; then
echo "Failed to get tarball url for npm/$t" >&2
exit $ret
fi
echo "fetching: $url" >&2
cd "$TMP" \
&& curl -SsL --cacert "$cacert" "$url" \
| $tar -xzf - \
&& rm "$cacert" \
&& cd "$TMP"/* \
&& (req=`"$node" bin/read-package-json.js package.json engines.node`
if [ -d node_modules ]; then
"$node" node_modules/semver/bin/semver -v "$node_version" -r "$req"
ret=$?
else
"$node" bin/semver.js -v "$node_version" -r "$req"
ret=$?
fi
if [ $ret -ne 0 ]; then
echo "You need node $req to run this program." >&2
echo "node --version reports: $node_version" >&2
echo "Please upgrade node before continuing." >&2
exit $ret
fi) \
&& (ver=`"$node" bin/read-package-json.js package.json version`
isnpm10=0
if [ $ret -eq 0 ]; then
req=`"$node" bin/read-package-json.js package.json engines.node`
if [ -d node_modules ]; then
if "$node" node_modules/semver/bin/semver -v "$ver" -r "1"
then
isnpm10=1
fi
else
if "$node" bin/semver -v "$ver" -r ">=1.0"; then
isnpm10=1
fi
fi
fi
ret=0
if [ $isnpm10 -eq 1 ] && [ -f "scripts/clean-old.sh" ]; then
if [ "x$skipclean" = "x" ]; then
(exit 0)
else
clean=no
fi
if [ "x$clean" = "xno" ] \
|| [ "x$clean" = "xn" ]; then
echo "Skipping 0.x cruft clean" >&2
ret=0
elif [ "x$clean" = "xy" ] || [ "x$clean" = "xyes" ]; then
NODE="$node" /bin/bash "scripts/clean-old.sh" "-y"
ret=$?
else
NODE="$node" /bin/bash "scripts/clean-old.sh" </dev/tty
ret=$?
fi
fi
if [ $ret -ne 0 ]; then
echo "Aborted 0.x cleanup. Exiting." >&2
exit $ret
fi) \
&& (if [ "x$configures" = "x" ]; then
(exit 0)
else
echo "./configure "$configures
echo "$configures" > npmrc
fi) \
&& (if [ "$make" = "NOMAKE" ]; then
(exit 0)
elif "$make" uninstall install; then
(exit 0)
else
make="NOMAKE"
fi
if [ "$make" = "NOMAKE" ]; then
"$node" cli.js rm npm -gf
"$node" cli.js install -gf
fi) \
&& cd "$BACK" \
&& rm -rf "$TMP" \
&& echo "It worked"
ret=$?
if [ $ret -ne 0 ]; then
echo "It failed" >&2
fi
exit $ret
You are getting the install.sh script, you just have to execute it. Do this
curl -O https://npmjs.org/install.sh
sudo sh install.sh
Update
If you get a 301 Moved Permanently file instead, try adding -L option to follow the redirect like this:
curl -O -L https://npmjs.org/install.sh
The original command would work if you use the current location (and not the old one) (at least on my system, same settings).
Meaning, use:
curl https://npmjs.org/install.sh | sh
instead of:
curl http://npmjs.org/install.sh | sh
I have tried many times with various curl options. But the main problem is npm site's https certificate verification. Finally this has worked for me:
curl -k -O -L https://npmjs.org/install.sh
Hope it will help somebody else.
This is still broken somehow? Trying following:
$curl https://npmjs.org/install.sh | sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 93 0 93 0 0 78 0 --:--:-- 0:00:01 --:--:-- 79
sh: line 1: syntax error near unexpected token `newline'
sh: line 1: `<html>Moved: https://www.npmjs.org/install.sh'
and with (https) www.npmjs.org :
$curl https://www.npmjs.org/install.sh | sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 6707 100 6707 0 0 6446 0 0:00:01 0:00:01 --:--:-- 6442
npm-install-606.sh: line 1: syntax error near unexpected token `newline'
npm-install-606.sh: line 1: `<html>Moved: https://www.npmjs.org/install.sh'
Using OS X Maverics 10.9.1
$uname -a
Darwin Esan-iMac.local 13.0.0 Darwin Kernel Version 13.0.0: Thu Sep 19 22:22:27 PDT 2013; root:xnu-2422.1.72~6/RELEASE_X86_64 x86_64
$curl --version
curl 7.30.0 (x86_64-apple-darwin13.0) libcurl/7.30.0 SecureTransport zlib/1.2.5
Should it work with similar env; someone tested???
Am I missing something. Remembering that it is discouraged to run with sudo?
Not yet tried separate download and 'sh'-command.
Edit: yes, separate download curl -O https://www.npmjs.org/install.sh and sh install.sh works fine, after changing owner for /usr/local/lib/node_modules/npm because previously installed with sudo. But it has nothing to do with error message, or can it?
Adding -L helped me. For example:
curl -L http://www.npmjs.org/install.sh | sh
In addition to this url: https://stackoverflow.com/a/15508817/1979882
In my case, for Debian OS it is important to create a symbol link:
ln -s /usr/bin/nodejs /usr/bin/node
More detailed installation workflow is here.
If you have already install nvm, all you have to do is run nvm install version
For example if you are willing to install version 8.0.0, then you run
->$ nvm install 8.0.0
then
->$ nvm use 8.0.0
and you can run all your npm commands....

bash: redirectly subshell into read

A little history behind this - I'm trying to write a nagios plugin to detect if an nfs mount is unmounted and if a mount is stale, which is where I'm running into a problem.
What I'm trying to achieve is detecting if a mount is stale. The problem I'm trying to work around is the fact that a stale nfs handle causes any action on that directory to hang and timeout after 3-4 minutes. By forcing a timeout onto a stat command inside an nfs mounted directory with read, I should be able to work around that problem.
So I picked up this snippet somewhere, which works perfectly when run manually from the cli on an nfs client (where /www/logs/foo is a stale nfs mount)
$ read -t 2 < <(stat -t /www/logs/foo/*); echo $?
1
The problem comes when I try to incorporate this snippet into a script like so (snippet attached, full script attached at the end):
list_of_mounts=$(grep nfs /etc/fstab | grep -v ^# | awk '{print $2'} | xargs)
exitstatus $LINENO
for X in $list_of_mounts; do
AM_I_EXCLUDED=`echo " $* " | grep " $X " -q; echo $?`
if [ "$AM_I_EXCLUDED" -eq "0" ]; then
echo "" >> /dev/null
#check to see if mount is mounted according to /proc/mounts
elif [ ! `grep --quiet "$X " /proc/mounts; echo $?` -eq 0 ]; then
#mount is not mounted at all, add to list to remount
remount_list=`echo $remount_list $X`;
#now make sure its not stale
elif [ ! "`read -t 2 < <(stat -t $X/*) ; echo $?`" -eq "0" ]; then
stalemount_list=`echo $stalemount_list $X`
fi
Gives me this error:
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: command substitution: line 46: syntax error near unexpected token `<'
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: command substitution: line 46: `read -t 2 < <( '
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: command substitution: line 46: syntax error near unexpected token `)'
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: command substitution: line 46: ` ) ; echo $?'
/usr/lib64/nagios/plugins/check_nfs_mounts.sh: line 46: [: stat -t /www/logs/foo/*: integer expression expected
I was able to work around the syntax error by using " read -t 2<<< $(stat -t $X/)" instead of " read -t 2< <(stat -t $X/)", however stat no longer benefits from the timeout on read, which takes me back to the original problem.
While I'm open to new solutions, I'm also curious as to what behavior might be causing this shell vs script difference.
Full nagios check:
#!/bin/bash
usage() {
echo "
Usage:
check_nfs_mounts.sh
It just works.
Optional: include an argument to exclude that mount point
"
}
ok() {
echo "OK - $*"; exit 0
exit
}
warning() {
echo "WARNING - $*"; exit 1
exit
}
critical() {
echo "CRITICAL - $*"; exit 2
exit
}
unknown() {
echo "UNKNOWN - $*"; exit 3
exit
}
exitstatus() {
if [ ! "$?" -eq "0" ] ;
then unknown "Plugin failure - exit code not OK - error line $*"
fi
}
# Get Mounts
list_of_mounts=$(grep nfs /etc/fstab | grep -v ^# | awk '{print $2'} | xargs)
exitstatus $LINENO
for X in $list_of_mounts; do
AM_I_EXCLUDED=`echo " $* " | grep " $X " -q; echo $?`
if [ "$AM_I_EXCLUDED" -eq "0" ]; then
echo "" >> /dev/null
#check to see if mount is mounted according to /proc/mounts
elif [ ! `grep --quiet "$X " /proc/mounts; echo $?` -eq 0 ]; then
#mount is not mounted at all, add to list to remount
remount_list=`echo $remount_list $X`;
#now make sure its not stale
elif [ ! "`read -t 2 <<< $(stat -t $X/*) ; echo $?`" -eq "0" ]; then
stalemount_list=`echo $stalemount_list $X`
fi
done
#Make sure result is a number
if [ -n "$remount_list" ] && [ -n "$stalemount_list" ]; then
critical "Not mounted: $remount_list , Stale mounts: $stalemount_list"
elif [ -n "$remount_list" ] && [ -z "$stalemount_list"]; then
critical "Not mounted: $remount_list"
elif [ -n "$stalemount_list" ] && [ -n "$remount_list" ]; then
critical "Stale mount: $stalemount_list"
elif [ -z "$stalemount_list" ] && [ -z "$remount_list" ]; then
ok "All mounts mounted"
fi
You need to make sure your shebang specifies Bash:
#!/bin/bash
The reason for the error message is that on your system, Bash is symlinked to /bin/sh which is used when there's no shebang or when it's #!/bin/sh.
In this case, Bash is run as if you had started it with bash --posix which disables some non-POSIX features such as process substitution (<()), but confusingly not others such as here strings (<<<).
Change your shebang and you should be OK.
You can save the output of a subshell in this way:
$ read a < <(echo one)
$ echo $a
one
Or in this way (if you just want to process $a and forget it:
$ ( echo one; echo two) | (read a; echo $a)
one
The first variant will work only in bash. Bourne Shell (/bin/sh) does not support this syntax. May be that is the reason why you get the error message. May be you script is interpreted by /bin/sh not by /bin/bash

How to get a password from a shell script without echoing

I have a script that automates a process that needs access to a password protected system. The system is accessed via a command-line program that accepts the user password as an argument.
I would like to prompt the user to type in their password, assign it to a shell variable, and then use that variable to construct the command line of the accessing program (which will of course produce stream output that I will process).
I am a reasonably competent shell programmer in Bourne/Bash, but I don't know how to accept the user input without having it echo to the terminal (or maybe having it echoed using '*' characters).
Can anyone help with this?
Here is another way to do it:
#!/bin/bash
# Read Password
echo -n Password:
read -s password
echo
# Run Command
echo $password
The read -s will turn off echo for you. Just replace the echo on the last line with the command you want to run.
In some shells (e.g. bash) read supports -p prompt-string which will allow the echo and read commands to be combined.
read -s -p "Password: " password
A POSIX compliant answer. Notice the use of /bin/sh instead of /bin/bash. (It does work with bash, but it does not require bash.)
#!/bin/sh
stty -echo
printf "Password: "
read PASSWORD
stty echo
printf "\n"
One liner:
read -s -p "Password: " password
Under Linux (and cygwin) this form works in bash and sh. It may not be standard Unix sh, though.
For more info and options, in bash, type "help read".
$ help read
read: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
Read a line from the standard input and split it into fields.
...
-p prompt output the string PROMPT without a trailing newline before
attempting to read
...
-s do not echo input coming from a terminal
The -s option of read is not defined in the POSIX standard. See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html. I wanted something that would work for any POSIX shell, so I wrote a little function that uses stty to disable echo.
#!/bin/sh
# Read secret string
read_secret()
{
# Disable echo.
stty -echo
# Set up trap to ensure echo is enabled before exiting if the script
# is terminated while echo is disabled.
trap 'stty echo' EXIT
# Read secret.
read "$#"
# Enable echo.
stty echo
trap - EXIT
# Print a newline because the newline entered by the user after
# entering the passcode is not echoed. This ensures that the
# next line of output begins at a new line.
echo
}
This function behaves quite similar to the read command. Here is a simple usage of read followed by similar usage of read_secret. The input to read_secret appears empty because it was not echoed to the terminal.
[susam#cube ~]$ read a b c
foo \bar baz \qux
[susam#cube ~]$ echo a=$a b=$b c=$c
a=foo b=bar c=baz qux
[susam#cube ~]$ unset a b c
[susam#cube ~]$ read_secret a b c
[susam#cube ~]$ echo a=$a b=$b c=$c
a=foo b=bar c=baz qux
[susam#cube ~]$ unset a b c
Here is another that uses the -r option to preserve the backslashes in the input. This works because the read_secret function defined above passes all arguments it receives to the read command.
[susam#cube ~]$ read -r a b c
foo \bar baz \qux
[susam#cube ~]$ echo a=$a b=$b c=$c
a=foo b=\bar c=baz \qux
[susam#cube ~]$ unset a b c
[susam#cube ~]$ read_secret -r a b c
[susam#cube ~]$ echo a=$a b=$b c=$c
a=foo b=\bar c=baz \qux
[susam#cube ~]$ unset a b c
Finally, here is an example that shows how to use the read_secret function to read a password in a POSIX compliant manner.
printf "Password: "
read_secret password
# Do something with $password here ...
I found to be the the askpass command useful
password=$(/lib/cryptsetup/askpass "Give a password")
Every input character is replaced by *. See:
Give a password ****
You can also prompt for a password without setting a variable in the current shell by doing something like this:
$(read -s;echo $REPLY)
For instance:
my-command --set password=$(read -sp "Password: ";echo $REPLY)
You can add several of these prompted values with line break, doing this:
my-command --set user=$(read -sp "`echo $'\n '`User: ";echo $REPLY) --set password=$(read -sp "`echo $'\n '`Password: ";echo $REPLY)
Turn echo off using stty, then back on again after.
While there are plenty of answers already, there is one more way to ask for passwords from terminal in virtually any modern linux that runs systemd (yes, booo, I know, but what can you do about it). systemd-ask-password command is included with standard core systemd package and can be used like this:
#!/bin/sh
PASSWORD="$(systemd-ask-password "Enter your password:")"
Use --emoji=no switch to suppress that stupid unicode lock character. It works great inside terminal but requires tweaking if you need it to pop up a GUI dialog, but that is out of scope here.
This link is help in defining,
* How to read password from use without echo-ing it back to terminal
* How to replace each character with * -character.
https://www.tutorialkart.com/bash-shell-scripting/bash-read-username-and-password/
For anyone needing to prompt for a password, you may be interested in using encpass.sh. This is a script I wrote for similar purposes of capturing a secret at runtime and then encrypting it for subsequent occasions. Subsequent runs do not prompt for the password as it will just use the encrypted value from disk.
It stores the encrypted passwords in a hidden folder under the user's home directory or in a custom folder that you can define through the environment variable ENCPASS_HOME_DIR. It is designed to be POSIX compliant and has an MIT License, so it can be used even in corporate enterprise environments. My company, Plyint LLC, maintains the script and occasionally releases updates. Pull requests are also welcome, if you find an issue. :)
To use it in your scripts simply source encpass.sh in your script and call the get_secret function. I'm including a copy of the script below for easy visibility.
#!/bin/sh
################################################################################
# Copyright (c) 2020 Plyint, LLC <contact#plyint.com>. All Rights Reserved.
# This file is licensed under the MIT License (MIT).
# Please see LICENSE.txt for more information.
#
# DESCRIPTION:
# This script allows a user to encrypt a password (or any other secret) at
# runtime and then use it, decrypted, within a script. This prevents shoulder
# surfing passwords and avoids storing the password in plain text, which could
# inadvertently be sent to or discovered by an individual at a later date.
#
# This script generates an AES 256 bit symmetric key for each script (or user-
# defined bucket) that stores secrets. This key will then be used to encrypt
# all secrets for that script or bucket. encpass.sh sets up a directory
# (.encpass) under the user's home directory where keys and secrets will be
# stored.
#
# For further details, see README.md or run "./encpass ?" from the command line.
#
################################################################################
encpass_checks() {
if [ -n "$ENCPASS_CHECKS" ]; then
return
fi
if [ ! -x "$(command -v openssl)" ]; then
echo "Error: OpenSSL is not installed or not accessible in the current path." \
"Please install it and try again." >&2
exit 1
fi
if [ -z "$ENCPASS_HOME_DIR" ]; then
ENCPASS_HOME_DIR=$(encpass_get_abs_filename ~)/.encpass
fi
if [ ! -d "$ENCPASS_HOME_DIR" ]; then
mkdir -m 700 "$ENCPASS_HOME_DIR"
mkdir -m 700 "$ENCPASS_HOME_DIR/keys"
mkdir -m 700 "$ENCPASS_HOME_DIR/secrets"
fi
if [ "$(basename "$0")" != "encpass.sh" ]; then
encpass_include_init "$1" "$2"
fi
ENCPASS_CHECKS=1
}
# Initializations performed when the script is included by another script
encpass_include_init() {
if [ -n "$1" ] && [ -n "$2" ]; then
ENCPASS_BUCKET=$1
ENCPASS_SECRET_NAME=$2
elif [ -n "$1" ]; then
ENCPASS_BUCKET=$(basename "$0")
ENCPASS_SECRET_NAME=$1
else
ENCPASS_BUCKET=$(basename "$0")
ENCPASS_SECRET_NAME="password"
fi
}
encpass_generate_private_key() {
ENCPASS_KEY_DIR="$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET"
if [ ! -d "$ENCPASS_KEY_DIR" ]; then
mkdir -m 700 "$ENCPASS_KEY_DIR"
fi
if [ ! -f "$ENCPASS_KEY_DIR/private.key" ]; then
(umask 0377 && printf "%s" "$(openssl rand -hex 32)" >"$ENCPASS_KEY_DIR/private.key")
fi
}
encpass_get_private_key_abs_name() {
ENCPASS_PRIVATE_KEY_ABS_NAME="$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET/private.key"
if [ "$1" != "nogenerate" ]; then
if [ ! -f "$ENCPASS_PRIVATE_KEY_ABS_NAME" ]; then
encpass_generate_private_key
fi
fi
}
encpass_get_secret_abs_name() {
ENCPASS_SECRET_ABS_NAME="$ENCPASS_HOME_DIR/secrets/$ENCPASS_BUCKET/$ENCPASS_SECRET_NAME.enc"
if [ "$3" != "nocreate" ]; then
if [ ! -f "$ENCPASS_SECRET_ABS_NAME" ]; then
set_secret "$1" "$2"
fi
fi
}
get_secret() {
encpass_checks "$1" "$2"
encpass_get_private_key_abs_name
encpass_get_secret_abs_name "$1" "$2"
encpass_decrypt_secret
}
set_secret() {
encpass_checks "$1" "$2"
if [ "$3" != "reuse" ] || { [ -z "$ENCPASS_SECRET_INPUT" ] && [ -z "$ENCPASS_CSECRET_INPUT" ]; }; then
echo "Enter $ENCPASS_SECRET_NAME:" >&2
stty -echo
read -r ENCPASS_SECRET_INPUT
stty echo
echo "Confirm $ENCPASS_SECRET_NAME:" >&2
stty -echo
read -r ENCPASS_CSECRET_INPUT
stty echo
fi
if [ "$ENCPASS_SECRET_INPUT" = "$ENCPASS_CSECRET_INPUT" ]; then
encpass_get_private_key_abs_name
ENCPASS_SECRET_DIR="$ENCPASS_HOME_DIR/secrets/$ENCPASS_BUCKET"
if [ ! -d "$ENCPASS_SECRET_DIR" ]; then
mkdir -m 700 "$ENCPASS_SECRET_DIR"
fi
printf "%s" "$(openssl rand -hex 16)" >"$ENCPASS_SECRET_DIR/$ENCPASS_SECRET_NAME.enc"
ENCPASS_OPENSSL_IV="$(cat "$ENCPASS_SECRET_DIR/$ENCPASS_SECRET_NAME.enc")"
echo "$ENCPASS_SECRET_INPUT" | openssl enc -aes-256-cbc -e -a -iv \
"$ENCPASS_OPENSSL_IV" -K \
"$(cat "$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET/private.key")" 1>> \
"$ENCPASS_SECRET_DIR/$ENCPASS_SECRET_NAME.enc"
else
echo "Error: secrets do not match. Please try again." >&2
exit 1
fi
}
encpass_get_abs_filename() {
# $1 : relative filename
filename="$1"
parentdir="$(dirname "${filename}")"
if [ -d "${filename}" ]; then
cd "${filename}" && pwd
elif [ -d "${parentdir}" ]; then
echo "$(cd "${parentdir}" && pwd)/$(basename "${filename}")"
fi
}
encpass_decrypt_secret() {
if [ -f "$ENCPASS_PRIVATE_KEY_ABS_NAME" ]; then
ENCPASS_DECRYPT_RESULT="$(dd if="$ENCPASS_SECRET_ABS_NAME" ibs=1 skip=32 2> /dev/null | openssl enc -aes-256-cbc \
-d -a -iv "$(head -c 32 "$ENCPASS_SECRET_ABS_NAME")" -K "$(cat "$ENCPASS_PRIVATE_KEY_ABS_NAME")" 2> /dev/null)"
if [ ! -z "$ENCPASS_DECRYPT_RESULT" ]; then
echo "$ENCPASS_DECRYPT_RESULT"
else
# If a failed unlock command occurred and the user tries to show the secret
# Present either locked or decrypt command
if [ -f "$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET/private.lock" ]; then
echo "**Locked**"
else
# The locked file wasn't present as expected. Let's display a failure
echo "Error: Failed to decrypt"
fi
fi
elif [ -f "$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET/private.lock" ]; then
echo "**Locked**"
else
echo "Error: Unable to decrypt. The key file \"$ENCPASS_PRIVATE_KEY_ABS_NAME\" is not present."
fi
}
##########################################################
# COMMAND LINE MANAGEMENT SUPPORT
# -------------------------------
# If you don't need to manage the secrets for the scripts
# with encpass.sh you can delete all code below this point
# in order to significantly reduce the size of encpass.sh.
# This is useful if you want to bundle encpass.sh with
# your existing scripts and just need the retrieval
# functions.
##########################################################
encpass_show_secret() {
encpass_checks
ENCPASS_BUCKET=$1
encpass_get_private_key_abs_name "nogenerate"
if [ ! -z "$2" ]; then
ENCPASS_SECRET_NAME=$2
encpass_get_secret_abs_name "$1" "$2" "nocreate"
if [ -z "$ENCPASS_SECRET_ABS_NAME" ]; then
echo "No secret named $2 found for bucket $1."
exit 1
fi
encpass_decrypt_secret
else
ENCPASS_FILE_LIST=$(ls -1 "$ENCPASS_HOME_DIR"/secrets/"$1")
for ENCPASS_F in $ENCPASS_FILE_LIST; do
ENCPASS_SECRET_NAME=$(basename "$ENCPASS_F" .enc)
encpass_get_secret_abs_name "$1" "$ENCPASS_SECRET_NAME" "nocreate"
if [ -z "$ENCPASS_SECRET_ABS_NAME" ]; then
echo "No secret named $ENCPASS_SECRET_NAME found for bucket $1."
exit 1
fi
echo "$ENCPASS_SECRET_NAME = $(encpass_decrypt_secret)"
done
fi
}
encpass_getche() {
old=$(stty -g)
stty raw min 1 time 0
printf '%s' "$(dd bs=1 count=1 2>/dev/null)"
stty "$old"
}
encpass_remove() {
if [ ! -n "$ENCPASS_FORCE_REMOVE" ]; then
if [ ! -z "$ENCPASS_SECRET" ]; then
printf "Are you sure you want to remove the secret \"%s\" from bucket \"%s\"? [y/N]" "$ENCPASS_SECRET" "$ENCPASS_BUCKET"
else
printf "Are you sure you want to remove the bucket \"%s?\" [y/N]" "$ENCPASS_BUCKET"
fi
ENCPASS_CONFIRM="$(encpass_getche)"
printf "\n"
if [ "$ENCPASS_CONFIRM" != "Y" ] && [ "$ENCPASS_CONFIRM" != "y" ]; then
exit 0
fi
fi
if [ ! -z "$ENCPASS_SECRET" ]; then
rm -f "$1"
printf "Secret \"%s\" removed from bucket \"%s\".\n" "$ENCPASS_SECRET" "$ENCPASS_BUCKET"
else
rm -Rf "$ENCPASS_HOME_DIR/keys/$ENCPASS_BUCKET"
rm -Rf "$ENCPASS_HOME_DIR/secrets/$ENCPASS_BUCKET"
printf "Bucket \"%s\" removed.\n" "$ENCPASS_BUCKET"
fi
}
encpass_save_err() {
if read -r x; then
{ printf "%s\n" "$x"; cat; } > "$1"
elif [ "$x" != "" ]; then
printf "%s" "$x" > "$1"
fi
}
encpass_help() {
less << EOF
NAME:
encpass.sh - Use encrypted passwords in shell scripts
DESCRIPTION:
A lightweight solution for using encrypted passwords in shell scripts
using OpenSSL. It allows a user to encrypt a password (or any other secret)
at runtime and then use it, decrypted, within a script. This prevents
shoulder surfing passwords and avoids storing the password in plain text,
within a script, which could inadvertently be sent to or discovered by an
individual at a later date.
This script generates an AES 256 bit symmetric key for each script
(or user-defined bucket) that stores secrets. This key will then be used
to encrypt all secrets for that script or bucket.
Subsequent calls to retrieve a secret will not prompt for a secret to be
entered as the file with the encrypted value already exists.
Note: By default, encpass.sh sets up a directory (.encpass) under the
user's home directory where keys and secrets will be stored. This directory
can be overridden by setting the environment variable ENCPASS_HOME_DIR to a
directory of your choice.
~/.encpass (or the directory specified by ENCPASS_HOME_DIR) will contain
the following subdirectories:
- keys (Holds the private key for each script/bucket)
- secrets (Holds the secrets stored for each script/bucket)
USAGE:
To use the encpass.sh script in an existing shell script, source the script
and then call the get_secret function.
Example:
#!/bin/sh
. encpass.sh
password=\$(get_secret)
When no arguments are passed to the get_secret function,
then the bucket name is set to the name of the script and
the secret name is set to "password".
There are 2 other ways to call get_secret:
Specify the secret name:
Ex: \$(get_secret user)
- bucket name = <script name>
- secret name = "user"
Specify both the secret name and bucket name:
Ex: \$(get_secret personal user)
- bucket name = "personal"
- secret name = "user"
encpass.sh also provides a command line interface to manage the secrets.
To invoke a command, pass it as an argument to encpass.sh from the shell.
$ encpass.sh [COMMAND]
See the COMMANDS section below for a list of available commands. Wildcard
handling is implemented for secret and bucket names. This enables
performing operations like adding/removing a secret to/from multiple buckets
at once.
COMMANDS:
add [-f] <bucket> <secret>
Add a secret to the specified bucket. The bucket will be created
if it does not already exist. If a secret with the same name already
exists for the specified bucket, then the user will be prompted to
confirm overwriting the value. If the -f option is passed, then the
add operation will perform a forceful overwrite of the value. (i.e. no
prompt)
list|ls [<bucket>]
Display the names of the secrets held in the bucket. If no bucket
is specified, then the names of all existing buckets will be
displayed.
lock
Locks all keys used by encpass.sh using a password. The user
will be prompted to enter a password and confirm it. A user
should take care to securely store the password. If the password
is lost then keys can not be unlocked. When keys are locked,
secrets can not be retrieved. (e.g. the output of the values
in the "show" command will be encrypted/garbage)
remove|rm [-f] <bucket> [<secret>]
Remove a secret from the specified bucket. If only a bucket is
specified then the entire bucket (i.e. all secrets and keys) will
be removed. By default the user is asked to confirm the removal of
the secret or the bucket. If the -f option is passed then a
forceful removal will be performed. (i.e. no prompt)
show [<bucket>] [<secret>]
Show the unencrypted value of the secret from the specified bucket.
If no secret is specified then all secrets for the bucket are displayed.
update <bucket> <secret>
Updates a secret in the specified bucket. This command is similar
to using an "add -f" command, but it has a safety check to only
proceed if the specified secret exists. If the secret, does not
already exist, then an error will be reported. There is no forceable
update implemented. Use "add -f" for any required forceable update
scenarios.
unlock
Unlocks all the keys for encpass.sh. The user will be prompted to
enter the password and confirm it.
dir
Prints out the current value of the ENCPASS_HOME_DIR environment variable.
help|--help|usage|--usage|?
Display this help message.
EOF
}
# Subcommands for cli support
case "$1" in
add )
shift
while getopts ":f" ENCPASS_OPTS; do
case "$ENCPASS_OPTS" in
f ) ENCPASS_FORCE_ADD=1;;
esac
done
encpass_checks
if [ -n "$ENCPASS_FORCE_ADD" ]; then
shift $((OPTIND-1))
fi
if [ ! -z "$1" ] && [ ! -z "$2" ]; then
# Allow globbing
# shellcheck disable=SC2027,SC2086
ENCPASS_ADD_LIST="$(ls -1d "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
if [ -z "$ENCPASS_ADD_LIST" ]; then
ENCPASS_ADD_LIST="$1"
fi
for ENCPASS_ADD_F in $ENCPASS_ADD_LIST; do
ENCPASS_ADD_DIR="$(basename "$ENCPASS_ADD_F")"
ENCPASS_BUCKET="$ENCPASS_ADD_DIR"
if [ ! -n "$ENCPASS_FORCE_ADD" ] && [ -f "$ENCPASS_ADD_F/$2.enc" ]; then
echo "Warning: A secret with the name \"$2\" already exists for bucket $ENCPASS_BUCKET."
echo "Would you like to overwrite the value? [y/N]"
ENCPASS_CONFIRM="$(encpass_getche)"
if [ "$ENCPASS_CONFIRM" != "Y" ] && [ "$ENCPASS_CONFIRM" != "y" ]; then
continue
fi
fi
ENCPASS_SECRET_NAME="$2"
echo "Adding secret \"$ENCPASS_SECRET_NAME\" to bucket \"$ENCPASS_BUCKET\"..."
set_secret "$ENCPASS_BUCKET" "$ENCPASS_SECRET_NAME" "reuse"
done
else
echo "Error: A bucket name and secret name must be provided when adding a secret."
exit 1
fi
;;
update )
shift
encpass_checks
if [ ! -z "$1" ] && [ ! -z "$2" ]; then
ENCPASS_SECRET_NAME="$2"
# Allow globbing
# shellcheck disable=SC2027,SC2086
ENCPASS_UPDATE_LIST="$(ls -1d "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
for ENCPASS_UPDATE_F in $ENCPASS_UPDATE_LIST; do
# Allow globbing
# shellcheck disable=SC2027,SC2086
if [ -f "$ENCPASS_UPDATE_F/"$2".enc" ]; then
ENCPASS_UPDATE_DIR="$(basename "$ENCPASS_UPDATE_F")"
ENCPASS_BUCKET="$ENCPASS_UPDATE_DIR"
echo "Updating secret \"$ENCPASS_SECRET_NAME\" to bucket \"$ENCPASS_BUCKET\"..."
set_secret "$ENCPASS_BUCKET" "$ENCPASS_SECRET_NAME" "reuse"
else
echo "Error: A secret with the name \"$2\" does not exist for bucket $1."
exit 1
fi
done
else
echo "Error: A bucket name and secret name must be provided when updating a secret."
exit 1
fi
;;
rm|remove )
shift
encpass_checks
while getopts ":f" ENCPASS_OPTS; do
case "$ENCPASS_OPTS" in
f ) ENCPASS_FORCE_REMOVE=1;;
esac
done
if [ -n "$ENCPASS_FORCE_REMOVE" ]; then
shift $((OPTIND-1))
fi
if [ -z "$1" ]; then
echo "Error: A bucket must be specified for removal."
fi
# Allow globbing
# shellcheck disable=SC2027,SC2086
ENCPASS_REMOVE_BKT_LIST="$(ls -1d "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
if [ ! -z "$ENCPASS_REMOVE_BKT_LIST" ]; then
for ENCPASS_REMOVE_B in $ENCPASS_REMOVE_BKT_LIST; do
ENCPASS_BUCKET="$(basename "$ENCPASS_REMOVE_B")"
if [ ! -z "$2" ]; then
# Removing secrets for a specified bucket
# Allow globbing
# shellcheck disable=SC2027,SC2086
ENCPASS_REMOVE_LIST="$(ls -1p "$ENCPASS_REMOVE_B/"$2".enc" 2>/dev/null)"
if [ -z "$ENCPASS_REMOVE_LIST" ]; then
echo "Error: No secrets found for $2 in bucket $ENCPASS_BUCKET."
exit 1
fi
for ENCPASS_REMOVE_F in $ENCPASS_REMOVE_LIST; do
ENCPASS_SECRET="$2"
encpass_remove "$ENCPASS_REMOVE_F"
done
else
# Removing a specified bucket
encpass_remove
fi
done
else
echo "Error: The bucket named $1 does not exist."
exit 1
fi
;;
show )
shift
encpass_checks
if [ -z "$1" ]; then
ENCPASS_SHOW_DIR="*"
else
ENCPASS_SHOW_DIR=$1
fi
if [ ! -z "$2" ]; then
# Allow globbing
# shellcheck disable=SC2027,SC2086
if [ -f "$(encpass_get_abs_filename "$ENCPASS_HOME_DIR/secrets/$ENCPASS_SHOW_DIR/"$2".enc")" ]; then
encpass_show_secret "$ENCPASS_SHOW_DIR" "$2"
fi
else
# Allow globbing
# shellcheck disable=SC2027,SC2086
ENCPASS_SHOW_LIST="$(ls -1d "$ENCPASS_HOME_DIR/secrets/"$ENCPASS_SHOW_DIR"" 2>/dev/null)"
if [ -z "$ENCPASS_SHOW_LIST" ]; then
if [ "$ENCPASS_SHOW_DIR" = "*" ]; then
echo "Error: No buckets exist."
else
echo "Error: Bucket $1 does not exist."
fi
exit 1
fi
for ENCPASS_SHOW_F in $ENCPASS_SHOW_LIST; do
ENCPASS_SHOW_DIR="$(basename "$ENCPASS_SHOW_F")"
echo "$ENCPASS_SHOW_DIR:"
encpass_show_secret "$ENCPASS_SHOW_DIR"
echo " "
done
fi
;;
ls|list )
shift
encpass_checks
if [ ! -z "$1" ]; then
# Allow globbing
# shellcheck disable=SC2027,SC2086
ENCPASS_FILE_LIST="$(ls -1p "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
if [ -z "$ENCPASS_FILE_LIST" ]; then
# Allow globbing
# shellcheck disable=SC2027,SC2086
ENCPASS_DIR_EXISTS="$(ls -d "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
if [ ! -z "$ENCPASS_DIR_EXISTS" ]; then
echo "Bucket $1 is empty."
else
echo "Error: Bucket $1 does not exist."
fi
exit 1
fi
ENCPASS_NL=""
for ENCPASS_F in $ENCPASS_FILE_LIST; do
if [ -d "${ENCPASS_F%:}" ]; then
printf "$ENCPASS_NL%s\n" "$(basename "$ENCPASS_F")"
ENCPASS_NL="\n"
else
printf "%s\n" "$(basename "$ENCPASS_F" .enc)"
fi
done
else
# Allow globbing
# shellcheck disable=SC2027,SC2086
ENCPASS_BUCKET_LIST="$(ls -1p "$ENCPASS_HOME_DIR/secrets/"$1"" 2>/dev/null)"
for ENCPASS_C in $ENCPASS_BUCKET_LIST; do
if [ -d "${ENCPASS_C%:}" ]; then
printf "\n%s" "\n$(basename "$ENCPASS_C")"
else
basename "$ENCPASS_C" .enc
fi
done
fi
;;
lock )
shift
encpass_checks
echo "************************!!!WARNING!!!*************************" >&2
echo "* You are about to lock your keys with a password. *" >&2
echo "* You will not be able to use your secrets again until you *" >&2
echo "* unlock the keys with the same password. It is important *" >&2
echo "* that you securely store the password, so you can recall it *" >&2
echo "* in the future. If you forget your password you will no *" >&2
echo "* longer be able to access your secrets. *" >&2
echo "************************!!!WARNING!!!*************************" >&2
printf "\n%s\n" "About to lock keys held in directory $ENCPASS_HOME_DIR/keys/"
printf "\nEnter Password to lock keys:" >&2
stty -echo
read -r ENCPASS_KEY_PASS
printf "\nConfirm Password:" >&2
read -r ENCPASS_CKEY_PASS
printf "\n"
stty echo
if [ -z "$ENCPASS_KEY_PASS" ]; then
echo "Error: You must supply a password value."
exit 1
fi
if [ "$ENCPASS_KEY_PASS" = "$ENCPASS_CKEY_PASS" ]; then
ENCPASS_NUM_KEYS_LOCKED=0
ENCPASS_KEYS_LIST="$(ls -1d "$ENCPASS_HOME_DIR/keys/"*"/" 2>/dev/null)"
for ENCPASS_KEY_F in $ENCPASS_KEYS_LIST; do
if [ -d "${ENCPASS_KEY_F%:}" ]; then
ENCPASS_KEY_NAME="$(basename "$ENCPASS_KEY_F")"
ENCPASS_KEY_VALUE=""
if [ -f "$ENCPASS_KEY_F/private.key" ]; then
ENCPASS_KEY_VALUE="$(cat "$ENCPASS_KEY_F/private.key")"
if [ ! -f "$ENCPASS_KEY_F/private.lock" ]; then
echo "Locking key $ENCPASS_KEY_NAME..."
else
echo "Error: The key $ENCPASS_KEY_NAME appears to have been previously locked."
echo " The current key file may hold a bad value. Exiting to avoid encrypting"
echo " a bad value and overwriting the lock file."
exit 1
fi
else
echo "Error: Private key file ${ENCPASS_KEY_F}private.key missing for bucket $ENCPASS_KEY_NAME."
exit 1
fi
if [ ! -z "$ENCPASS_KEY_VALUE" ]; then
openssl enc -aes-256-cbc -pbkdf2 -iter 10000 -salt -in "$ENCPASS_KEY_F/private.key" -out "$ENCPASS_KEY_F/private.lock" -k "$ENCPASS_KEY_PASS"
if [ -f "$ENCPASS_KEY_F/private.key" ] && [ -f "$ENCPASS_KEY_F/private.lock" ]; then
# Both the key and lock file exist. We can remove the key file now
rm -f "$ENCPASS_KEY_F/private.key"
echo "Locked key $ENCPASS_KEY_NAME."
ENCPASS_NUM_KEYS_LOCKED=$(( ENCPASS_NUM_KEYS_LOCKED + 1 ))
else
echo "Error: The key fle and/or lock file were not found as expected for key $ENCPASS_KEY_NAME."
fi
else
echo "Error: No key value found for the $ENCPASS_KEY_NAME key."
exit 1
fi
fi
done
echo "Locked $ENCPASS_NUM_KEYS_LOCKED keys."
else
echo "Error: Passwords do not match."
fi
;;
unlock )
shift
encpass_checks
printf "%s\n" "About to unlock keys held in the $ENCPASS_HOME_DIR/keys/ directory."
printf "\nEnter Password to unlock keys: " >&2
stty -echo
read -r ENCPASS_KEY_PASS
printf "\n"
stty echo
if [ ! -z "$ENCPASS_KEY_PASS" ]; then
ENCPASS_NUM_KEYS_UNLOCKED=0
ENCPASS_KEYS_LIST="$(ls -1d "$ENCPASS_HOME_DIR/keys/"*"/" 2>/dev/null)"
for ENCPASS_KEY_F in $ENCPASS_KEYS_LIST; do
if [ -d "${ENCPASS_KEY_F%:}" ]; then
ENCPASS_KEY_NAME="$(basename "$ENCPASS_KEY_F")"
echo "Unlocking key $ENCPASS_KEY_NAME..."
if [ -f "$ENCPASS_KEY_F/private.key" ] && [ ! -f "$ENCPASS_KEY_F/private.lock" ]; then
echo "Error: Key $ENCPASS_KEY_NAME appears to be unlocked already."
exit 1
fi
if [ -f "$ENCPASS_KEY_F/private.lock" ]; then
# Remove the failed file in case previous decryption attempts were unsuccessful
rm -f "$ENCPASS_KEY_F/failed" 2>/dev/null
# Decrypt key. Log any failure to the "failed" file.
openssl enc -aes-256-cbc -d -pbkdf2 -iter 10000 -salt \
-in "$ENCPASS_KEY_F/private.lock" -out "$ENCPASS_KEY_F/private.key" \
-k "$ENCPASS_KEY_PASS" 2>&1 | encpass_save_err "$ENCPASS_KEY_F/failed"
if [ ! -f "$ENCPASS_KEY_F/failed" ]; then
# No failure has occurred.
if [ -f "$ENCPASS_KEY_F/private.key" ] && [ -f "$ENCPASS_KEY_F/private.lock" ]; then
# Both the key and lock file exist. We can remove the lock file now.
rm -f "$ENCPASS_KEY_F/private.lock"
echo "Unlocked key $ENCPASS_KEY_NAME."
ENCPASS_NUM_KEYS_UNLOCKED=$(( ENCPASS_NUM_KEYS_UNLOCKED + 1 ))
else
echo "Error: The key file and/or lock file were not found as expected for key $ENCPASS_KEY_NAME."
fi
else
printf "Error: Failed to unlock key %s.\n" "$ENCPASS_KEY_NAME"
printf " Please view %sfailed for details.\n" "$ENCPASS_KEY_F"
fi
else
echo "Error: No lock file found for the $ENCPASS_KEY_NAME key."
fi
fi
done
echo "Unlocked $ENCPASS_NUM_KEYS_UNLOCKED keys."
else
echo "No password entered."
fi
;;
dir )
shift
encpass_checks
echo "ENCPASS_HOME_DIR=$ENCPASS_HOME_DIR"
;;
help|--help|usage|--usage|\? )
encpass_checks
encpass_help
;;
* )
if [ ! -z "$1" ]; then
echo "Command not recognized. See \"encpass.sh help\" for a list commands."
exit 1
fi
;;
esac

Resources