mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-30 00:04:49 -04:00 
			
		
		
		
	Add scripts for retrieving the cluster file encryption key
Scripts are passphrase, direct, AWS, and two Yubikey ones. Backpatch-through: master
This commit is contained in:
		
							parent
							
								
									3d4843babc
								
							
						
					
					
						commit
						d7602afa2e
					
				| @ -54,6 +54,15 @@ ifeq ($(with_systemd),yes) | ||||
| LIBS += -lsystemd | ||||
| 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) | ||||
| @ -212,6 +221,7 @@ endif | ||||
| 	$(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)/utils/misc/postgresql.conf.sample '$(DESTDIR)$(datadir)/postgresql.conf.sample' | ||||
| 	$(INSTALL_DATA) $(addprefix 'crypto/', $(CRYPTO_SCRIPTS)) '$(DESTDIR)$(datadir)/$(CRYPTO_SCRIPTDIR)' | ||||
| 
 | ||||
| ifeq ($(with_llvm), yes) | ||||
| install-bin: install-postgres-bitcode | ||||
| @ -237,6 +247,7 @@ endif | ||||
| 
 | ||||
| installdirs: | ||||
| 	$(MKDIR_P) '$(DESTDIR)$(bindir)' '$(DESTDIR)$(datadir)' | ||||
| 	$(MKDIR_P) '$(DESTDIR)$(datadir)' '$(DESTDIR)$(datadir)/$(CRYPTO_SCRIPTDIR)' | ||||
| ifeq ($(PORTNAME), cygwin) | ||||
| ifeq ($(MAKE_DLL), true) | ||||
| 	$(MKDIR_P) '$(DESTDIR)$(libdir)' | ||||
| @ -257,6 +268,7 @@ endif | ||||
| 
 | ||||
| uninstall: | ||||
| 	rm -f '$(DESTDIR)$(bindir)/postgres$(X)' '$(DESTDIR)$(bindir)/postmaster' | ||||
| 	rm -f $(addprefix '$(DESTDIR)$(datadir)/$(CRYPTO_SCRIPTDIR)'/, $(CRYPTO_SCRIPTS)) | ||||
| ifeq ($(MAKE_EXPORTS), true) | ||||
| 	rm -f '$(DESTDIR)$(pkglibdir)/$(POSTGRES_IMP)' | ||||
| 	rm -f '$(DESTDIR)$(pgxsdir)/$(MKLDEXPORT_DIR)/mkldexport.sh' | ||||
|  | ||||
							
								
								
									
										50
									
								
								src/backend/crypto/ckey_aws.sh.sample
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										50
									
								
								src/backend/crypto/ckey_aws.sh.sample
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,50 @@ | ||||
| #!/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 | ||||
							
								
								
									
										37
									
								
								src/backend/crypto/ckey_direct.sh.sample
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										37
									
								
								src/backend/crypto/ckey_direct.sh.sample
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,37 @@ | ||||
| #!/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 | ||||
							
								
								
									
										33
									
								
								src/backend/crypto/ckey_passphrase.sh.sample
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										33
									
								
								src/backend/crypto/ckey_passphrase.sh.sample
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,33 @@ | ||||
| #!/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 | ||||
							
								
								
									
										63
									
								
								src/backend/crypto/ckey_piv_nopin.sh.sample
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										63
									
								
								src/backend/crypto/ckey_piv_nopin.sh.sample
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,63 @@ | ||||
| #!/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 | ||||
							
								
								
									
										76
									
								
								src/backend/crypto/ckey_piv_pin.sh.sample
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										76
									
								
								src/backend/crypto/ckey_piv_pin.sh.sample
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,76 @@ | ||||
| #!/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 | ||||
							
								
								
									
										33
									
								
								src/backend/crypto/ssl_passphrase.sh.sample
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										33
									
								
								src/backend/crypto/ssl_passphrase.sh.sample
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,33 @@ | ||||
| #!/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 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user