]> granicus.if.org Git - postgresql/commitdiff
Allow group access on PGDATA
authorStephen Frost <sfrost@snowman.net>
Sat, 7 Apr 2018 21:45:39 +0000 (17:45 -0400)
committerStephen Frost <sfrost@snowman.net>
Sat, 7 Apr 2018 21:45:39 +0000 (17:45 -0400)
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

32 files changed:
doc/src/sgml/config.sgml
doc/src/sgml/ref/initdb.sgml
doc/src/sgml/ref/pg_basebackup.sgml
doc/src/sgml/ref/pg_receivewal.sgml
doc/src/sgml/ref/pg_recvlogical.sgml
doc/src/sgml/runtime.sgml
src/backend/bootstrap/bootstrap.c
src/backend/postmaster/postmaster.c
src/backend/tcop/postgres.c
src/backend/utils/init/globals.c
src/backend/utils/init/miscinit.c
src/backend/utils/misc/guc.c
src/bin/initdb/initdb.c
src/bin/initdb/t/001_initdb.pl
src/bin/pg_basebackup/pg_basebackup.c
src/bin/pg_basebackup/pg_receivewal.c
src/bin/pg_basebackup/pg_recvlogical.c
src/bin/pg_basebackup/streamutil.c
src/bin/pg_basebackup/t/010_pg_basebackup.pl
src/bin/pg_ctl/pg_ctl.c
src/bin/pg_ctl/t/001_start_stop.pl
src/bin/pg_resetwal/pg_resetwal.c
src/bin/pg_rewind/RewindTest.pm
src/bin/pg_rewind/pg_rewind.c
src/bin/pg_rewind/t/002_databases.pl
src/bin/pg_upgrade/pg_upgrade.c
src/bin/pg_upgrade/test.sh
src/common/file_perm.c
src/include/common/file_perm.h
src/include/miscadmin.h
src/test/perl/PostgresNode.pm
src/test/perl/TestLib.pm

index a189a8efc3fe52d99a06b44bd348318ea60413d7..5d5f2d23c4f27de69b7159cd1f7f6690c6b6d7a8 100644 (file)
@@ -8144,6 +8144,23 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-data-directory-mode" xreflabel="data_directory_mode">
+      <term><varname>data_directory_mode</varname> (<type>integer</type>)
+      <indexterm>
+       <primary><varname>data_directory_mode</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        On Unix systems this parameter reports the permissions of the data
+        directory defined by (<xref linkend="guc-data-directory"/>) at startup.
+        (On Microsoft Windows this parameter will always display
+        <literal>0700</literal>). See
+        <xref linkend="app-initdb-allow-group-access"/> for more information.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-debug-assertions" xreflabel="debug_assertions">
       <term><varname>debug_assertions</varname> (<type>boolean</type>)
       <indexterm>
index 826dd91f7290b30c8552096404339b644a07d9fb..10a8a86a030100097b24bea451802aae3ab7e4b7 100644 (file)
@@ -76,6 +76,14 @@ PostgreSQL documentation
    to do so.)
   </para>
 
+  <para>
+    For security reasons the new cluster created by <command>initdb</command>
+    will only be accessible by the cluster owner by default.  The
+    <option>--allow-group-access</option> option allows any user in the same
+    group as the cluster owner to read files in the cluster.  This is useful
+    for performing backups as a non-privileged user.
+  </para>
+
   <para>
    <command>initdb</command> initializes the database cluster's default
    locale and character set encoding. The character set encoding,
@@ -188,6 +196,17 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry id="app-initdb-allow-group-access" xreflabel="group access">
+      <term><option>-g</option></term>
+      <term><option>--allow-group-access</option></term>
+      <listitem>
+       <para>
+        Allows users in the same group as the cluster owner to read all cluster
+        files created by <command>initdb</command>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="app-initdb-data-checksums" xreflabel="data checksums">
       <term><option>-k</option></term>
       <term><option>--data-checksums</option></term>
index 95045669c9363781c2ebeb9e10f986dc93735e25..fc1edf48645b13bd8d2cc5d6e8bd4ee7949c598a 100644 (file)
@@ -737,6 +737,12 @@ PostgreSQL documentation
    or later.
   </para>
 
+  <para>
+   <application>pg_basebackup</application> will preserve group permissions in
+   both the <literal>plain</literal> and <literal>tar</literal> formats if group
+   permissions are enabled on the source cluster.
+  </para>
+
  </refsect1>
 
  <refsect1>
index e3f2ce1fcb73647419448ce609057a5c701a62c7..a18ddd4bff18c544395cb727993f30ab606a04f5 100644 (file)
@@ -425,6 +425,12 @@ PostgreSQL documentation
    not keep up with fetching the WAL data.
   </para>
 
+  <para>
+   <application>pg_receivewal</application> will preserve group permissions on
+   the received WAL files if group permissions are enabled on the source
+   cluster.
+  </para>
+
  </refsect1>
 
  <refsect1>
index a79ca2008499db541b9f99dc20269c078bbfef9e..141c5cddce1c9bdf0ec190de85cf7206bf03e258 100644 (file)
@@ -399,6 +399,17 @@ PostgreSQL documentation
   </para>
  </refsect1>
 
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   <application>pg_recvlogical</application> will preserve group permissions on
+   the received WAL files if group permissions are enabled on the source
+   cluster.
+  </para>
+
+ </refsect1>
+
  <refsect1>
   <title>Examples</title>
 
index 587b4305274c07f147ae2977ff69b40c296d25ca..330e38a29e9eca85a8c4711ee00c69ba1576a102 100644 (file)
@@ -137,7 +137,22 @@ postgres$ <userinput>initdb -D /usr/local/pgsql/data</userinput>
    database, it is essential that it be secured from unauthorized
    access. <command>initdb</command> therefore revokes access
    permissions from everyone but the
-   <productname>PostgreSQL</productname> user.
+   <productname>PostgreSQL</productname> user, and optionally, group.
+   Group access, when enabled, is read-only.  This allows an unprivileged
+   user in the same group as the cluster owner to take a backup of the
+   cluster data or perform other operations that only require read access.
+  </para>
+
+  <para>
+   Note that enabling or disabling group access on an existing cluster requires
+   the cluster to be shut down and the appropriate mode to be set on all
+   directories and files before restarting
+   <productname>PostgreSQL</productname>.  Otherwise, a mix of modes might
+   exist in the data directory.  For clusters that allow access only by the
+   owner, the appropriate modes are <literal>0700</literal> for directories
+   and <literal>0600</literal> for files.  For clusters that also allow
+   reads by the group, the appropriate modes are <literal>0750</literal>
+   for directories and <literal>0640</literal> for files.
   </para>
 
   <para>
@@ -2194,6 +2209,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
    member of the group that has access to those certificate and key files.
   </para>
 
+  <para>
+    If the data directory allows group read access then certificate files may
+    need to be located outside of the data directory in order to conform to the
+    security requirements outlined above.  Generally, group access is enabled
+    to allow an unprivileged user to backup the database, and in that case the
+    backup software will not be able to read the certificate files and will
+    likely error.
+  </para>
+
   <para>
    If the private key is protected with a passphrase, the
    server will prompt for the passphrase and will not start until it has
index 644084d1c3ba9c80d255e2b86c47384b42f2d8e4..59cd4b17f326418256bb9eb6088c3e77d4a5f80c 100644 (file)
@@ -349,13 +349,15 @@ AuxiliaryProcessMain(int argc, char *argv[])
                        proc_exit(1);
        }
 
-       /* Validate we have been given a reasonable-looking DataDir */
-       Assert(DataDir);
-       ValidatePgVersion(DataDir);
-
-       /* Change into DataDir (if under postmaster, should be done already) */
+       /*
+        * Validate we have been given a reasonable-looking DataDir and change
+        * into it (if under postmaster, should be done already).
+        */
        if (!IsUnderPostmaster)
+       {
+               checkDataDir();
                ChangeToDataDir();
+       }
 
        /* If standalone, create lockfile for data directory */
        if (!IsUnderPostmaster)
index 10afecffb37e160c06cf66311d91dae14550cc16..ccf63e93097f3d062c826f73103ede0b69b54cfc 100644 (file)
@@ -391,7 +391,7 @@ static DNSServiceRef bonjour_sdref = NULL;
 static void CloseServerPorts(int status, Datum arg);
 static void unlink_external_pid_file(int status, Datum arg);
 static void getInstallationPaths(const char *argv0);
-static void checkDataDir(void);
+static void checkControlFile(void);
 static Port *ConnCreate(int serverFd);
 static void ConnFree(Port *port);
 static void reset_shared(int port);
@@ -588,7 +588,12 @@ PostmasterMain(int argc, char *argv[])
        IsPostmasterEnvironment = true;
 
        /*
-        * for security, no dir or file created can be group or other accessible
+        * We should not be creating any files or directories before we check the
+        * data directory (see checkDataDir()), but just in case set the umask to
+        * the most restrictive (owner-only) permissions.
+        *
+        * checkDataDir() will reset the umask based on the data directory
+        * permissions.
         */
        umask(PG_MODE_MASK_OWNER);
 
@@ -877,6 +882,9 @@ PostmasterMain(int argc, char *argv[])
        /* Verify that DataDir looks reasonable */
        checkDataDir();
 
+       /* Check that pg_control exists */
+       checkControlFile();
+
        /* And switch working directory into it */
        ChangeToDataDir();
 
@@ -1469,82 +1477,17 @@ getInstallationPaths(const char *argv0)
         */
 }
 
-
 /*
- * Validate the proposed data directory
+ * Check that pg_control exists in the correct location in the data directory.
+ *
+ * No attempt is made to validate the contents of pg_control here.  This is
+ * just a sanity check to see if we are looking at a real data directory.
  */
 static void
-checkDataDir(void)
+checkControlFile(void)
 {
        char            path[MAXPGPATH];
        FILE       *fp;
-       struct stat stat_buf;
-
-       Assert(DataDir);
-
-       if (stat(DataDir, &stat_buf) != 0)
-       {
-               if (errno == ENOENT)
-                       ereport(FATAL,
-                                       (errcode_for_file_access(),
-                                        errmsg("data directory \"%s\" does not exist",
-                                                       DataDir)));
-               else
-                       ereport(FATAL,
-                                       (errcode_for_file_access(),
-                                        errmsg("could not read permissions of directory \"%s\": %m",
-                                                       DataDir)));
-       }
-
-       /* eventual chdir would fail anyway, but let's test ... */
-       if (!S_ISDIR(stat_buf.st_mode))
-               ereport(FATAL,
-                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                errmsg("specified data directory \"%s\" is not a directory",
-                                               DataDir)));
-
-       /*
-        * Check that the directory belongs to my userid; if not, reject.
-        *
-        * This check is an essential part of the interlock that prevents two
-        * postmasters from starting in the same directory (see CreateLockFile()).
-        * Do not remove or weaken it.
-        *
-        * XXX can we safely enable this check on Windows?
-        */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-       if (stat_buf.st_uid != geteuid())
-               ereport(FATAL,
-                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                errmsg("data directory \"%s\" has wrong ownership",
-                                               DataDir),
-                                errhint("The server must be started by the user that owns the data directory.")));
-#endif
-
-       /*
-        * Check if the directory has group or world access.  If so, reject.
-        *
-        * It would be possible to allow weaker constraints (for example, allow
-        * group access) but we cannot make a general assumption that that is
-        * okay; for example there are platforms where nearly all users
-        * customarily belong to the same group.  Perhaps this test should be
-        * configurable.
-        *
-        * XXX temporarily suppress check when on Windows, because there may not
-        * be proper support for Unix-y file permissions.  Need to think of a
-        * reasonable check to apply on Windows.
-        */
-#if !defined(WIN32) && !defined(__CYGWIN__)
-       if (stat_buf.st_mode & (S_IRWXG | S_IRWXO))
-               ereport(FATAL,
-                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-                                errmsg("data directory \"%s\" has group or world access",
-                                               DataDir),
-                                errdetail("Permissions should be u=rwx (0700).")));
-#endif
-
-       /* Look for PG_VERSION before looking for pg_control */
-       ValidatePgVersion(DataDir);
 
        snprintf(path, sizeof(path), "%s/global/pg_control", DataDir);
 
index 7bdecc32ec0c648b4a8240fa1f6fc13d740a339c..5095a4f6867dff9a6bc52df301c288374896d9a2 100644 (file)
@@ -3731,8 +3731,7 @@ PostgresMain(int argc, char *argv[],
                 * Validate we have been given a reasonable-looking DataDir (if under
                 * postmaster, assume postmaster did this already).
                 */
-               Assert(DataDir);
-               ValidatePgVersion(DataDir);
+               checkDataDir();
 
                /* Change into DataDir (if under postmaster, was done already) */
                ChangeToDataDir();
index c1f0441b0817f382eeb2174233b7a9c6f1cbf891..0a3163398f3f2fb9f7ff864dde85f52556fa4bec 100644 (file)
  *
  *-------------------------------------------------------------------------
  */
+#include <sys/stat.h>
+
 #include "postgres.h"
 
+#include "common/file_perm.h"
 #include "libpq/libpq-be.h"
 #include "libpq/pqcomm.h"
 #include "miscadmin.h"
@@ -59,6 +62,12 @@ struct Latch *MyLatch;
  */
 char      *DataDir = NULL;
 
+/*
+ * Mode of the data directory.  The default is 0700 but may it be changed in
+ * checkDataDir() to 0750 if the data directory actually has that mode.
+ */
+int                    data_directory_mode = PG_DIR_MODE_OWNER;
+
 char           OutputFileName[MAXPGPATH];      /* debugging output file */
 
 char           my_exec_path[MAXPGPATH];        /* full path to my executable */
index f8f08f3f88bfe9190466e1223790db14a8037ed4..03b28c3604a9b65ec6f114f268db176f62361354 100644 (file)
@@ -88,6 +88,100 @@ SetDatabasePath(const char *path)
        DatabasePath = MemoryContextStrdup(TopMemoryContext, path);
 }
 
+/*
+ * Validate the proposed data directory.
+ *
+ * Also initialize file and directory create modes and mode mask.
+ */
+void
+checkDataDir(void)
+{
+       struct stat stat_buf;
+
+       Assert(DataDir);
+
+       if (stat(DataDir, &stat_buf) != 0)
+       {
+               if (errno == ENOENT)
+                       ereport(FATAL,
+                                       (errcode_for_file_access(),
+                                        errmsg("data directory \"%s\" does not exist",
+                                                       DataDir)));
+               else
+                       ereport(FATAL,
+                                       (errcode_for_file_access(),
+                                        errmsg("could not read permissions of directory \"%s\": %m",
+                                                       DataDir)));
+       }
+
+       /* eventual chdir would fail anyway, but let's test ... */
+       if (!S_ISDIR(stat_buf.st_mode))
+               ereport(FATAL,
+                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                errmsg("specified data directory \"%s\" is not a directory",
+                                               DataDir)));
+
+       /*
+        * Check that the directory belongs to my userid; if not, reject.
+        *
+        * This check is an essential part of the interlock that prevents two
+        * postmasters from starting in the same directory (see CreateLockFile()).
+        * Do not remove or weaken it.
+        *
+        * XXX can we safely enable this check on Windows?
+        */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+       if (stat_buf.st_uid != geteuid())
+               ereport(FATAL,
+                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                errmsg("data directory \"%s\" has wrong ownership",
+                                               DataDir),
+                                errhint("The server must be started by the user that owns the data directory.")));
+#endif
+
+       /*
+        * Check if the directory has correct permissions.  If not, reject.
+        *
+        * Only two possible modes are allowed, 0700 and 0750.  The latter mode
+        * indicates that group read/execute should be allowed on all newly
+        * created files and directories.
+        *
+        * XXX temporarily suppress check when on Windows, because there may not
+        * be proper support for Unix-y file permissions.  Need to think of a
+        * reasonable check to apply on Windows.
+        */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+       if (stat_buf.st_mode & PG_MODE_MASK_GROUP)
+               ereport(FATAL,
+                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                errmsg("data directory \"%s\" has invalid permissions",
+                                               DataDir),
+                                errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).")));
+#endif
+
+       /*
+        * Reset creation modes and mask based on the mode of the data directory.
+        *
+        * The mask was set earlier in startup to disallow group permissions on
+        * newly created files and directories.  However, if group read/execute
+        * are present on the data directory then modify the create modes and mask
+        * to allow group read/execute on newly created files and directories and
+        * set the data_directory_mode GUC.
+        *
+        * Suppress when on Windows, because there may not be proper support for
+        * Unix-y file permissions.
+        */
+#if !defined(WIN32) && !defined(__CYGWIN__)
+       SetDataDirectoryCreatePerm(stat_buf.st_mode);
+
+       umask(pg_mode_mask);
+       data_directory_mode = pg_dir_create_mode;
+#endif
+
+       /* Check for PG_VERSION */
+       ValidatePgVersion(DataDir);
+}
+
 /*
  * Set data directory, but make sure it's an absolute path.  Use this,
  * never set DataDir directly.
@@ -829,7 +923,7 @@ CreateLockFile(const char *filename, bool amPostmaster,
                /*
                 * Try to create the lock file --- O_EXCL makes this atomic.
                 *
-                * Think not to make the file protection weaker than 0600.  See
+                * Think not to make the file protection weaker than 0600/0640.  See
                 * comments below.
                 */
                fd = open(filename, O_RDWR | O_CREAT | O_EXCL, pg_file_create_mode);
@@ -899,17 +993,14 @@ CreateLockFile(const char *filename, bool amPostmaster,
                 * implies that the existing process has a different userid than we
                 * do, which means it cannot be a competing postmaster.  A postmaster
                 * cannot successfully attach to a data directory owned by a userid
-                * other than its own.  (This is now checked directly in
-                * checkDataDir(), but has been true for a long time because of the
-                * restriction that the data directory isn't group- or
-                * world-accessible.)  Also, since we create the lockfiles mode 600,
-                * we'd have failed above if the lockfile belonged to another userid
-                * --- which means that whatever process kill() is reporting about
-                * isn't the one that made the lockfile.  (NOTE: this last
-                * consideration is the only one that keeps us from blowing away a
-                * Unix socket file belonging to an instance of Postgres being run by
-                * someone else, at least on machines where /tmp hasn't got a
-                * stickybit.)
+                * other than its own, as enforced in checkDataDir(). Also, since we
+                * create the lockfiles mode 0600/0640, we'd have failed above if the
+                * lockfile belonged to another userid --- which means that whatever
+                * process kill() is reporting about isn't the one that made the
+                * lockfile.  (NOTE: this last consideration is the only one that
+                * keeps us from blowing away a Unix socket file belonging to an
+                * instance of Postgres being run by someone else, at least on
+                * machines where /tmp hasn't got a stickybit.)
                 */
                if (other_pid != my_pid && other_pid != my_p_pid &&
                        other_pid != my_gp_pid)
index 71c2b4eff1613bbdb333ffd109307b21118d0009..8441837e0ff2cf0a064325b4f99a4a72b3d2ebd2 100644 (file)
@@ -194,6 +194,7 @@ static void assign_application_name(const char *newval, void *extra);
 static bool check_cluster_name(char **newval, void **extra, GucSource source);
 static const char *show_unix_socket_permissions(void);
 static const char *show_log_file_mode(void);
+static const char *show_data_directory_mode(void);
 
 /* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -2044,6 +2045,21 @@ static struct config_int ConfigureNamesInt[] =
                NULL, NULL, show_log_file_mode
        },
 
+
+       {
+               {"data_directory_mode", PGC_INTERNAL, PRESET_OPTIONS,
+                       gettext_noop("Mode of the data directory."),
+                       gettext_noop("The parameter value is a numeric mode specification "
+                                                "in the form accepted by the chmod and umask system "
+                                                "calls. (To use the customary octal format the number "
+                                                "must start with a 0 (zero).)"),
+                       GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+               },
+               &data_directory_mode,
+               0700, 0000, 0777,
+               NULL, NULL, show_data_directory_mode
+       },
+
        {
                {"work_mem", PGC_USERSET, RESOURCES_MEM,
                        gettext_noop("Sets the maximum memory to be used for query workspaces."),
@@ -10744,4 +10760,13 @@ show_log_file_mode(void)
        return buf;
 }
 
+static const char *
+show_data_directory_mode(void)
+{
+       static char buf[12];
+
+       snprintf(buf, sizeof(buf), "%04o", data_directory_mode);
+       return buf;
+}
+
 #include "guc-file.c"
index 3765548a249c3160fa8b715ec6f228bdc61ce85d..ec1f0c4bff1e75d8596ce4e0cb44342789b16a89 100644 (file)
@@ -1168,6 +1168,19 @@ setup_config(void)
                                                                  "password_encryption = scram-sha-256");
        }
 
+       /*
+        * If group access has been enabled for the cluster then it makes sense to
+        * ensure that the log files also allow group access.  Otherwise a backup
+        * from a user in the group would fail if the log files were not
+        * relocated.
+        */
+       if (pg_dir_create_mode == PG_DIR_MODE_GROUP)
+       {
+               conflines = replace_token(conflines,
+                                                                 "#log_file_mode = 0600",
+                                                                 "log_file_mode = 0640");
+       }
+
        snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
 
        writefile(path, conflines);
@@ -2312,6 +2325,7 @@ usage(const char *progname)
        printf(_("      --auth-local=METHOD   default authentication method for local-socket connections\n"));
        printf(_(" [-D, --pgdata=]DATADIR     location for this database cluster\n"));
        printf(_("  -E, --encoding=ENCODING   set default encoding for new databases\n"));
+       printf(_("  -g, --allow-group-access  allow group read/execute on data directory\n"));
        printf(_("      --locale=LOCALE       set default locale for new databases\n"));
        printf(_("      --lc-collate=, --lc-ctype=, --lc-messages=LOCALE\n"
                         "      --lc-monetary=, --lc-numeric=, --lc-time=LOCALE\n"
@@ -2883,8 +2897,13 @@ initialize_data_directory(void)
 
        setup_signals();
 
-       /* Set dir/file mode mask */
-       umask(PG_MODE_MASK_OWNER);
+       /*
+        * Set mask based on requested PGDATA permissions.  pg_mode_mask, and
+        * friends like pg_dir_create_mode, are set to owner-only by default and
+        * then updated if -g is passed in by calling SetDataDirectoryCreatePerm()
+        * when parsing our options (see above).
+        */
+       umask(pg_mode_mask);
 
        create_data_directory();
 
@@ -3018,6 +3037,7 @@ main(int argc, char *argv[])
                {"waldir", required_argument, NULL, 'X'},
                {"wal-segsize", required_argument, NULL, 12},
                {"data-checksums", no_argument, NULL, 'k'},
+               {"allow-group-access", no_argument, NULL, 'g'},
                {NULL, 0, NULL, 0}
        };
 
@@ -3059,7 +3079,7 @@ main(int argc, char *argv[])
 
        /* process command-line options */
 
-       while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:", long_options, &option_index)) != -1)
+       while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1)
        {
                switch (c)
                {
@@ -3153,6 +3173,9 @@ main(int argc, char *argv[])
                        case 12:
                                str_wal_segment_size_mb = pg_strdup(optarg);
                                break;
+                       case 'g':
+                               SetDataDirectoryCreatePerm(PG_DIR_MODE_GROUP);
+                               break;
                        default:
                                /* getopt_long already emitted a complaint */
                                fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
index 9dfb2ed96ff20b3e1cb15609557fd7aacd6bdef7..8609d2ecbcca34a5b5d18b71d47a9aad337a016f 100644 (file)
@@ -4,9 +4,11 @@
 
 use strict;
 use warnings;
+use Fcntl ':mode';
+use File::stat qw{lstat};
 use PostgresNode;
 use TestLib;
-use Test::More tests => 16;
+use Test::More tests => 18;
 
 my $tempdir = TestLib::tempdir;
 my $xlogdir = "$tempdir/pgxlog";
@@ -57,3 +59,19 @@ mkdir $datadir;
 }
 command_ok([ 'initdb', '-S', $datadir ], 'sync only');
 command_fails([ 'initdb', $datadir ], 'existing data directory');
+
+# Check group access on PGDATA
+SKIP:
+{
+       skip "unix-style permissions not supported on Windows", 2 if ($windows_os);
+
+       # Init a new db with group access
+       my $datadir_group = "$tempdir/data_group";
+
+       command_ok(
+               [ 'initdb', '-g', $datadir_group ],
+               'successful creation with group access');
+
+       ok(check_mode_recursive($datadir_group, 0750, 0640),
+               'check PGDATA permissions');
+}
index 32b41e313cc35b9638b3531128b4a8aa170cdcdd..58f780c0691249bf30f0f5e99d6bdd326b126dfe 100644 (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);
index b82e073b862f02d0262e68ecf4ef30f66bd3da3a..535355ebdad28cbc44cece4f0cebcfc6676d07f9 100644 (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);
index 0866395944c1b2dfd05b370c5bd88f56cedb03b3..ef85c9af4c77d319d8fd9e52929082b26f821e26 100644 (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)
        {
index 1438f368edca2485c87f4b7687d834037dfc8df6..4fd536931b197e9858dbda7f1366d4c60d74cb93 100644 (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"
 
 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:
index ac5bb99f1b84bd913ab1c51b1ba4a1e5410c3448..f502a2e3c71ec90b22289f97d5b95da57d9e2084 100644 (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;
 
index 5ede385e6ab73e1396783222db3936ca4204359b..143021de05f91783d83942a8590d4601e8d8a223 100644 (file)
@@ -2171,7 +2171,7 @@ main(int argc, char **argv)
         */
        argv0 = argv[0];
 
-       /* Set dir/file mode mask */
+       /* Set restrictive mode mask until PGDATA permissions are checked */
        umask(PG_MODE_MASK_OWNER);
 
        /* support --help and --version even if invoked as root */
@@ -2407,6 +2407,16 @@ main(int argc, char **argv)
                snprintf(version_file, MAXPGPATH, "%s/PG_VERSION", pg_data);
                snprintf(pid_file, MAXPGPATH, "%s/postmaster.pid", pg_data);
                snprintf(backup_file, MAXPGPATH, "%s/backup_label", pg_data);
+
+               /*
+                * Set mask based on PGDATA permissions,
+                *
+                * Don't error here if the data directory cannot be stat'd. This is
+                * handled differently based on the command and we don't want to
+                * interfere with that logic.
+                */
+               if (GetDataDirectoryCreatePerm(pg_data))
+                       umask(pg_mode_mask);
        }
 
        switch (ctl_command)
index 067a084c87c3c442cfa0cc7a37c89d0af024e182..2f9dfa7b81ba6229fb48723f46e82b1727ef721c 100644 (file)
@@ -2,9 +2,11 @@ use strict;
 use warnings;
 
 use Config;
+use Fcntl ':mode';
+use File::stat qw{lstat};
 use PostgresNode;
 use TestLib;
-use Test::More tests => 21;
+use Test::More tests => 24;
 
 my $tempdir       = TestLib::tempdir;
 my $tempdir_short = TestLib::tempdir_short;
@@ -74,6 +76,27 @@ SKIP:
        ok(check_mode_recursive("$tempdir/data", 0700, 0600));
 }
 
+# Log file for group access test
+$logFileName = "$tempdir/data/perm-test-640.log";
+
+SKIP:
+{
+       skip "group access not supported on Windows", 3 if ($windows_os);
+
+       system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data";
+
+       # Change the data dir mode so log file will be created with group read
+       # privileges on the next start
+       chmod_recursive("$tempdir/data", 0750, 0640);
+
+       command_ok(
+               [ 'pg_ctl', 'start', '-D', "$tempdir/data", '-l', $logFileName ],
+               'start server to check group permissions');
+
+       ok(-f $logFileName);
+       ok(check_mode_recursive("$tempdir/data", 0750, 0640));
+}
+
 command_ok([ 'pg_ctl', 'restart', '-D', "$tempdir/data" ],
        'pg_ctl restart with server running');
 
index bdf71886ee2694dd0be49e9bc68cd6cacbc808fd..8a0a805f1ed56c567e97feb8cc1b5f7a8d04b5c7 100644 (file)
@@ -363,6 +363,16 @@ main(int argc, char *argv[])
                exit(1);
        }
 
+       /* Set mask based on PGDATA permissions */
+       if (!GetDataDirectoryCreatePerm(DataDir))
+       {
+               fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+                               progname, DataDir);
+               exit(1);
+       }
+
+       umask(pg_mode_mask);
+
        /* Check that data directory matches our server version */
        CheckDataVersion();
 
index 7b632c7dcdb097e51a47abfd6fa1a7703f160c8e..63d9bd517dfb67153f0ba4dca4109fcb8ebd61cd 100644 (file)
@@ -115,11 +115,13 @@ sub check_query
 
 sub setup_cluster
 {
-       my $extra_name = shift;
+       my $extra_name = shift;         # Used to differentiate clusters
+       my $extra = shift;                      # Extra params for initdb
 
        # Initialize master, data checksums are mandatory
        $node_master = get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
-       $node_master->init(allows_streaming => 1);
+       $node_master->init(
+               allows_streaming => 1, extra => $extra);
        # Set wal_keep_segments to prevent WAL segment recycling after enforced
        # checkpoints in the tests.
        $node_master->append_conf('postgresql.conf', qq(
@@ -237,7 +239,8 @@ sub run_pg_rewind
                "$tmp_folder/master-postgresql.conf.tmp",
                "$master_pgdata/postgresql.conf");
 
-       chmod(0600, "$master_pgdata/postgresql.conf")
+       chmod($node_master->group_access() ? 0640 : 0600,
+                 "$master_pgdata/postgresql.conf")
                or BAIL_OUT(
                        "unable to set permissions for $master_pgdata/postgresql.conf");
 
index 72ab2f8d5ebaa604b63827517ef836e7144333ea..b9ea6a4c213aba281c0f34e3ef5d4c1feb9de439 100644 (file)
@@ -24,6 +24,7 @@
 #include "access/xlog_internal.h"
 #include "catalog/catversion.h"
 #include "catalog/pg_control.h"
+#include "common/file_perm.h"
 #include "common/restricted_token.h"
 #include "getopt_long.h"
 #include "storage/bufpage.h"
@@ -185,6 +186,16 @@ main(int argc, char **argv)
                exit(1);
        }
 
+       /* Set mask based on PGDATA permissions */
+       if (!GetDataDirectoryCreatePerm(datadir_target))
+       {
+               fprintf(stderr, _("%s: unable to read permissions from \"%s\"\n"),
+                               progname, datadir_target);
+               exit(1);
+       }
+
+       umask(pg_mode_mask);
+
        /*
         * Don't allow pg_rewind to be run as root, to avoid overwriting the
         * ownership of files in the data directory. We need only check for root
index 37cdd712f3177d99b5c02f56ba51df3e314c7905..c364965d3ae7d74d410d3c315b093b67f129140f 100644 (file)
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 6;
 
 use RewindTest;
 
@@ -9,7 +9,7 @@ sub run_test
 {
        my $test_mode = shift;
 
-       RewindTest::setup_cluster($test_mode);
+       RewindTest::setup_cluster($test_mode, ['-g']);
        RewindTest::start_master();
 
        # Create a database in master.
@@ -42,6 +42,15 @@ template1
 ),
                'database names');
 
+       # Permissions on PGDATA should have group permissions
+       SKIP:
+       {
+               skip "unix-style permissions not supported on Windows", 1 if ($windows_os);
+
+               ok(check_mode_recursive($node_master->data_dir(), 0750, 0640),
+                       'check PGDATA permissions');
+       }
+
        RewindTest::clean_rewind_test();
 }
 
index 1d351881435b618a519aeec8259192f3b3164f14..cc8e8c94c5e3484b72aa0db2eae05c6497d21d1d 100644 (file)
@@ -79,7 +79,7 @@ main(int argc, char **argv)
 
        set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));
 
-       /* Ensure that all files created by pg_upgrade are non-world-readable */
+       /* Set default restrictive mask until new cluster permissions are read */
        umask(PG_MODE_MASK_OWNER);
 
        parseCommandLine(argc, argv);
@@ -100,6 +100,16 @@ main(int argc, char **argv)
 
        check_cluster_compatibility(live_check);
 
+       /* Set mask based on PGDATA permissions */
+       if (!GetDataDirectoryCreatePerm(new_cluster.pgdata))
+       {
+               pg_log(PG_FATAL, "unable to read permissions from \"%s\"\n",
+                          new_cluster.pgdata);
+               exit(1);
+       }
+
+       umask(pg_mode_mask);
+
        check_and_dump_old_cluster(live_check);
 
 
index 574639d47e56f57ae69dac6fba8ba68185f990d2..24631a91c614e83a2c1867bcaced7e2677441db1 100644 (file)
@@ -20,9 +20,9 @@ unset MAKELEVEL
 # Run a given "initdb" binary and overlay the regression testing
 # authentication configuration.
 standard_initdb() {
-       # To increase coverage of non-standard segment size without
-       # increase test runtime, run these tests with a lower setting.
-       "$1" -N --wal-segsize 1
+       # To increase coverage of non-standard segment size and group access
+       # without increasing test runtime, run these tests with a custom setting.
+       "$1" -N --wal-segsize 1 -g
        if [ -n "$TEMP_CONFIG" -a -r "$TEMP_CONFIG" ]
        then
                cat "$TEMP_CONFIG" >> "$PGDATA/postgresql.conf"
@@ -230,14 +230,14 @@ standard_initdb 'initdb'
 
 pg_upgrade $PG_UPGRADE_OPTS -d "${PGDATA}.old" -D "${PGDATA}" -b "$oldbindir" -B "$bindir" -p "$PGPORT" -P "$PGPORT"
 
-# make sure all directories and files have correct permissions
-if [ $(find ${PGDATA} -type f ! -perm 600 | wc -l) -ne 0 ]; then
-       echo "files in PGDATA with permission != 600";
+# make sure all directories and files have group permissions
+if [ $(find ${PGDATA} -type f ! -perm 640 | wc -l) -ne 0 ]; then
+       echo "files in PGDATA with permission != 640";
        exit 1;
 fi
 
-if [ $(find ${PGDATA} -type d ! -perm 700 | wc -l) -ne 0 ]; then
-       echo "directories in PGDATA with permission != 700";
+if [ $(find ${PGDATA} -type d ! -perm 750 | wc -l) -ne 0 ]; then
+       echo "directories in PGDATA with permission != 750";
        exit 1;
 fi
 
index fdfbb9a44c4a0f61f24e8e1ce9e198e2b83edc64..d640d6a1fdd66c8f81ccff52db0c2c0338c11ac4 100644 (file)
  */
 #include <sys/stat.h>
 
+#include "c.h"
 #include "common/file_perm.h"
 
 /* Modes for creating directories and files in the data directory */
-int pg_dir_create_mode = PG_DIR_MODE_OWNER;
-int pg_file_create_mode = PG_FILE_MODE_OWNER;
+int                    pg_dir_create_mode = PG_DIR_MODE_OWNER;
+int                    pg_file_create_mode = PG_FILE_MODE_OWNER;
+
+/*
+ * Mode mask to pass to umask().  This is more of a preventative measure since
+ * all file/directory creates should be performed using the create modes above.
+ */
+int                    pg_mode_mask = PG_MODE_MASK_OWNER;
+
+/*
+ * Set create modes and mask to use when writing to PGDATA based on the data
+ * directory mode passed.  If group read/execute are present in the mode, then
+ * create modes and mask will be relaxed to allow group read/execute on all
+ * newly created files and directories.
+ */
+void
+SetDataDirectoryCreatePerm(int dataDirMode)
+{
+       /* If the data directory mode has group access */
+       if ((PG_DIR_MODE_GROUP & dataDirMode) == PG_DIR_MODE_GROUP)
+       {
+               pg_dir_create_mode = PG_DIR_MODE_GROUP;
+               pg_file_create_mode = PG_FILE_MODE_GROUP;
+               pg_mode_mask = PG_MODE_MASK_GROUP;
+       }
+       /* Else use default permissions */
+       else
+       {
+               pg_dir_create_mode = PG_DIR_MODE_OWNER;
+               pg_file_create_mode = PG_FILE_MODE_OWNER;
+               pg_mode_mask = PG_MODE_MASK_OWNER;
+       }
+}
+
+#ifdef FRONTEND
+
+/*
+ * Get the create modes and mask to use when writing to PGDATA by examining the
+ * mode of the PGDATA directory and calling SetDataDirectoryCreatePerm().
+ *
+ * Errors are not handled here and should be reported by the application when
+ * false is returned.
+ *
+ * Suppress when on Windows, because there may not be proper support for Unix-y
+ * file permissions.
+ */
+bool
+GetDataDirectoryCreatePerm(const char *dataDir)
+{
+#if !defined(WIN32) && !defined(__CYGWIN__)
+       struct stat statBuf;
+
+       /*
+        * If an error occurs getting the mode then return false.  The caller is
+        * responsible for generating an error, if appropriate, indicating that we
+        * were unable to access the data directory.
+        */
+       if (stat(dataDir, &statBuf) == -1)
+               return false;
+
+       /* Set permissions */
+       SetDataDirectoryCreatePerm(statBuf.st_mode);
+
+       return true;
+#endif                                                 /* !defined(WIN32) && !defined(__CYGWIN__) */
+}
+
+
+#endif                                                 /* FRONTEND */
index 37631a7191d52ba96c07e3b0a5b878ce425dc273..3090f789317be42c04ccb1f6b3ec8844970e4b5a 100644 (file)
  */
 #define PG_MODE_MASK_OWNER                 (S_IRWXG | S_IRWXO)
 
+/*
+ * Mode mask for data directory permissions that also allows group read/execute.
+ */
+#define PG_MODE_MASK_GROUP                     (S_IWGRP | S_IRWXO)
+
 /* Default mode for creating directories */
 #define PG_DIR_MODE_OWNER                      S_IRWXU
 
+/* Mode for creating directories that allows group read/execute */
+#define PG_DIR_MODE_GROUP                      (S_IRWXU | S_IRGRP | S_IXGRP)
+
 /* Default mode for creating files */
 #define PG_FILE_MODE_OWNER                 (S_IRUSR | S_IWUSR)
 
+/* Mode for creating files that allows group read */
+#define PG_FILE_MODE_GROUP                     (S_IRUSR | S_IWUSR | S_IRGRP)
+
 /* Modes for creating directories and files in the data directory */
-extern int pg_dir_create_mode;
-extern int pg_file_create_mode;
+extern int     pg_dir_create_mode;
+extern int     pg_file_create_mode;
+
+/* Mode mask to pass to umask() */
+extern int     pg_mode_mask;
+
+/* Set permissions and mask based on the provided mode */
+extern void SetDataDirectoryCreatePerm(int dataDirMode);
+
+/* Set permissions and mask based on the mode of the data directory */
+extern bool GetDataDirectoryCreatePerm(const char *dataDir);
 
 #endif                                                 /* FILE_PERM_H */
index b5ad8419684afbec3e71985c2736c4c2d8a58ea4..e167ee8fcbec51cb9075bad6d21553bb8d774127 100644 (file)
@@ -153,6 +153,7 @@ extern PGDLLIMPORT bool IsBinaryUpgrade;
 extern PGDLLIMPORT bool ExitOnAnyError;
 
 extern PGDLLIMPORT char *DataDir;
+extern PGDLLIMPORT int data_directory_mode;
 
 extern PGDLLIMPORT int NBuffers;
 extern PGDLLIMPORT int MaxBackends;
@@ -323,6 +324,7 @@ extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid     GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
 
+extern void checkDataDir(void);
 extern void SetDataDir(const char *dir);
 extern void ChangeToDataDir(void);
 
index 76e571b98c8cd5b7fab0db0ab40ee99bdffa9e0c..1bd91524d7ecd83ce6dbf0121dc0712d985d7219 100644 (file)
@@ -86,9 +86,11 @@ use Carp;
 use Config;
 use Cwd;
 use Exporter 'import';
+use Fcntl qw(:mode);
 use File::Basename;
 use File::Path qw(rmtree);
 use File::Spec;
+use File::stat qw(stat);
 use File::Temp ();
 use IPC::Run;
 use RecursiveCopy;
@@ -269,6 +271,26 @@ sub connstr
 
 =pod
 
+=item $node->group_access()
+
+Does the data dir allow group access?
+
+=cut
+
+sub group_access
+{
+       my ($self) = @_;
+
+       my $dir_stat = stat($self->data_dir);
+
+       defined($dir_stat)
+               or die('unable to stat ' . $self->data_dir);
+
+       return (S_IMODE($dir_stat->mode) == 0750);
+}
+
+=pod
+
 =item $node->data_dir()
 
 Returns the path to the data directory. postgresql.conf and pg_hba.conf are
@@ -460,6 +482,9 @@ sub init
        }
        close $conf;
 
+    chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+        or die("unable to set permissions for $pgdata/postgresql.conf");
+
        $self->set_replication_conf if $params{allows_streaming};
        $self->enable_archiving     if $params{has_archiving};
 }
@@ -485,7 +510,7 @@ sub append_conf
 
        TestLib::append_to_file($conffile, $str . "\n");
 
-    chmod(0600, $conffile)
+    chmod($self->group_access() ? 0640 : 0600, $conffile)
         or die("unable to set permissions for $conffile");
 }
 
index 93610e4bc400098599387f2edd20d7490839f808..8047404efd466bb05b0b133f49b85e253c5586e9 100644 (file)
@@ -31,6 +31,7 @@ our @EXPORT = qw(
   slurp_file
   append_to_file
   check_mode_recursive
+  chmod_recursive
   check_pg_config
   system_or_bail
   system_log
@@ -313,6 +314,30 @@ sub check_mode_recursive
        return $result;
 }
 
+# Change mode recursively on a directory
+sub chmod_recursive
+{
+       my ($dir, $dir_mode, $file_mode) = @_;
+
+       find
+       (
+               {follow_fast => 1,
+               wanted =>
+                       sub
+                       {
+                               my $file_stat = stat($File::Find::name);
+
+                               if (defined($file_stat))
+                               {
+                                       chmod(S_ISDIR($file_stat->mode) ? $dir_mode : $file_mode,
+                                                 $File::Find::name)
+                                               or die "unable to chmod $File::Find::name";
+                               }
+                       }},
+               $dir
+       );
+}
+
 # Check presence of a given regexp within pg_config.h for the installation
 # where tests are running, returning a match status result depending on
 # that.