Hiding Password in Shell Scripts
Solution 1
First, as several people have already said, keeping the credentials separate from the script is essential. (In addition to increased security, it also means that you can re-use the same script for several systems with different credentials.)
Second, you should consider not only the security of the credentials but also the impact if/when those credentials are compromised. You shouldn't have just one password for all access to the database, you should have different credentials with different levels of access. You could, for instance, have one DB user that has the ability to perform a search in the database - that user should have read-only access. Another user may have permission to insert new records, but not to delete them. A third one may have permission to delete records.
In addition to restricting the permissions for each account, you should also have restriction on where each account can be used from. For instance, the account used by your web server should not be allowed to connect from any other IP address than that of the webserver. An account with full root permissions to the database should be very restricted indeed in terms of where it may connect from and should never be used other than interactively. Also, consider using stored procedures in the database to restrict exactly what can be done by each account.
These restrictions need to be implemented on the DB-server side of the system so that even if the client-side is compromised, the restrictions cannot be altered from it. (And, obviously, the DB server needs to be protected with firewalls etc in addition to the DB configuration...)
In the case of a DB account that is only permitted limited read-only access, and only from a particular IP address, you might not need any further credentials than that, depending on the sensitivity of the data and the security of the host the script is being run from. One example may be a search form on your web site, which can be run with a user that is only allowed to use a stored procedure which extracts only the information that will be presented on the web page. In this case, adding a password does not really confer any extra security, since that information is already meant to be public, and the user can't access any other data that would be more sensitive.
Also, make sure that the connection to the database is made using TLS, or anybody listening on the network can get your credentials.
Third, consider what kind of credentials to use. Passwords are just one form, and not the most secure. You could instead use some form of public/private key pair, or AD/PAM or the like.
Fourth, consider the conditions under which the script will be run:
If it is run interactively, then you should enter the password, or the password to the private key, or the private key, or be logged in with a valid Kerberos ticket, when you run it - in other words, the script should get its credentials directly from you at the time that you run it, instead of reading them from some file.
If it is run from a webserver, consider setting up the credentials at the time when you start the webserver. A good example here is SSL certificates - they have a public certificate and a private key, and the private key has a password. You may store the private key on the web server, but you still need to enter the password to it when you start Apache. You could also have the credentials on some kind of hardware, such as a physical card or an HSM, that can be removed or locked once the server is started. (Of course, the downside to this method is that the server can't restart on its own if something happens. I would prefer this to the risk of having my system compromised, but your mileage may vary...)
If the script is being run from cron, this is the hard part. You don't want to have the credentials lying around anywhere on your system where someone can access them - but you do want to have them lying around so that your script can access them, right? Well, not quite right. Consider exactly what the script is doing. What permissions does it need on the database? Can it be restricted so that it doesn't matter if the wrong person connects with those permissions? Can you instead run the script directly on the DB server that nobody else has access to, instead of from the server that does have other users? If, for some reason that I can't think of, you absolutely must have the script running on an insecure server and it must be able to do something dangerous/destructive... now is a good time to re-think your architecture.
Fifth, if you value the security of your database, you should not be running these scripts on servers that other people have access to. If someone is logged in on your system, then they will have the possibility to get at your credentials. For instance, in the case of a web server with an SSL certificate, there is at least a theoretical possibility of someone being able to gain root and access the httpd process's memory area and extract the credentials. There has been at least one exploit in recent times where this could be done over SSL, not even requiring the attacker to be logged in.
Also, consider using SELinux or AppArmor or whatever is available for your system to restrict which users can do what. They will make it possible for you to disallow users to even try to connect to the database, even if they do manage to gain access to the credentials.
If all this sounds like overkill to you, and you can't afford or don't have the time to do it - then, in my (arrogant and elitist) opinion, you should not be storing anything important or sensitive in your database. And if you're not storing anything important or sensitive, then where you store your credentials is also not important - in which case, why use a password at all?
Lastly, if you absolutely cannot avoid storing some kind of credentials, you could have the credentials read-only and own by root and root could grant ownership on an exceedingly temporary basis when requested to do so by a script (because your script should not be run as root unless absolutely necessary, and connecting to a database does not make it necessary). But it's still not a good idea.
Solution 2
First of all, if there is any way at all you can change things to avoid having to store a password inside or alongside a script in the first place, you should make every effort to do that. Jenny D's answer contains a lot of good advice to that effect.
Otherwise, your idea of placing the password in a separate file with restricted permissions is pretty much it. You can for example source that file from the main script:
. /usr/local/etc/secret-password-here
You could also restrict the permissions of the main script so that only authorized persons can execute it, but it's probably better to do as you suggest and store only the password itself in a restricted file. That way you can allow inspection of the code itself (without sensitive secrets), version-control and copy around the script more easily, etc...
Solution 3
Other answers have addressed the how, but I'll consider the whether. Depending on what kind of database your users are connecting to, you might already have a suitable mechanism that's already used by those client programs, in which case be sure to use them (I'm thinking of ~/.mysqlrc
or ~/.pgpass
).
If you're giving several users the ability to access the database to make specific queries using a shared account, then you probably shouldn't. Instead, make sure they have accounts on the database and that those accounts have no more permissions than they need (probably read on much of it, and very little write access). If they need to make specific queries on certain tables that they can't otherwise access, provide stored procedures with SECURTY DEFINER
to allow them to do so.
If none of the above avoids you needing to store credentials, then read the other answers here.
Solution 4
Another solution, without regard to security (I also think it is better to keep the credentials in another file or in a database) is to encrypt the password with gpg and insert it in the script.
I use a password-less gpg key pair that I keep in a usb. (Note: When you export this key pair don't use --armor, export them in binary format).
First encrypt your password:
echo -n "pAssw0rd" | gpg --armor --no-default-keyring --keyring /media/usb/key.pub --recipient [email protected] --encrypt
That will be print out the gpg encrypted password in the standart output. Copy the whole message and add this to the script:
password=$(gpg --batch --quiet --no-default-keyring --secret-keyring /media/usb/key.priv --decrypt <<EOF
-----BEGIN PGP MESSAGE-----
hQEMA0CjbyauRLJ8AQgAkZT5gK8TrdH6cZEy+Ufl0PObGZJ1YEbshacZb88RlRB9
h2z+s/Bso5HQxNd5tzkwulvhmoGu6K6hpMXM3mbYl07jHF4qr+oWijDkdjHBVcn5
0mkpYO1riUf0HXIYnvCZq/4k/ajGZRm8EdDy2JIWuwiidQ18irp07UUNO+AB9mq8
5VXUjUN3tLTexg4sLZDKFYGRi4fyVrYKGsi0i5AEHKwn5SmTb3f1pa5yXbv68eYE
lCVfy51rBbG87UTycZ3gFQjf1UkNVbp0WV+RPEM9JR7dgR+9I8bKCuKLFLnGaqvc
beA3A6eMpzXQqsAg6GGo3PW6fMHqe1ZCvidi6e4a/dJDAbHq0XWp93qcwygnWeQW
Ozr1hr5mCa+QkUSymxiUrRncRhyqSP0ok5j4rjwSJu9vmHTEUapiyQMQaEIF2e2S
/NIWGg==
=uriR
-----END PGP MESSAGE-----
EOF)
In this way only if the usb is mounted in the system the password can be decrypted. Of course you can also import the keys into the system (less secure, or no security at all) or you can protect the private key with password (so it can not be automated).
Solution 5
I ran into this problem as well. The short answer is to store the password separate from the script and then have the script load in the password from the environment. Which raises the question how best to do that?
Ideally you might use an external service like HashiCorp Vault to pull it into the shell environment; however, Vault is a fairly complicated beast to setup and in many enterprise environments you might be limited by what you can install and even what type of shell you might be able to use.
For this reason, I created encpass.sh - Lightweight solution for using encrypted passwords in shell scripts that allows you store secrets in a hidden directory only accessible by your user. It should work in any POSIX compliant shell and it's only real dependency is having OpenSSL installed on the local box to handle the encryption of secrets. All you have to do is include it in your script and call the "get_secret" function. It has an MIT License, so you can use it in a corporate environment. My company, Plyint LLC, maintains the script and releases updates occasionally. I'll copy the code below for easy visibility.
#!/bin/sh
################################################################################
# Copyright (c) 2020 Plyint, LLC <[email protected]>. 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
Related videos on Youtube
Admin
Updated on September 18, 2022Comments
-
Admin over 1 year
How can I hide a password in shell scripts? There are a number of scripts that are accessing database. If we open the script others also aware the username and password. So if anyone knows how to hide please let me know.
I have one way: place the password in a file and make the file as hidden and no one going to access the file (change the permissions and use the file in script while going to accessing database).
-
Celada almost 9 years@BasileStarynkevitch ah but I see that it also says "
/usr/local/etc
may be a symbolic link to/etc/local
". I missed that. So you're also right, but it's a MAY so it's optional. But yeah, it doesn't matter much, and personal preference. -
Basile Starynkevitch almost 9 yearsExcept that I am backuping
/etc/
but not/usr/local/
(which I assume can be rebuilt after disk crash) -
Celada almost 9 yearsfair point about backups
-
mikeserv almost 9 years@JennyD - if you need to connect to a service - then you donit with a certificate of course. You do not put passwords in plain text. Period. I don't understand what you're talking about w/
b
. -
Jenny D almost 9 yearsSure, but again within the constraints of a shell script, anyone who can get at it can also get at the certificate...
-
mikeserv almost 9 years@JennyD - of course they might. That's why we have public/private certs. This is not a new problem.
-
Jenny D almost 9 yearsAnd then you have the problem of protecting the key - which, again, if you have access to the script, then you can get at the credentials, whether they be a password or a file. The problem isn't new, but that doesn't mean that it's easily solved for your average shell script writer.
-
mikeserv almost 9 years@JennyD - No. The private key is yours. It doesn't go on the system w/ the script - it is not distributed. This is not a new problem. This is a common problem. The solution to this problem is certification. It needn't be done necessarily with a common GPG/SSL cert. Hardware can be certified - it happens all of the time. Users can be certified - and in fact it happens each time they login. If a shell scripter needs a password - the script needs to stop and ask for it or certify otherwise. You don't put passwords in scripts - ever - and you certainly don't recommend the average scripter should
-
Jenny D almost 9 yearsFirstly, At no point did I recommend using passwords in scripts. Secondly, for a script that is run interactively, obviously entering the password or the key is trivial - the problem is when the scripts are not run interactively. I'd be very interested in your take on this in an answer.
-
mikeserv almost 9 years@JennyD - I've given my answer - never do this. That's the only answer this question should ever have got. And yes, i know, you recommend a root-owned file. So how does the script read it exactly - it's root too? You don't run shell scripts as root either - not after init. They should be run as nobody - which might be an acceptable way to go if a pre-authorized proxy service runs in the background which can grant permissions based on predefined rules (which is how this is usually done - such as w/
PAM
). Compromised security is not a solution, but a problem. -
Jenny D almost 9 yearsYour predictive powers are lacking.
-
Jenny D almost 9 yearsYou are claiming to know my answer before I've written it.
-
Jenny D almost 9 yearsIt would have been really interesting to get to read an actual answer that the poster could have used to implement a more secure system from you. Do you think you might write one at some point?
-
mikeserv almost 9 years@JennyD - i thought you had answered here! Well, now im curious. I guess i was predicting. In any case the real answer is flatly - don't. That's mine, anyhow - but it's real enough.
-
Jenny D almost 9 yearsI've written my answer now. What I was trying to get at from you was partly my fifth point - once the credentials are stored in memory they are not fully secure from an attacker with access to the server. Also that your terse answer was not very helpful, even though not incorrect. I'm thinking of pointing at it the next time someone complains that we at ServerFault are not as nice as the people at Unix/Linux :-)
-
mikeserv almost 9 years@JennyD - once the cred is in memory - it certainly doesn't need to stay there - and that's a very good reason to deal w/ hashes instead anyway. Still, i'll happily represent community's courtesy standards as its exemplar at large - im very well known for that trait, in fact.
-
mikeserv almost 9 yearsWe agree on a lot here - thanks for doing that - you did it very well. I still think the user should have been told directly and flatly no - any db worth its salt already provides some remote access solution and will doc it. And i'll leave this answer - though i would normally delete it if i believed a far better answer in the same vein had made (as i do now), for you and your SF friends. I would not wish to be discourteous.
-
Jenny D almost 9 yearsThanks for the compliment! If this had been ServerFault I would have voted to close it for not being according to business practices... but here I'm more willing to take the time to educate people being new to things.
-
Celada almost 9 yearsAfter your big discussion I would just like to mention in case there's any misunderstanding that it was I who downvoted, not Jenny. And the reason wasn't an opinion on whether or not it's a good idea to use a certificate&privkey or interactively-entered password or do what the OP wanted or whatever. It was because the answer as it stands is wrong: storing a salted hash of the password as a client credential isn't a thing: salted hashes are a thing you keep on the end of the connection that is verifying the auth, not on the side that is submitting it.
-
mikeserv almost 9 years@Celada - it's fine. the vote doesn't matter - there's a better answer here anyway. That's what's important. You should reconsider your answer though - it's dangerous. You're right about the hashed client cred of course - for some reason i had the impression that the request was for the reverse op.
-
Celada almost 9 yearsRight, I did edit my answer. I hope it's less dangerous than before.
-
mikeserv almost 9 years@Celada - indeed! I so emphatically (as I thought) argued against your answer with JennyD here, and posted this one specifically because there was no warning at all. It's not wrong to say you can do this thing, but God help you if you do! where it is quite often wrong to say only you can do this thing. I didn't come here and downvote anybody - I just considered it important there be a strongly dissenting voice among those heard here.
-
Wildcard about 8 yearsNot arrogant and elitist at all. Or else I am, too. "How can I protect my jewelry in case a burglar breaks in?" "Put it in a safe that is bolted/welded in place." "That's too much trouble, I'll just get a portable safe and hang the key from a hook." "Then don't bother using a safe." Eminently sensible advice.
-
KrishPrabakar over 4 yearsA complete example script with above file usage would be helpful for layman
-
Ken Ingram over 2 yearsIs this a viable solution if the scripts are being versioned?