From b2fd566c3638d830f08687e22e748c78d0e00535 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Tue, 30 Apr 2019 08:12:06 -0700 Subject: [PATCH] Breaking Change: Completely Change PostgreSQL Account Management This is a breaking change that will require you to change your Phoebe settings for PostgreSQL. * New database configuration options * Accounts no longer automatically create databases * Databases have `owners' that tie them back to an account * Databases have `users' that grant accounts full access * Databases have `readers' that grant read-only access to accounts * Accounts can use `ident' authentication for local connections if you enable the `allowIdent' option. * Existing accounts that are not configured via Phoebe will be locked so they cannot be used. That way if you delete a user from Phoebe the account will continue to exist, but won't have access to anything. --- default.nix | 2 +- .../databases/postgresql/create-db.sh | 79 ++++++++ .../databases/postgresql/create-grant.sh | 108 ++++++++++ .../databases/postgresql/create-user.sh | 33 +-- .../databases/postgresql/create-user.sql | 2 +- .../services/databases/postgresql/default.nix | 190 ++++++++++++++---- .../services/databases/postgresql/nologin.sh | 27 +++ .../{create-user.nix => scripts.nix} | 11 +- 8 files changed, 380 insertions(+), 72 deletions(-) create mode 100755 modules/services/databases/postgresql/create-db.sh create mode 100755 modules/services/databases/postgresql/create-grant.sh create mode 100755 modules/services/databases/postgresql/nologin.sh rename modules/services/databases/postgresql/{create-user.nix => scripts.nix} (62%) diff --git a/default.nix b/default.nix index 712d424..49d43e5 100644 --- a/default.nix +++ b/default.nix @@ -4,7 +4,7 @@ pkgs.stdenvNoCC.mkDerivation rec { name = "phoebe-${version}"; - version = "0.1"; + version = "0.2"; src = ./.; phases = diff --git a/modules/services/databases/postgresql/create-db.sh b/modules/services/databases/postgresql/create-db.sh new file mode 100755 index 0000000..94ebfab --- /dev/null +++ b/modules/services/databases/postgresql/create-db.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +################################################################################ +# Create a database if it's missing. +set -e + +################################################################################ +option_database="" +option_owner="@superuser@" +option_extensions="" + +################################################################################ +usage () { +cat <&2 echo "ERROR: must give -d" + exit 1; +fi + +################################################################################ +create_database +enable_extensions diff --git a/modules/services/databases/postgresql/create-grant.sh b/modules/services/databases/postgresql/create-grant.sh new file mode 100755 index 0000000..c0e152c --- /dev/null +++ b/modules/services/databases/postgresql/create-grant.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +################################################################################ +# Grant a user specific rights to a database. +set -e + +################################################################################ +option_user="" +option_database="" +option_access="r" + +################################################################################ +usage () { +cat <&2 echo "ERROR: invalid access level: $level" + exit 1 + esac +} + +################################################################################ +while getopts "a:d:hu:" o; do + case "${o}" in + a) option_access=$(verify_access_level "$OPTARG") + ;; + + d) option_database=$OPTARG + ;; + + h) usage + exit + ;; + + u) option_user=$OPTARG + ;; + + *) exit 1 + ;; + esac +done + +shift $((OPTIND-1)) + +################################################################################ +_psql() { + @sudo@ -u @superuser@ -H psql "$@" +} + +################################################################################ +echo_grants() { + local r_list="SELECT" + local w_list="INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER" + + # Needed to resolve ambiguous role memberships. + echo "SET ROLE @superuser@;" + + # Start by removing all access then granting the ability to connect: + echo "REVOKE ALL PRIVILEGES ON DATABASE $option_database FROM $option_user;" + echo "GRANT CONNECT ON DATABASE $option_database TO $option_user;" + + # Basic options: + echo "GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO $option_user;" + echo "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON FUNCTIONS TO $option_user;" + + if [ "$option_access" = "r" ] || [ "$option_access" = "rw" ]; then + echo "GRANT USAGE ON SCHEMA public TO $option_user;" + + echo "GRANT $r_list ON ALL TABLES IN SCHEMA public TO $option_user;" + echo "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT $r_list ON TABLES TO $option_user;" + + echo "GRANT USAGE,SELECT ON ALL SEQUENCES IN SCHEMA public TO $option_user;" + echo "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE,SELECT ON SEQUENCES TO $option_user;" + fi + + if [ "$option_access" = "w" ] || [ "$option_access" = "rw" ]; then + echo "GRANT $w_list ON ALL TABLES IN SCHEMA public TO $option_user;" + echo "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT $w_list ON TABLES TO $option_user;" + + echo "GRANT UPDATE ON ALL SEQUENCES IN SCHEMA public TO $option_user;" + echo "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT UPDATE ON SEQUENCES TO $option_user;" + fi +} + +################################################################################ +# Let's do it! +sql_file=$(mktemp) +echo_grants > "$sql_file" +chown @superuser@ "$sql_file" +_psql --dbname="$option_database" --file="$sql_file" --single-transaction +rm "$sql_file" diff --git a/modules/services/databases/postgresql/create-user.sh b/modules/services/databases/postgresql/create-user.sh index e78a48f..c7eefe6 100755 --- a/modules/services/databases/postgresql/create-user.sh +++ b/modules/services/databases/postgresql/create-user.sh @@ -6,8 +6,6 @@ set -e ################################################################################ option_username="" option_password_file="" -option_database="" -option_extensions="" option_sqlfile="@out@/sql/create-user.sql" option_superuser=0 @@ -16,8 +14,6 @@ usage () { cat < "$sql_file" + + # Unlock configured accounts: + ( + ${allowLogin accounts} + ) >> "$sql_file" + + chown ${superuser} "$sql_file" + + ${pkgs.sudo}/bin/sudo -u ${superuser} -H \ + psql --dbname="postgres" --file="$sql_file" --single-transaction + + rm "$sql_file" + ''; + + # Master grant creation function: + createGrants = database: + let find = names: map (name: cfg.accounts."${name}") + (filter (name: cfg.accounts ? "${name}") + names); + ro = find database.readers; + rw = find (database.users ++ [database.owner]); + owner = find [database.owner]; + in (concatMapStringsSep "\n" (createReadGrant database) ro) + + (concatMapStringsSep "\n" (createGrant database) rw); in { #### Interface @@ -119,7 +227,13 @@ in accounts = mkOption { type = types.attrsOf (types.submodule account); default = { }; - description = "Additional user accounts"; + description = "Additional user accounts."; + }; + + databases = mkOption { + type = types.attrsOf (types.submodule database); + default = { }; + description = "Additional databases to create."; }; }; @@ -142,13 +256,19 @@ in }; # Create missing accounts: - systemd.services.pg-accounts = mkIf (length (attrValues cfg.accounts) > 0) { + systemd.services.postgres-account-manager = { description = "PostgreSQL Account Manager"; path = [ pkgs.gawk config.services.postgresql.package ]; - script = (concatMapStringsSep "\n" createScript (attrValues cfg.accounts)); wantedBy = [ "postgresql.service" ]; after = [ "postgresql.service" ] ++ afterservices; wants = afterservices; + + script = '' + set -e + '' + (concatMapStringsSep "\n" createUser (attrValues cfg.accounts)) + + (lockAccounts (attrValues cfg.accounts)) + + (concatMapStringsSep "\n" createDB (attrValues cfg.databases)) + + (concatMapStringsSep "\n" createGrants (attrValues cfg.databases)); }; }; } diff --git a/modules/services/databases/postgresql/nologin.sh b/modules/services/databases/postgresql/nologin.sh new file mode 100755 index 0000000..c60eb17 --- /dev/null +++ b/modules/services/databases/postgresql/nologin.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +################################################################################ +# Generate SQL that locks out all users (except the superuser). +set -e +set -u + +################################################################################ +_psql() { + @sudo@ -u @superuser@ -H psql "$@" +} + +################################################################################ +accounts() { + echo "SELECT rolname FROM pg_catalog.pg_roles;" | \ + _psql --tuples-only postgres | \ + sed 's/^[[:space:]]*//' | \ + grep --fixed-strings --invert-match --line-regexp '@superuser@' | \ + grep --extended-regexp --invert-match '^pg_' +} + +################################################################################ +for name in $(accounts); do + if [ -n "$name" ]; then + echo "ALTER ROLE $name NOLOGIN;" + fi +done diff --git a/modules/services/databases/postgresql/create-user.nix b/modules/services/databases/postgresql/scripts.nix similarity index 62% rename from modules/services/databases/postgresql/create-user.nix rename to modules/services/databases/postgresql/scripts.nix index 2e461a4..52414b3 100644 --- a/modules/services/databases/postgresql/create-user.nix +++ b/modules/services/databases/postgresql/scripts.nix @@ -10,9 +10,14 @@ pkgs.stdenvNoCC.mkDerivation { export superuser=${config.services.postgresql.superUser} mkdir -p $out/bin $out/sql - cp ${./create-user.sql} $out/sql/create-user.sql - substituteAll ${./create-user.sh} $out/bin/create-user.sh - chmod 555 $out/bin/create-user.sh + + cp ${./create-user.sql} $out/sql/create-user.sql + substituteAll ${./create-user.sh} $out/bin/create-user.sh + substituteAll ${./create-db.sh} $out/bin/create-db.sh + substituteAll ${./create-grant.sh} $out/bin/create-grant.sh + substituteAll ${./nologin.sh} $out/bin/nologin.sh + + chmod 555 $out/bin/*.sh ''; meta = with lib; {