Commit d7602afa authored by Bruce Momjian's avatar Bruce Momjian

Add scripts for retrieving the cluster file encryption key

Scripts are passphrase, direct, AWS, and two Yubikey ones.

Backpatch-through: master
parent 3d4843ba
...@@ -54,6 +54,15 @@ ifeq ($(with_systemd),yes) ...@@ -54,6 +54,15 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd LIBS += -lsystemd
endif endif
CRYPTO_SCRIPTDIR=auth_commands
CRYPTO_SCRIPTS = \
ckey_aws.sh.sample \
ckey_direct.sh.sample \
ckey_passphrase.sh.sample \
ckey_piv_nopin.sh.sample \
ckey_piv_pin.sh.sample \
ssl_passphrase.sh.sample
########################################################################## ##########################################################################
all: submake-libpgport submake-catalog-headers submake-utils-headers postgres $(POSTGRES_IMP) all: submake-libpgport submake-catalog-headers submake-utils-headers postgres $(POSTGRES_IMP)
...@@ -212,6 +221,7 @@ endif ...@@ -212,6 +221,7 @@ endif
$(INSTALL_DATA) $(srcdir)/libpq/pg_hba.conf.sample '$(DESTDIR)$(datadir)/pg_hba.conf.sample' $(INSTALL_DATA) $(srcdir)/libpq/pg_hba.conf.sample '$(DESTDIR)$(datadir)/pg_hba.conf.sample'
$(INSTALL_DATA) $(srcdir)/libpq/pg_ident.conf.sample '$(DESTDIR)$(datadir)/pg_ident.conf.sample' $(INSTALL_DATA) $(srcdir)/libpq/pg_ident.conf.sample '$(DESTDIR)$(datadir)/pg_ident.conf.sample'
$(INSTALL_DATA) $(srcdir)/utils/misc/postgresql.conf.sample '$(DESTDIR)$(datadir)/postgresql.conf.sample' $(INSTALL_DATA) $(srcdir)/utils/misc/postgresql.conf.sample '$(DESTDIR)$(datadir)/postgresql.conf.sample'
$(INSTALL_DATA) $(addprefix 'crypto/', $(CRYPTO_SCRIPTS)) '$(DESTDIR)$(datadir)/$(CRYPTO_SCRIPTDIR)'
ifeq ($(with_llvm), yes) ifeq ($(with_llvm), yes)
install-bin: install-postgres-bitcode install-bin: install-postgres-bitcode
...@@ -237,6 +247,7 @@ endif ...@@ -237,6 +247,7 @@ endif
installdirs: installdirs:
$(MKDIR_P) '$(DESTDIR)$(bindir)' '$(DESTDIR)$(datadir)' $(MKDIR_P) '$(DESTDIR)$(bindir)' '$(DESTDIR)$(datadir)'
$(MKDIR_P) '$(DESTDIR)$(datadir)' '$(DESTDIR)$(datadir)/$(CRYPTO_SCRIPTDIR)'
ifeq ($(PORTNAME), cygwin) ifeq ($(PORTNAME), cygwin)
ifeq ($(MAKE_DLL), true) ifeq ($(MAKE_DLL), true)
$(MKDIR_P) '$(DESTDIR)$(libdir)' $(MKDIR_P) '$(DESTDIR)$(libdir)'
...@@ -257,6 +268,7 @@ endif ...@@ -257,6 +268,7 @@ endif
uninstall: uninstall:
rm -f '$(DESTDIR)$(bindir)/postgres$(X)' '$(DESTDIR)$(bindir)/postmaster' rm -f '$(DESTDIR)$(bindir)/postgres$(X)' '$(DESTDIR)$(bindir)/postmaster'
rm -f $(addprefix '$(DESTDIR)$(datadir)/$(CRYPTO_SCRIPTDIR)'/, $(CRYPTO_SCRIPTS))
ifeq ($(MAKE_EXPORTS), true) ifeq ($(MAKE_EXPORTS), true)
rm -f '$(DESTDIR)$(pkglibdir)/$(POSTGRES_IMP)' rm -f '$(DESTDIR)$(pkglibdir)/$(POSTGRES_IMP)'
rm -f '$(DESTDIR)$(pgxsdir)/$(MKLDEXPORT_DIR)/mkldexport.sh' rm -f '$(DESTDIR)$(pgxsdir)/$(MKLDEXPORT_DIR)/mkldexport.sh'
......
#!/bin/sh
# This uses the AWS Secrets Manager using the AWS CLI and OpenSSL.
[ "$#" -ne 1 ] && echo "cluster_key_command usage: $0 \"%d\"" 1>&2 && exit 1
# No need for %R or -R since we are not prompting
DIR="$1"
[ ! -e "$DIR" ] && echo "$DIR does not exist" 1>&2 && exit 1
[ ! -d "$DIR" ] && echo "$DIR is not a directory" 1>&2 && exit 1
# File containing the id of the AWS secret
AWS_ID_FILE="$DIR/aws-secret.id"
# ----------------------------------------------------------------------
# Create an AWS Secrets Manager secret?
if [ ! -e "$AWS_ID_FILE" ]
then # The 'postgres' operating system user must have permission to
# access the AWS CLI
# The epoch-time/directory/hostname combination is unique
HASH=$(echo -n "$(date '+%s')$DIR$(hostname)" | sha1sum | cut -d' ' -f1)
AWS_SECRET_ID="Postgres-cluster-key-$HASH"
# Use stdin to avoid passing the secret on the command line
openssl rand -hex 32 |
aws secretsmanager create-secret \
--name "$AWS_SECRET_ID" \
--description 'Used for Postgres cluster file encryption' \
--secret-string 'file:///dev/stdin' \
--output text > /dev/null
if [ "$?" -ne 0 ]
then echo 'cluster key generation failed' 1>&2
exit 1
fi
echo "$AWS_SECRET_ID" > "$AWS_ID_FILE"
fi
if ! aws secretsmanager get-secret-value \
--secret-id "$(cat "$AWS_ID_FILE")" \
--output text
then echo 'cluster key retrieval failed' 1>&2
exit 1
fi | awk -F'\t' 'NR == 1 {print $4}'
exit 0
#!/bin/sh
# This uses a key supplied by the user
# If OpenSSL is installed, you can generate a pseudo-random key by running:
# openssl rand -hex 32
# To get a true random key, run:
# wget -q -O - 'https://www.random.org/cgi-bin/randbyte?nbytes=32&format=h' | tr -d ' \n'; echo
[ "$#" -lt 1 ] && echo "cluster_key_command usage: $0 %R [%p]" 1>&2 && exit 1
# Supports environment variable PROMPT
FD="$1"
[ ! -t "$FD" ] && echo "file descriptor $FD does not refer to a terminal" 1>&2 && exit 1
[ "$2" ] && PROMPT="$2"
# ----------------------------------------------------------------------
[ ! "$PROMPT" ] && PROMPT='Enter cluster key as 64 hexadecimal characters: '
stty -echo <&"$FD"
echo 1>&"$FD"
echo -n "$PROMPT" 1>&"$FD"
read KEY <&"$FD"
stty echo <&"$FD"
if [ "$(expr "$KEY" : '[0-9a-fA-F]*$')" -ne 64 ]
then echo 'invalid; must be 64 hexadecimal characters' 1>&2
exit 1
fi
echo "$KEY"
exit 0
#!/bin/sh
# This uses a passphrase supplied by the user.
[ "$#" -lt 1 ] && echo "cluster_key_command usage: $0 %R [\"%p\"]" 1>&2 && exit 1
FD="$1"
[ ! -t "$FD" ] && echo "file descriptor $FD does not refer to a terminal" 1>&2 && exit 1
# Supports environment variable PROMPT
[ "$2" ] && PROMPT="$2"
# ----------------------------------------------------------------------
[ ! "$PROMPT" ] && PROMPT='Enter cluster passphrase: '
stty -echo <&"$FD"
echo 1>&"$FD"
echo -n "$PROMPT" 1>&"$FD"
read PASS <&"$FD"
stty echo <&"$FD"
if [ ! "$PASS" ]
then echo 'invalid: empty passphrase' 1>&2
exit 1
fi
echo "$PASS" | sha256sum | cut -d' ' -f1
exit 0
#!/bin/sh
# This uses the public/private keys on a PIV device, like a CAC or Yubikey.
# It uses a PIN stored in a file.
# It uses OpenSSL with PKCS11 enabled via OpenSC.
[ "$#" -ne 1 ] && echo "cluster_key_command usage: $0 \"%d\"" 1>&2 && exit 1
# Supports environment variable PIV_PIN_FILE
# No need for %R or -R since we are not prompting for a PIN
DIR="$1"
[ ! -e "$DIR" ] && echo "$DIR does not exist" 1>&2 && exit 1
[ ! -d "$DIR" ] && echo "$DIR is not a directory" 1>&2 && exit 1
# Set these here or pass in as environment variables.
# File that stores the PIN to unlock the PIV
#PIV_PIN_FILE=''
# PIV slot 3 is the "Key Management" slot, so we use '0:3'
PIV_SLOT='0:3'
# File containing the cluster key encrypted with the PIV_SLOT's public key
KEY_FILE="$DIR/pivpass.key"
# ----------------------------------------------------------------------
[ ! "$PIV_PIN_FILE" ] && echo 'PIV_PIN_FILE undefined' 1>&2 && exit 1
[ ! -e "$PIV_PIN_FILE" ] && echo "$PIV_PIN_FILE does not exist" 1>&2 && exit 1
[ -d "$PIV_PIN_FILE" ] && echo "$PIV_PIN_FILE is a directory" 1>&2 && exit 1
[ ! "$KEY_FILE" ] && echo 'KEY_FILE undefined' 1>&2 && exit 1
[ -d "$KEY_FILE" ] && echo "$KEY_FILE is a directory" 1>&2 && exit 1
# Create a cluster key encrypted with the PIV_SLOT's public key?
if [ ! -e "$KEY_FILE" ]
then # The 'postgres' operating system user must have permission to
# access the PIV device.
openssl rand -hex 32 |
if ! openssl rsautl -engine pkcs11 -keyform engine -encrypt \
-inkey "$PIV_SLOT" -passin file:"$PIV_PIN_FILE" -out "$KEY_FILE"
then echo 'cluster key generation failed' 1>&2
exit 1
fi
# Warn the user to save the cluster key in a safe place
cat 1>&2 <<END
WARNING: The PIV device can be locked and require a reset if too many PIN
attempts fail. It is recommended to run this command manually and save
the cluster key in a secure location for possible recovery.
END
fi
# Decrypt the cluster key encrypted with the PIV_SLOT's public key
if ! openssl rsautl -engine pkcs11 -keyform engine -decrypt \
-inkey "$PIV_SLOT" -passin file:"$PIV_PIN_FILE" -in "$KEY_FILE"
then echo 'cluster key decryption failed' 1>&2
exit 1
fi
exit 0
#!/bin/sh
# This uses the public/private keys on a PIV device, like a CAC or Yubikey.
# It requires a user-entered PIN.
# It uses OpenSSL with PKCS11 enabled via OpenSC.
[ "$#" -lt 2 ] && echo "cluster_key_command usage: $0 \"%d\" %R [\"%p\"]" 1>&2 && exit 1
# Supports environment variable PROMPT
DIR="$1"
[ ! -e "$DIR" ] && echo "$DIR does not exist" 1>&2 && exit 1
[ ! -d "$DIR" ] && echo "$DIR is not a directory" 1>&2 && exit 1
FD="$2"
[ ! -t "$FD" ] && echo "file descriptor $FD does not refer to a terminal" 1>&2 && exit 1
[ "$3" ] && PROMPT="$3"
# PIV slot 3 is the "Key Management" slot, so we use '0:3'
PIV_SLOT='0:3'
# File containing the cluster key encrypted with the PIV_SLOT's public key
KEY_FILE="$DIR/pivpass.key"
# ----------------------------------------------------------------------
[ ! "$PROMPT" ] && PROMPT='Enter PIV PIN: '
stty -echo <&"$FD"
# Create a cluster key encrypted with the PIV_SLOT's public key?
if [ ! -e "$KEY_FILE" ]
then echo 1>&"$FD"
echo -n "$PROMPT" 1>&"$FD"
# The 'postgres' operating system user must have permission to
# access the PIV device.
openssl rand -hex 32 |
# 'engine "pkcs11" set.' message confuses prompting
if ! openssl rsautl -engine pkcs11 -keyform engine -encrypt \
-inkey "$PIV_SLOT" -passin fd:"$FD" -out "$KEY_FILE" 2>&1
then stty echo <&"$FD"
echo 'cluster key generation failed' 1>&2
exit 1
fi | grep -v 'engine "pkcs11" set\.'
echo 1>&"$FD"
# Warn the user to save the cluster key in a safe place
cat 1>&"$FD" <<END
WARNING: The PIV can be locked and require a reset if too many PIN
attempts fail. It is recommended to run this command manually and save
the cluster key in a secure location for possible recovery.
END
fi
echo 1>&"$FD"
echo -n "$PROMPT" 1>&"$FD"
# Decrypt the cluster key encrypted with the PIV_SLOT's public key
if ! openssl rsautl -engine pkcs11 -keyform engine -decrypt \
-inkey "$PIV_SLOT" -passin fd:"$FD" -in "$KEY_FILE" 2>&1
then stty echo <&"$FD"
echo 'cluster key retrieval failed' 1>&2
exit 1
fi | grep -v 'engine "pkcs11" set\.'
echo 1>&"$FD"
stty echo <&"$FD"
exit 0
#!/bin/sh
# This uses a passphrase supplied by the user.
[ "$#" -lt 1 ] && echo "ssl_passphrase_command usage: $0 %R [\"%p\"]" 1>&2 && exit 1
FD="$1"
[ ! -t "$FD" ] && echo "file descriptor $FD does not refer to a terminal" 1>&2 && exit 1
# Supports environment variable PROMPT
[ "$2" ] && PROMPT="$2"
# ----------------------------------------------------------------------
[ ! "$PROMPT" ] && PROMPT='Enter cluster passphrase: '
stty -echo <&"$FD"
echo 1>&"$FD"
echo -n "$PROMPT" 1>&"$FD"
read PASS <&"$FD"
stty echo <&"$FD"
if [ ! "$PASS" ]
then echo 'invalid: empty passphrase' 1>&2
exit 1
fi
echo "$PASS"
exit 0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment