Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update PostgreSQL passwords when changed #492

Merged
merged 9 commits into from
Jan 19, 2024
Merged
8 changes: 4 additions & 4 deletions cloud
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ function execute_command() {
declare -a -r psql_args

# Enable fetching PostgreSQL passwords from their secrets
password_set_secret "psql_superuser" "$project" "kcidb_psql_superuser"
password_set_secret "psql_viewer" "$project" "kcidb_psql_viewer"
password_set_secret "psql_editor" "$project" "${prefix}psql_editor"
password_secret_set "psql_superuser" "$project" "kcidb_psql_superuser"
password_secret_set "psql_viewer" "$project" "kcidb_psql_viewer"
password_secret_set "psql_editor" "$project" "${prefix}psql_editor"

declare -r bigquery_dataset="${prefix}${version}"
declare -r bigquery_clean_test_dataset="${prefix}${version}_clean_test"
Expand Down Expand Up @@ -237,7 +237,7 @@ function execute_command() {
fi

# Register SMTP password secret
password_set_secret "smtp" "$project" "$SECRETS_SMTP_PASSWORD"
password_secret_set "smtp" "$project" "$SECRETS_SMTP_PASSWORD"

declare -r smtp_topic=$("$smtp_mocked" && echo "${prefix}smtp" || true)
declare -r smtp_subscription=$(
Expand Down
2 changes: 2 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
params=["empty_deployment"]
if os.environ.get("KCIDB_DEPLOYMENT", "") == "This deployment is empty"
else [],
# Only clean up after all tests have run, to reduce testing time
# Tests are supposed to avoid interfering with each other
scope="session"
)
def empty_deployment():
Expand Down
26 changes: 26 additions & 0 deletions kcidb/cloud/misc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@ export TMPDIR=$(mktemp -d -t "kcidb_cloud.XXXXXXXXXX")
# Remove the directory with all the temporary files on exit
atexit_push "rm -Rf ${TMPDIR@Q}"

# Evaluate and execute a command string,
# exit shell with error message and status 1 if unsuccessfull.
# Args: [eval_arg]...
function assert()
{
# Use private global-style variable names
# to avoid clashes with "evaled" names
declare _ASSERT_ATTRS
declare _ASSERT_STATUS

# Prevent shell from exiting due to `set -e` if the command fails
read -rd '' _ASSERT_ATTRS < <(set +o) || [ $? == 1 ]
set +o errexit
(
eval "$_ASSERT_ATTRS"
eval "$@"
)
_ASSERT_STATUS=$?
eval "$_ASSERT_ATTRS"

if [ "$_ASSERT_STATUS" != 0 ]; then
echo "Assertion failed: $*" >&1
exit 1
fi
}

# Generate code declaring parameter variables with names and values passed
# through long-option command-line argument list, and assigning the positional
# arguments.
Expand Down
153 changes: 101 additions & 52 deletions kcidb/cloud/password.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ if [ -z "${_PASSWORD_SH+set}" ]; then
declare _PASSWORD_SH=

. secret.sh
. misc.sh

# A map of password names and their descriptions
declare -r -A PASSWORD_DESCS=(
Expand Down Expand Up @@ -31,15 +32,25 @@ declare -A PASSWORD_FILES=()
# A map of password names and their strings
declare -A PASSWORD_STRINGS=()

# Check that every specified password exists.
# Args: name...
function password_exists() {
declare name
while (($#)); do
name="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
return 1
fi
done
return 0
}

# Ask the user to input a password with specified name.
# Args: name
# Output: The retrieved password
function password_input() {
declare -r name="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
echo "Unknown password name ${name@Q}" >&2
exit 1
fi
assert password_exists "$name"
declare password
read -p "Enter ${PASSWORD_DESCS[$name]:-a} password: " -r -s password
echo "" >&2
Expand All @@ -53,10 +64,7 @@ function password_input() {
# Output: The retrieved password
function password_get() {
declare -r name="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
echo "Unknown password name ${name@Q}" >&2
exit 1
fi
assert password_exists "$name"

declare password
declare -r password_file="${PASSWORD_FILES[$name]:-}"
Expand Down Expand Up @@ -112,10 +120,7 @@ function password_get_pgpass() {

while (($#)); do
name="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
echo "Unknown password name ${name@Q}" >&2
exit 1
fi
assert password_exists "$name"
username="$1"; shift

# Cache the password in the current shell
Expand All @@ -136,10 +141,7 @@ function password_get_pgpass() {
# Args: name file
function password_set_file() {
declare -r name="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
echo "Unknown password name ${name@Q}" >&2
exit 1
fi
assert password_exists "$name"
declare -r file="$1"; shift
PASSWORD_FILES[$name]="$file"
}
Expand All @@ -148,31 +150,64 @@ function password_set_file() {
# specified name. The password will be retrieved from the secret, if it wasn't
# cached, and if its source file wasn't specified.
# Args: name project secret
function password_set_secret() {
function password_secret_set() {
declare -r name="$1"; shift
declare -r project="$1"; shift
declare -r secret="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
echo "Unknown password name ${name@Q}" >&2
exit 1
fi
assert password_exists "$name"
if [[ "$project" = *:* ]]; then
echo "Invalid project name ${project@Q}" >&2
exit 1
fi
PASSWORD_SECRETS[$name]="$project:$secret"
}

# Check if every specified password has its secret set
# Assumes every specified password is known/exists.
# Args: name...
function password_secret_is_specified() {
declare name
assert password_exists "$@"
while (($#)); do
name="$1"; shift
if ! [[ -v PASSWORD_SECRETS[$name] ]]; then
return 1
fi
done
return 0
}

# Check if every specified password's secret exists.
# Assumes every specified password is known/exists and has its secret set.
# Args: name...
# Output: "true" if all secrets exists, "false" otherwise.
function password_secret_exists() {
declare name
declare project
declare secret
declare exists
assert password_exists "$@"
assert password_secret_is_specified "$@"
while (($#)); do
name="$1"; shift
project="${PASSWORD_SECRETS[$name]%%:*}"
secret="${PASSWORD_SECRETS[$name]#*:}"
exists=$(secret_exists "$project" "$secret")
if ! "$exists"; then
echo false
return
fi
done
echo true
}

# Specify the single-word command returning exit status specifying if the
# password with specified name could be auto-generated or not.
# Args: name generate
function password_set_generate() {
declare -r name="$1"; shift
declare -r generate="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
echo "Unknown password name ${name@Q}" >&2
exit 1
fi
assert password_exists "$name"
PASSWORD_GENERATE[$name]="$generate"
}

Expand All @@ -181,63 +216,77 @@ function password_set_generate() {
# Args: name...
function password_is_specified() {
declare name
assert password_exists "$@"
while (($#)); do
name="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
echo "Unknown password name ${name@Q}" >&2
exit 1
fi
if ! [[ -v PASSWORD_FILES[$name] ]]; then
return 1
fi
done
return 0
}

# Check if any of the passwords with specified names are (going to be) updated
# (specified on the command line, or missing).
# Args: name...
# Output: "true" if all secrets exists, "false" otherwise.
function password_is_updated() {
declare name
declare secret_exists
assert password_exists "$@"
while (($#)); do
name="$1"; shift
if password_is_specified "$name"; then
echo true
return
fi
if password_secret_is_specified "$name"; then
secret_exists="$(password_secret_exists "$name")"
if "$secret_exists"; then
continue
fi
fi
echo true
return
done
echo false
}

# Deploy passwords to their secrets (assuming they're set with
# "password_set_secret"). For every password deploy only if the password is
# specified, or the secret doesn't exist.
# "password_secret_set"). For every password deploy only if the password is
# updated, or the secret doesn't exist.
# Args: name...
function password_deploy_secret() {
function password_secret_deploy() {
declare name
declare updated
declare project
declare secret
declare exists
assert password_exists "$@"
assert password_secret_is_specified "$@"
while (($#)); do
name="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
echo "Unknown password name ${name@Q}" >&2
exit 1
fi
if ! [[ -v PASSWORD_SECRETS[$name] ]]; then
echo "Password ${name@Q} has no secret specified" >&2
exit 1
fi
project="${PASSWORD_SECRETS[$name]%%:*}"
secret="${PASSWORD_SECRETS[$name]#*:}"
exists=$(secret_exists "$project" "$secret")
if ! "$exists" || password_is_specified "$name"; then
updated=$(password_is_updated "$name")
if "$updated"; then
# Get and cache the password in the current shell first
password_get "$name" > /dev/null
# Deploy the cached password
project="${PASSWORD_SECRETS[$name]%%:*}"
secret="${PASSWORD_SECRETS[$name]#*:}"
password_get "$name" | secret_deploy "$project" "$secret"
fi
done
}

# Withdraw passwords from their secrets (assuming they're set with
# "password_set_secret").
# "password_secret_set").
# Args: name...
function password_withdraw_secret() {
function password_secret_withdraw() {
declare name
declare project
declare secret
assert password_exists "$@"
while (($#)); do
name="$1"; shift
if ! [[ -v PASSWORD_DESCS[$name] ]]; then
echo "Unknown password name ${name@Q}" >&2
exit 1
fi
if ! [[ -v PASSWORD_SECRETS[$name] ]]; then
echo "Password ${name@Q} has no secret specified" >&2
exit 1
Expand All @@ -252,7 +301,7 @@ function password_withdraw_secret() {
# Deploy only if one of the passwords is specified, or if the pgpass secret
# doesn't exist.
# Args: project pgpass_secret [password_name user_name]...
function password_deploy_pgpass_secret() {
function password_secret_deploy_pgpass() {
declare -r project="$1"; shift
declare -r pgpass_secret="$1"; shift
declare -a -r password_and_user_names=("$@")
Expand Down
9 changes: 7 additions & 2 deletions kcidb/cloud/psql.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ function psql_instance_deploy() {
declare -r name="$1"; shift
declare -r viewer="$1"; shift
declare exists
declare updated

exists=$(psql_instance_exists "$project" "$name")
if ! "$exists"; then
Expand All @@ -97,7 +98,8 @@ function psql_instance_deploy() {

# Deploy the shared viewer user
exists=$(psql_user_exists "$project" "$name" "$viewer")
if ! "$exists" || password_is_specified psql_viewer; then
updated=$(password_is_updated psql_viewer)
if ! "$exists" || "$updated"; then
# Get and cache the password in the current shell first
password_get psql_viewer >/dev/null
# Create the user with the cached password
Expand Down Expand Up @@ -291,6 +293,8 @@ function psql_databases_deploy() {
declare database
declare editor
declare init
declare exists
declare updated

while (($#)); do
database="$1"; shift
Expand All @@ -316,7 +320,8 @@ function psql_databases_deploy() {

# Deploy the per-database editor user
exists=$(psql_user_exists "$project" "$instance" "$editor")
if ! "$exists" || password_is_specified psql_editor; then
updated=$(password_is_updated psql_editor)
if ! "$exists" || "$updated"; then
# Get and cache the password in the current shell first
password_get psql_editor >/dev/null
# Create the user with the cached password
Expand Down
8 changes: 4 additions & 4 deletions kcidb/cloud/secrets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ function secrets_deploy() {
declare exists

# Make sure the shared SMTP password secret is deployed
password_deploy_secret smtp
password_secret_deploy smtp
# Give Cloud Functions access to the shared SMTP password secret
mute gcloud secrets add-iam-policy-binding \
--quiet --project="$project" "$SECRETS_SMTP_PASSWORD" \
--role roles/secretmanager.secretAccessor \
--member "serviceAccount:[email protected]"

# Make sure all PostgreSQL's password secrets are deployed
password_deploy_secret psql_superuser psql_editor psql_viewer
password_secret_deploy psql_superuser psql_editor psql_viewer
# DO NOT give Cloud Functions access to *any* PostgreSQL password secrets

# Make sure PostgreSQL's .pgpass secret is deployed
password_deploy_pgpass_secret "$project" "$psql_pgpass_secret" \
password_secret_deploy_pgpass "$project" "$psql_pgpass_secret" \
psql_editor "$psql_editor_username"

# Give Cloud Functions access to the .pgpass secret
Expand All @@ -47,7 +47,7 @@ function secrets_deploy() {
function secrets_withdraw() {
declare -r project="$1"; shift
declare -r psql_pgpass_secret="$1"; shift
password_withdraw_secret psql_editor
password_secret_withdraw psql_editor
secret_withdraw "$project" "$psql_pgpass_secret"
# NOTE: Not withdrawing the shared secrets
}
Expand Down