]> granicus.if.org Git - postgresql/commitdiff
Re-allow SSL passphrase prompt at server start, but not thereafter.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 4 Jan 2017 17:43:52 +0000 (12:43 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 4 Jan 2017 17:44:03 +0000 (12:44 -0500)
Leave OpenSSL's default passphrase collection callback in place during
the first call of secure_initialize() in server startup.  Although that
doesn't work terribly well in daemon contexts, some people feel we should
not break it for anyone who was successfully using it before.  We still
block passphrase demands during SIGHUP, meaning that you can't adjust SSL
configuration on-the-fly if you used a passphrase, but this is no worse
than what it was before commit de41869b6.  And we block passphrase demands
during EXEC_BACKEND reloads; that behavior wasn't useful either, but at
least now it's documented.

Tweak some related log messages for more readability, and avoid issuing
essentially duplicate messages about reload failure caused by a passphrase.

Discussion: https://postgr.es/m/29982.1483412575@sss.pgh.pa.us

doc/src/sgml/runtime.sgml
src/backend/libpq/be-secure-openssl.c
src/backend/libpq/be-secure.c
src/backend/postmaster/postmaster.c
src/include/libpq/libpq-be.h
src/include/libpq/libpq.h

index 38f561886a1efa09bec8a7a1ef4b3b5b36250637..130c38646223c9a231b0786385b94d418273c496 100644 (file)
@@ -2159,8 +2159,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
   </para>
 
   <para>
-   The private key cannot be protected with a passphrase, as there is no
-   way to supply the passphrase to the server.
+   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.
+   Using a passphrase also disables the ability to change the server's SSL
+   configuration without a server restart.
+   Furthermore, passphrase-protected private keys cannot be used at all
+   on Windows.
   </para>
 
   <para>
@@ -2293,9 +2298,9 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
    <para>
     If an error in these files is detected at server start, the server will
     refuse to start.  But if an error is detected during a configuration
-    reload, the files are ignored and the old values continue to be used.
-    On <systemitem class="osname">Windows</> systems, if an error in these
-    files is detected at backend start, that backend will be unable to
+    reload, the files are ignored and the old SSL configuration continues to
+    be used.  On <systemitem class="osname">Windows</> systems, if an error in
+    these files is detected at backend start, that backend will be unable to
     establish an SSL connection.  In all these cases, the error condition is
     reported in the server log.
    </para>
@@ -2314,8 +2319,8 @@ openssl req -new -text -out server.req
     you enter the local host name as <quote>Common Name</>; the challenge
     password can be left blank. The program will generate a key that is
     passphrase protected; it will not accept a passphrase that is less
-    than four characters long.  To remove the passphrase again (as you must),
-    next run the commands:
+    than four characters long.  To remove the passphrase again (as you must
+    if you want automatic start-up of the server), next run the commands:
 <programlisting>
 openssl rsa -in privkey.pem -out server.key
 rm privkey.pem
index 07341ff696e304c7cb8f7acab3a68cf356ab07c0..44c84a7869ea2568e8ff0dedca520abec098b9e2 100644 (file)
@@ -78,13 +78,14 @@ static DH  *tmp_dh_cb(SSL *s, int is_export, int keylength);
 static int     ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
 static int     verify_cb(int, X509_STORE_CTX *);
 static void info_cb(const SSL *ssl, int type, int args);
-static bool initialize_ecdh(SSL_CTX *context, bool failOnError);
+static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
 static const char *SSLerrmessage(unsigned long ecode);
 
 static char *X509_NAME_to_cstring(X509_NAME *name);
 
 static SSL_CTX *SSL_context = NULL;
 static bool SSL_initialized = false;
+static bool ssl_passwd_cb_called = false;
 
 /* ------------------------------------------------------------ */
 /*                                              Hardcoded values                                               */
@@ -159,12 +160,12 @@ KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=\n\
 /*
  *     Initialize global SSL context.
  *
- * If failOnError is true, report any errors as FATAL (so we don't return).
- * Otherwise, log errors at LOG level and return -1 to indicate trouble.
- * Returns 0 if OK.
+ * If isServerStart is true, report any errors as FATAL (so we don't return).
+ * Otherwise, log errors at LOG level and return -1 to indicate trouble,
+ * preserving the old SSL state if any.  Returns 0 if OK.
  */
 int
-be_tls_init(bool failOnError)
+be_tls_init(bool isServerStart)
 {
        STACK_OF(X509_NAME) *root_cert_list = NULL;
        SSL_CTX    *context;
@@ -192,7 +193,7 @@ be_tls_init(bool failOnError)
        context = SSL_CTX_new(SSLv23_method());
        if (!context)
        {
-               ereport(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errmsg("could not create SSL context: %s",
                                                SSLerrmessage(ERR_get_error()))));
                goto error;
@@ -205,16 +206,21 @@ be_tls_init(bool failOnError)
        SSL_CTX_set_mode(context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
 
        /*
-        * Override OpenSSL's default handling of passphrase-protected files.
+        * If reloading, override OpenSSL's default handling of
+        * passphrase-protected files, because we don't want to prompt for a
+        * passphrase in an already-running server.  (Not that the default
+        * handling is very desirable during server start either, but some people
+        * insist we need to keep it.)
         */
-       SSL_CTX_set_default_passwd_cb(context, ssl_passwd_cb);
+       if (!isServerStart)
+               SSL_CTX_set_default_passwd_cb(context, ssl_passwd_cb);
 
        /*
         * Load and verify server's certificate and private key
         */
        if (SSL_CTX_use_certificate_chain_file(context, ssl_cert_file) != 1)
        {
-               ereport(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("could not load server certificate file \"%s\": %s",
                                                ssl_cert_file, SSLerrmessage(ERR_get_error()))));
@@ -223,7 +229,7 @@ be_tls_init(bool failOnError)
 
        if (stat(ssl_key_file, &buf) != 0)
        {
-               ereport(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errcode_for_file_access(),
                                 errmsg("could not access private key file \"%s\": %m",
                                                ssl_key_file)));
@@ -232,7 +238,7 @@ be_tls_init(bool failOnError)
 
        if (!S_ISREG(buf.st_mode))
        {
-               ereport(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("private key file \"%s\" is not a regular file",
                                                ssl_key_file)));
@@ -240,14 +246,14 @@ be_tls_init(bool failOnError)
        }
 
        /*
-        * Refuse to load files owned by users other than us or root.
+        * Refuse to load key files owned by users other than us or root.
         *
         * XXX surely we can check this on Windows somehow, too.
         */
 #if !defined(WIN32) && !defined(__CYGWIN__)
        if (buf.st_uid != geteuid() && buf.st_uid != 0)
        {
-               ereport(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("private key file \"%s\" must be owned by the database user or root",
                                                ssl_key_file)));
@@ -270,7 +276,7 @@ be_tls_init(bool failOnError)
        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(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("private key file \"%s\" has group or world access",
                                                ssl_key_file),
@@ -279,20 +285,31 @@ be_tls_init(bool failOnError)
        }
 #endif
 
+       /*
+        * OK, try to load the private key file.
+        */
+       ssl_passwd_cb_called = false;
+
        if (SSL_CTX_use_PrivateKey_file(context,
                                                                        ssl_key_file,
                                                                        SSL_FILETYPE_PEM) != 1)
        {
-               ereport(failOnError ? FATAL : LOG,
-                               (errcode(ERRCODE_CONFIG_FILE_ERROR),
-                                errmsg("could not load private key file \"%s\": %s",
-                                               ssl_key_file, SSLerrmessage(ERR_get_error()))));
+               if (ssl_passwd_cb_called)
+                       ereport(isServerStart ? FATAL : LOG,
+                                       (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                        errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase",
+                                                       ssl_key_file)));
+               else
+                       ereport(isServerStart ? FATAL : LOG,
+                                       (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                        errmsg("could not load private key file \"%s\": %s",
+                                                       ssl_key_file, SSLerrmessage(ERR_get_error()))));
                goto error;
        }
 
        if (SSL_CTX_check_private_key(context) != 1)
        {
-               ereport(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("check of private key failed: %s",
                                                SSLerrmessage(ERR_get_error()))));
@@ -306,13 +323,13 @@ be_tls_init(bool failOnError)
                                                SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 
        /* set up ephemeral ECDH keys */
-       if (!initialize_ecdh(context, failOnError))
+       if (!initialize_ecdh(context, isServerStart))
                goto error;
 
        /* set up the allowed cipher list */
        if (SSL_CTX_set_cipher_list(context, SSLCipherSuites) != 1)
        {
-               ereport(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("could not set the cipher list (no valid ciphers available)")));
                goto error;
@@ -330,7 +347,7 @@ be_tls_init(bool failOnError)
                if (SSL_CTX_load_verify_locations(context, ssl_ca_file, NULL) != 1 ||
                        (root_cert_list = SSL_load_client_CA_file(ssl_ca_file)) == NULL)
                {
-                       ereport(failOnError ? FATAL : LOG,
+                       ereport(isServerStart ? FATAL : LOG,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("could not load root certificate file \"%s\": %s",
                                                        ssl_ca_file, SSLerrmessage(ERR_get_error()))));
@@ -366,7 +383,7 @@ be_tls_init(bool failOnError)
                        }
                        else
                        {
-                               ereport(failOnError ? FATAL : LOG,
+                               ereport(isServerStart ? FATAL : LOG,
                                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                 errmsg("could not load SSL certificate revocation list file \"%s\": %s",
                                                         ssl_crl_file, SSLerrmessage(ERR_get_error()))));
@@ -1071,19 +1088,16 @@ tmp_dh_cb(SSL *s, int is_export, int keylength)
  *
  * If OpenSSL is told to use a passphrase-protected server key, by default
  * it will issue a prompt on /dev/tty and try to read a key from there.
- * That's completely no good for a postmaster SIGHUP cycle, not to mention
- * SSL context reload in an EXEC_BACKEND postmaster child.  So override it
- * with this dummy function that just returns an empty passphrase,
- * guaranteeing failure.  Later we might think about collecting a passphrase
- * at server start and feeding it to OpenSSL repeatedly, but we'd still
- * need this callback for that.
+ * That's no good during a postmaster SIGHUP cycle, not to mention SSL context
+ * reload in an EXEC_BACKEND postmaster child.  So override it with this dummy
+ * function that just returns an empty passphrase, guaranteeing failure.
  */
 static int
 ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
 {
-       ereport(LOG,
-                       (errcode(ERRCODE_CONFIG_FILE_ERROR),
-                        errmsg("server's private key file requires a passphrase")));
+       /* Set flag to change the error message we'll report */
+       ssl_passwd_cb_called = true;
+       /* And return empty string */
        Assert(size > 0);
        buf[0] = '\0';
        return 0;
@@ -1151,7 +1165,7 @@ info_cb(const SSL *ssl, int type, int args)
 }
 
 static bool
-initialize_ecdh(SSL_CTX *context, bool failOnError)
+initialize_ecdh(SSL_CTX *context, bool isServerStart)
 {
 #ifndef OPENSSL_NO_ECDH
        EC_KEY     *ecdh;
@@ -1160,7 +1174,7 @@ initialize_ecdh(SSL_CTX *context, bool failOnError)
        nid = OBJ_sn2nid(SSLECDHCurve);
        if (!nid)
        {
-               ereport(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("ECDH: unrecognized curve name: %s", SSLECDHCurve)));
                return false;
@@ -1169,7 +1183,7 @@ initialize_ecdh(SSL_CTX *context, bool failOnError)
        ecdh = EC_KEY_new_by_curve_name(nid);
        if (!ecdh)
        {
-               ereport(failOnError ? FATAL : LOG,
+               ereport(isServerStart ? FATAL : LOG,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("ECDH: could not create key")));
                return false;
index e5ee974c270518ff150fb28d4a0fd0e7f9ded75f..785dadb6c2fd8d626ca1cc22baf79ec914d53fdb 100644 (file)
@@ -65,15 +65,15 @@ bool                SSLPreferServerCiphers;
 /*
  *     Initialize global context.
  *
- * If failOnError is true, report any errors as FATAL (so we don't return).
- * Otherwise, log errors at LOG level and return -1 to indicate trouble.
- * Returns 0 if OK.
+ * If isServerStart is true, report any errors as FATAL (so we don't return).
+ * Otherwise, log errors at LOG level and return -1 to indicate trouble,
+ * preserving the old SSL state if any.  Returns 0 if OK.
  */
 int
-secure_initialize(bool failOnError)
+secure_initialize(bool isServerStart)
 {
 #ifdef USE_SSL
-       return be_tls_init(failOnError);
+       return be_tls_init(isServerStart);
 #else
        return 0;
 #endif
index 21066e7bb7e2193e2e95e7c64b53bd3f57f884a5..5be30b0ee1562970e6d0a04cc08eb155cec5248a 100644 (file)
@@ -2507,11 +2507,11 @@ SIGHUP_handler(SIGNAL_ARGS)
                /* Reload authentication config files too */
                if (!load_hba())
                        ereport(LOG,
-                                       (errmsg("pg_hba.conf not reloaded")));
+                                       (errmsg("pg_hba.conf was not reloaded")));
 
                if (!load_ident())
                        ereport(LOG,
-                                       (errmsg("pg_ident.conf not reloaded")));
+                                       (errmsg("pg_ident.conf was not reloaded")));
 
 #ifdef USE_SSL
                /* Reload SSL configuration as well */
@@ -2521,7 +2521,7 @@ SIGHUP_handler(SIGNAL_ARGS)
                                LoadedSSL = true;
                        else
                                ereport(LOG,
-                                               (errmsg("SSL context not reloaded")));
+                                               (errmsg("SSL configuration was not reloaded")));
                }
                else
                {
@@ -4772,7 +4772,7 @@ SubPostmasterMain(int argc, char *argv[])
                                LoadedSSL = true;
                        else
                                ereport(LOG,
-                                               (errmsg("SSL context could not be reloaded in child process")));
+                                               (errmsg("SSL configuration could not be loaded in child process")));
                }
 #endif
 
index b7582d6126603da7bb8057a182e6ac521bc2e0d7..79d38cedd76aac2cffa0433d455cd643bbdddd93 100644 (file)
@@ -199,7 +199,7 @@ typedef struct Port
  * These functions are implemented by the glue code specific to each
  * SSL implementation (e.g. be-secure-openssl.c)
  */
-extern int     be_tls_init(bool failOnError);
+extern int     be_tls_init(bool isServerStart);
 extern void be_tls_destroy(void);
 extern int     be_tls_open_server(Port *port);
 extern void be_tls_close(Port *port);
index 46c5b726ba282997df20756b7550915cb752d2ad..538066e106737868f32c942073e3e38b2c023b64 100644 (file)
@@ -81,7 +81,7 @@ extern char *ssl_key_file;
 extern char *ssl_ca_file;
 extern char *ssl_crl_file;
 
-extern int     secure_initialize(bool failOnError);
+extern int     secure_initialize(bool isServerStart);
 extern bool secure_loaded_verify_locations(void);
 extern void secure_destroy(void);
 extern int     secure_open_server(Port *port);