1
0
mirror of https://github.com/postgres/postgres.git synced 2025-09-02 04:21:28 +03:00

Allow group access on PGDATA

Allow the cluster to be optionally init'd with read access for the
group.

This means a relatively non-privileged user can perform a backup of the
cluster without requiring write privileges, which enhances security.

The mode of PGDATA is used to determine whether group permissions are
enabled for directory and file creates.  This method was chosen as it's
simple and works well for the various utilities that write into PGDATA.

Changing the mode of PGDATA manually will not automatically change the
mode of all the files contained therein.  If the user would like to
enable group access on an existing cluster then changing the mode of all
the existing files will be required.  Note that pg_upgrade will
automatically change the mode of all migrated files if the new cluster
is init'd with the -g option.

Tests are included for the backend and all the utilities which operate
on the PG data directory to ensure that the correct mode is set based on
the data directory permissions.

Author: David Steele <david@pgmasters.net>
Reviewed-By: Michael Paquier, with discussion amongst many others.
Discussion: https://postgr.es/m/ad346fe6-b23e-59f1-ecb7-0e08390ad629%40pgmasters.net
This commit is contained in:
Stephen Frost
2018-04-07 17:45:39 -04:00
parent da9b580d89
commit c37b3d08ca
32 changed files with 661 additions and 127 deletions

View File

@@ -2470,14 +2470,6 @@ main(int argc, char **argv)
}
#endif
/*
* Verify that the target directory exists, or create it. For plaintext
* backups, always require the directory. For tar backups, require it
* unless we are writing to stdout.
*/
if (format == 'p' || strcmp(basedir, "-") != 0)
verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
/* connection in replication mode to server */
conn = GetConnection();
if (!conn)
@@ -2486,6 +2478,24 @@ main(int argc, char **argv)
exit(1);
}
/*
* Set umask so that directories/files are created with the same
* permissions as directories/files in the source data directory.
*
* pg_mode_mask is set to owner-only by default and then updated in
* GetConnection() where we get the mode from the server-side with
* RetrieveDataDirCreatePerm() and then call SetDataDirectoryCreatePerm().
*/
umask(pg_mode_mask);
/*
* Verify that the target directory exists, or create it. For plaintext
* backups, always require the directory. For tar backups, require it
* unless we are writing to stdout.
*/
if (format == 'p' || strcmp(basedir, "-") != 0)
verify_dir_is_empty_or_create(basedir, &made_new_pgdata, &found_existing_pgdata);
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);

View File

@@ -19,6 +19,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include "common/file_perm.h"
#include "libpq-fe.h"
#include "access/xlog_internal.h"
#include "getopt_long.h"
@@ -703,6 +704,16 @@ main(int argc, char **argv)
if (!RunIdentifySystem(conn, NULL, NULL, NULL, &db_name))
disconnect_and_exit(1);
/*
* Set umask so that directories/files are created with the same
* permissions as directories/files in the source data directory.
*
* pg_mode_mask is set to owner-only by default and then updated in
* GetConnection() where we get the mode from the server-side with
* RetrieveDataDirCreatePerm() and then call SetDataDirectoryCreatePerm().
*/
umask(pg_mode_mask);
/* determine remote server's xlog segment size */
if (!RetrieveWalSegSize(conn))
disconnect_and_exit(1);

View File

@@ -23,6 +23,7 @@
#include "streamutil.h"
#include "access/xlog_internal.h"
#include "common/file_perm.h"
#include "common/fe_memutils.h"
#include "getopt_long.h"
#include "libpq-fe.h"
@@ -959,6 +960,16 @@ main(int argc, char **argv)
disconnect_and_exit(1);
}
/*
* Set umask so that directories/files are created with the same
* permissions as directories/files in the source data directory.
*
* pg_mode_mask is set to owner-only by default and then updated in
* GetConnection() where we get the mode from the server-side with
* RetrieveDataDirCreatePerm() and then call SetDataDirectoryCreatePerm().
*/
umask(pg_mode_mask);
/* Drop a replication slot. */
if (do_drop_slot)
{

View File

@@ -23,6 +23,7 @@
#include "access/xlog_internal.h"
#include "common/fe_memutils.h"
#include "common/file_perm.h"
#include "datatype/timestamp.h"
#include "fe_utils/connect.h"
#include "port/pg_bswap.h"
@@ -32,9 +33,16 @@
uint32 WalSegSz;
static bool RetrieveDataDirCreatePerm(PGconn *conn);
/* SHOW command for replication connection was introduced in version 10 */
#define MINIMUM_VERSION_FOR_SHOW_CMD 100000
/*
* Group access is supported from version 11.
*/
#define MINIMUM_VERSION_FOR_GROUP_ACCESS 110000
const char *progname;
char *connection_string = NULL;
char *dbhost = NULL;
@@ -254,6 +262,16 @@ GetConnection(void)
exit(1);
}
/*
* Retrieve the source data directory mode and use it to construct a umask
* for creating directories and files.
*/
if (!RetrieveDataDirCreatePerm(tmpconn))
{
PQfinish(tmpconn);
exit(1);
}
return tmpconn;
}
@@ -327,6 +345,64 @@ RetrieveWalSegSize(PGconn *conn)
return true;
}
/*
* RetrieveDataDirCreatePerm
*
* This function is used to determine the privileges on the server's PG data
* directory and, based on that, set what the permissions will be for
* directories and files we create.
*
* PG11 added support for (optionally) group read/execute rights to be set on
* the data directory. Prior to PG11, only the owner was allowed to have rights
* on the data directory.
*/
static bool
RetrieveDataDirCreatePerm(PGconn *conn)
{
PGresult *res;
int data_directory_mode;
/* check connection existence */
Assert(conn != NULL);
/* for previous versions leave the default group access */
if (PQserverVersion(conn) < MINIMUM_VERSION_FOR_GROUP_ACCESS)
return true;
res = PQexec(conn, "SHOW data_directory_mode");
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
fprintf(stderr, _("%s: could not send replication command \"%s\": %s\n"),
progname, "SHOW data_directory_mode", PQerrorMessage(conn));
PQclear(res);
return false;
}
if (PQntuples(res) != 1 || PQnfields(res) < 1)
{
fprintf(stderr,
_("%s: could not fetch group access flag: got %d rows and %d fields, expected %d rows and %d or more fields\n"),
progname, PQntuples(res), PQnfields(res), 1, 1);
PQclear(res);
return false;
}
if (sscanf(PQgetvalue(res, 0, 0), "%o", &data_directory_mode) != 1)
{
fprintf(stderr, _("%s: group access flag could not be parsed: %s\n"),
progname, PQgetvalue(res, 0, 0));
PQclear(res);
return false;
}
SetDataDirectoryCreatePerm(data_directory_mode);
PQclear(res);
return true;
}
/*
* Run IDENTIFY_SYSTEM through a given connection and give back to caller
* some result information if requested:

View File

@@ -6,7 +6,7 @@ use File::Basename qw(basename dirname);
use File::Path qw(rmtree);
use PostgresNode;
use TestLib;
use Test::More tests => 105;
use Test::More tests => 106;
program_help_ok('pg_basebackup');
program_version_ok('pg_basebackup');
@@ -44,10 +44,17 @@ $node->command_fails(
ok(!-d "$tempdir/backup", 'backup directory was cleaned up');
# Create a backup directory that is not empty so the next commnd will fail
# but leave the data directory behind
mkdir("$tempdir/backup")
or BAIL_OUT("unable to create $tempdir/backup");
append_to_file("$tempdir/backup/dir-not-empty.txt");
$node->command_fails([ 'pg_basebackup', '-D', "$tempdir/backup", '-n' ],
'failing run with no-clean option');
ok(-d "$tempdir/backup", 'backup directory was created and left behind');
rmtree("$tempdir/backup");
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "max_replication_slots = 10\n";
@@ -200,11 +207,17 @@ unlink "$pgdata/$superlongname";
# skip on Windows.
SKIP:
{
skip "symlinks not supported on Windows", 17 if ($windows_os);
skip "symlinks not supported on Windows", 18 if ($windows_os);
# Move pg_replslot out of $pgdata and create a symlink to it.
$node->stop;
# Set umask so test directories and files are created with group permissions
umask(0027);
# Enable group permissions on PGDATA
chmod_recursive("$pgdata", 0750, 0640);
rename("$pgdata/pg_replslot", "$tempdir/pg_replslot")
or BAIL_OUT "could not move $pgdata/pg_replslot";
symlink("$tempdir/pg_replslot", "$pgdata/pg_replslot")
@@ -275,6 +288,10 @@ SKIP:
"tablespace symlink was updated");
closedir $dh;
# Group access should be enabled on all backup files
ok(check_mode_recursive("$tempdir/backup1", 0750, 0640),
"check backup dir permissions");
# Unlogged relation forks other than init should not be copied
my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g;