diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml index 9d8e69056f..a9912eb418 100644 --- a/doc/src/sgml/backup.sgml +++ b/doc/src/sgml/backup.sgml @@ -1095,7 +1095,11 @@ SELECT pg_stop_backup(); and postmaster.opts, which record information about the running postmaster, not about the postmaster which will eventually use this backup. - (These files can confuse pg_ctl.) + (These files can confuse pg_ctl.) If group read + access is enabled on the data directory and an unprivileged user in the + PostgreSQL group is performing the backup, then + postmaster.pid will not be readable and must be + excluded. diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml index 585665f161..5f57b933b9 100644 --- a/doc/src/sgml/ref/initdb.sgml +++ b/doc/src/sgml/ref/initdb.sgml @@ -77,6 +77,14 @@ PostgreSQL documentation + For security reasons the new cluster created by initdb + will only be accessible by the cluster owner by default. The + 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. + + + initdb initializes the database cluster's default locale and character set encoding. The character set encoding, collation order (LC_COLLATE) and character set classes @@ -302,6 +310,17 @@ PostgreSQL documentation + + + + + Allows users in the same group as the cluster owner to read all cluster + files created by initdb. + + + + + diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index a2ebd3e21c..8f602dc705 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -137,7 +137,10 @@ postgres$ initdb -D /usr/local/pgsql/data database, it is essential that it be secured from unauthorized access. initdb therefore revokes access permissions from everyone but the - PostgreSQL user. + PostgreSQL user, and optionally, group. + Group access, when enabled, is read-only. This allows an unprivileged + user in the PostgreSQL group to take a backup of + the cluster data or perform other operations that only require read access. @@ -2237,6 +2240,15 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 + 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. + + + If the private key is protected with a passphrase, the server will prompt for the passphrase and will not start until it has been entered. diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index aeda54f00b..b780b70e53 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -587,7 +587,8 @@ PostmasterMain(int argc, char *argv[]) IsPostmasterEnvironment = true; /* - * for security, no dir or file created can be group or other accessible + * By default, no dir or file created can be group or other accessible. This + * may be modified later depending in the permissions of the data directory. */ umask(S_IRWXG | S_IRWXO); @@ -1524,25 +1525,30 @@ checkDataDir(void) #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. + * Check if the directory has correct permissions. If not, reject. * * 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)) + if (stat_buf.st_mode & PG_MODE_MASK_ALLOW_GROUP) 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)."))); + errmsg("data directory \"%s\" has invalid permissions", + DataDir), + errdetail("Permissions should be u=rwx (0700) or u=rwx,g=rx (0750)."))); +#endif + + /* + * Reset the file mode creation mask based on the mode of the data + * directory. + * + * Suppress when on Windows, because there may not be proper support + * for Unix-y file permissions. + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + umask(~(stat_buf.st_mode & PG_DIR_MODE_DEFAULT) & 0777); #endif /* Look for PG_VERSION before looking for pg_control */ diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index 342401b8dd..52a4a31ce1 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -125,12 +125,6 @@ #define FD_MINFREE 10 /* - * Default mode for created files, unless something else is specified using - * the *Perm() function variants. - */ -#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR) - -/* * A number of platforms allow individual processes to open many more files * than they can really support when *many* processes do the same thing. * This GUC parameter lets the DBA limit max_safe_fds to something less than diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index ddc850db1c..afc94c7f94 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -113,6 +113,12 @@ static const char *const auth_methods_local[] = { NULL }; +/* Default file mode creation mask */ +#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO) + +/* File mode mask that allows group access */ +#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO) + /* * these values are passed in by makefile defines */ @@ -144,7 +150,13 @@ static bool data_checksums = false; static char *xlog_dir = NULL; static char *str_wal_segment_size_mb = NULL; static int wal_segment_size_mb; +static mode_t file_mode_mask = PG_MODE_MASK_DEFAULT; + +/* File mode to use with chmod on files */ +#define PG_FILE_MODE ((S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) & ~file_mode_mask) +/* File mode to use with chmod on directories */ +#define PG_DIR_MODE ((S_IRWXU | S_IRWXG | S_IRWXO) & ~file_mode_mask) /* internal vars */ static const char *progname; @@ -1170,7 +1182,7 @@ setup_config(void) snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data); writefile(path, conflines); - if (chmod(path, S_IRUSR | S_IWUSR) != 0) + if (chmod(path, PG_FILE_MODE) != 0) { fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"), progname, path, strerror(errno)); @@ -1190,7 +1202,7 @@ setup_config(void) sprintf(path, "%s/postgresql.auto.conf", pg_data); writefile(path, autoconflines); - if (chmod(path, S_IRUSR | S_IWUSR) != 0) + if (chmod(path, PG_FILE_MODE) != 0) { fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"), progname, path, strerror(errno)); @@ -1277,7 +1289,7 @@ setup_config(void) snprintf(path, sizeof(path), "%s/pg_hba.conf", pg_data); writefile(path, conflines); - if (chmod(path, S_IRUSR | S_IWUSR) != 0) + if (chmod(path, PG_FILE_MODE) != 0) { fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"), progname, path, strerror(errno)); @@ -1293,7 +1305,7 @@ setup_config(void) snprintf(path, sizeof(path), "%s/pg_ident.conf", pg_data); writefile(path, conflines); - if (chmod(path, S_IRUSR | S_IWUSR) != 0) + if (chmod(path, PG_FILE_MODE) != 0) { fprintf(stderr, _("%s: could not change permissions of \"%s\": %s\n"), progname, path, strerror(errno)); @@ -2311,6 +2323,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 set file mode creation mask to 0027\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" @@ -2692,7 +2705,7 @@ create_data_directory(void) pg_data); fflush(stdout); - if (pg_mkdir_p(pg_data, S_IRWXU) != 0) + if (pg_mkdir_p(pg_data, PG_DIR_MODE) != 0) { fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"), progname, pg_data, strerror(errno)); @@ -2710,7 +2723,7 @@ create_data_directory(void) pg_data); fflush(stdout); - if (chmod(pg_data, S_IRWXU) != 0) + if (chmod(pg_data, PG_DIR_MODE) != 0) { fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"), progname, pg_data, strerror(errno)); @@ -2778,7 +2791,8 @@ create_xlog_or_symlink(void) xlog_dir); fflush(stdout); - if (pg_mkdir_p(xlog_dir, S_IRWXU) != 0) + printf("MASK: %04o\n", file_mode_mask); + if (pg_mkdir_p(xlog_dir, PG_DIR_MODE) != 0) { fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"), progname, xlog_dir, strerror(errno)); @@ -2796,7 +2810,8 @@ create_xlog_or_symlink(void) xlog_dir); fflush(stdout); - if (chmod(xlog_dir, S_IRWXU) != 0) + printf("MASK: %04o\n", file_mode_mask); + if (chmod(xlog_dir, PG_DIR_MODE) != 0) { fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"), progname, xlog_dir, strerror(errno)); @@ -2846,7 +2861,7 @@ create_xlog_or_symlink(void) else { /* Without -X option, just make the subdirectory normally */ - if (mkdir(subdirloc, S_IRWXU) < 0) + if (mkdir(subdirloc, PG_DIR_MODE) < 0) { fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"), progname, subdirloc, strerror(errno)); @@ -2882,7 +2897,9 @@ initialize_data_directory(void) setup_signals(); - umask(S_IRWXG | S_IRWXO); + /* Set the file mode creation mask. */ + umask(file_mode_mask); + printf(_("Initializing with file mode creation mask: %04o\n"), file_mode_mask); create_data_directory(); @@ -2902,7 +2919,7 @@ initialize_data_directory(void) * The parent directory already exists, so we only need mkdir() not * pg_mkdir_p() here, which avoids some failure modes; cf bug #13853. */ - if (mkdir(path, S_IRWXU) < 0) + if (mkdir(path, PG_DIR_MODE) < 0) { fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"), progname, path, strerror(errno)); @@ -3016,6 +3033,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} }; @@ -3057,7 +3075,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) { @@ -3150,6 +3168,8 @@ main(int argc, char *argv[]) break; case 12: str_wal_segment_size_mb = pg_strdup(optarg); + case 'g': + file_mode_mask = PG_MODE_MASK_ALLOW_GROUP; break; default: /* getopt_long already emitted a complaint */ diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index bc2d7bc063..5258584cd3 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -58,10 +58,28 @@ extern PGDLLIMPORT int max_files_per_process; extern int max_safe_fds; /* + * Default mode mask for data directory permissions that does not allow + * group execute/read. + */ +#define PG_MODE_MASK_DEFAULT (S_IRWXG | S_IRWXO) + +/* + * Optional mode mask for data directory permissions that allows group + * read/execute. + */ +#define PG_MODE_MASK_ALLOW_GROUP (S_IWGRP | S_IRWXO) + +/* + * Default mode for created files, unless something else is specified using + * the *Perm() function variants. + */ +#define PG_FILE_MODE_DEFAULT (S_IRUSR | S_IWUSR | S_IRGRP) + +/* * Default mode for created directories, unless something else is specified * using the MakeDirectoryPerm() function variant. */ -#define PG_DIR_MODE_DEFAULT (S_IRWXU) +#define PG_DIR_MODE_DEFAULT (S_IRWXU | S_IRGRP | S_IXGRP) /* * prototypes for functions in fd.c