mirror of
https://github.com/postgres/postgres.git
synced 2025-07-18 17:42:25 +03:00
This change makes libpq apply the same private-key-file ownership and permissions checks that we have used in the backend since commit9a83564c5
. Namely, that the private key can be owned by either the current user or root (with different file permissions allowed in the two cases). This allows system-wide management of key files, which is just as sensible on the client side as the server, particularly when the client is itself some application daemon. Sync the comments about this between libpq and the backend, too. Back-patch ofa59c79564
and50f03473e
into all supported branches. David Steele Discussion: https://postgr.es/m/f4b7bc55-97ac-9e69-7398-335e212f7743@pgmasters.net
194 lines
4.8 KiB
C
194 lines
4.8 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* be-secure-common.c
|
|
*
|
|
* common implementation-independent SSL support code
|
|
*
|
|
* While be-secure.c contains the interfaces that the rest of the
|
|
* communications code calls, this file contains support routines that are
|
|
* used by the library-specific implementations such as be-secure-openssl.c.
|
|
*
|
|
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/libpq/be-secure-common.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "libpq/libpq.h"
|
|
#include "storage/fd.h"
|
|
|
|
/*
|
|
* Run ssl_passphrase_command
|
|
*
|
|
* prompt will be substituted for %p. is_server_start determines the loglevel
|
|
* of error messages.
|
|
*
|
|
* The result will be put in buffer buf, which is of size size. The return
|
|
* value is the length of the actual result.
|
|
*/
|
|
int
|
|
run_ssl_passphrase_command(const char *prompt, bool is_server_start, char *buf, int size)
|
|
{
|
|
int loglevel = is_server_start ? ERROR : LOG;
|
|
StringInfoData command;
|
|
char *p;
|
|
FILE *fh;
|
|
int pclose_rc;
|
|
size_t len = 0;
|
|
|
|
Assert(prompt);
|
|
Assert(size > 0);
|
|
buf[0] = '\0';
|
|
|
|
initStringInfo(&command);
|
|
|
|
for (p = ssl_passphrase_command; *p; p++)
|
|
{
|
|
if (p[0] == '%')
|
|
{
|
|
switch (p[1])
|
|
{
|
|
case 'p':
|
|
appendStringInfoString(&command, prompt);
|
|
p++;
|
|
break;
|
|
case '%':
|
|
appendStringInfoChar(&command, '%');
|
|
p++;
|
|
break;
|
|
default:
|
|
appendStringInfoChar(&command, p[0]);
|
|
}
|
|
}
|
|
else
|
|
appendStringInfoChar(&command, p[0]);
|
|
}
|
|
|
|
fh = OpenPipeStream(command.data, "r");
|
|
if (fh == NULL)
|
|
{
|
|
ereport(loglevel,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not execute command \"%s\": %m",
|
|
command.data)));
|
|
goto error;
|
|
}
|
|
|
|
if (!fgets(buf, size, fh))
|
|
{
|
|
if (ferror(fh))
|
|
{
|
|
ereport(loglevel,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not read from command \"%s\": %m",
|
|
command.data)));
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
pclose_rc = ClosePipeStream(fh);
|
|
if (pclose_rc == -1)
|
|
{
|
|
ereport(loglevel,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not close pipe to external command: %m")));
|
|
goto error;
|
|
}
|
|
else if (pclose_rc != 0)
|
|
{
|
|
ereport(loglevel,
|
|
(errcode_for_file_access(),
|
|
errmsg("command \"%s\" failed",
|
|
command.data),
|
|
errdetail_internal("%s", wait_result_to_str(pclose_rc))));
|
|
goto error;
|
|
}
|
|
|
|
/* strip trailing newline, including \r in case we're on Windows */
|
|
len = strlen(buf);
|
|
while (len > 0 && (buf[len - 1] == '\n' ||
|
|
buf[len - 1] == '\r'))
|
|
buf[--len] = '\0';
|
|
|
|
error:
|
|
pfree(command.data);
|
|
return len;
|
|
}
|
|
|
|
|
|
/*
|
|
* Check permissions for SSL key files.
|
|
*/
|
|
bool
|
|
check_ssl_key_file_permissions(const char *ssl_key_file, bool isServerStart)
|
|
{
|
|
int loglevel = isServerStart ? FATAL : LOG;
|
|
struct stat buf;
|
|
|
|
if (stat(ssl_key_file, &buf) != 0)
|
|
{
|
|
ereport(loglevel,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not access private key file \"%s\": %m",
|
|
ssl_key_file)));
|
|
return false;
|
|
}
|
|
|
|
/* Key file must be a regular file */
|
|
if (!S_ISREG(buf.st_mode))
|
|
{
|
|
ereport(loglevel,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("private key file \"%s\" is not a regular file",
|
|
ssl_key_file)));
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Refuse to load key files owned by users other than us or root, and
|
|
* require no public access to the key file. If the file is owned by us,
|
|
* require mode 0600 or less. If owned by root, require 0640 or less to
|
|
* allow read access through either our gid or a supplementary gid that
|
|
* allows us to read system-wide certificates.
|
|
*
|
|
* Note that similar checks are performed in
|
|
* src/interfaces/libpq/fe-secure-openssl.c so any changes here may need
|
|
* to be made there as well.
|
|
*
|
|
* Ideally we would do similar permissions checks on Windows, but it is
|
|
* not clear how that would work since Unix-style permissions may not be
|
|
* available.
|
|
*/
|
|
#if !defined(WIN32) && !defined(__CYGWIN__)
|
|
if (buf.st_uid != geteuid() && buf.st_uid != 0)
|
|
{
|
|
ereport(loglevel,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("private key file \"%s\" must be owned by the database user or root",
|
|
ssl_key_file)));
|
|
return false;
|
|
}
|
|
|
|
if ((buf.st_uid == geteuid() && buf.st_mode & (S_IRWXG | S_IRWXO)) ||
|
|
(buf.st_uid == 0 && buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
|
|
{
|
|
ereport(loglevel,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("private key file \"%s\" has group or world access",
|
|
ssl_key_file),
|
|
errdetail("File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root.")));
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|