Add support for SSL passphrase.
authorTatsuo Ishii <ishii@sraoss.co.jp>
Mon, 30 Mar 2020 03:23:37 +0000 (12:23 +0900)
committerTatsuo Ishii <ishii@sraoss.co.jp>
Mon, 30 Mar 2020 03:23:37 +0000 (12:23 +0900)
Authors: Umar Hayat. Small modification and Japanese document by Tatsuo Ishii.
Dicussion: https://www.pgpool.net/pipermail/pgpool-hackers/2020-March/003548.html

doc.ja/src/sgml/ssl.sgml
doc/src/sgml/ssl.sgml
src/config/pool_config_variables.c
src/include/pool_config.h
src/sample/pgpool.conf.sample-logical
src/sample/pgpool.conf.sample-raw
src/sample/pgpool.conf.sample-replication
src/sample/pgpool.conf.sample-slony
src/sample/pgpool.conf.sample-stream
src/utils/pool_process_reporting.c
src/utils/pool_ssl.c

index 755d40f1b4a451d8aac9b7f09ef0c708207e527d..bab07f4297f348177b5382337ae06fd0c8c50ce2 100644 (file)
     </listitem>
    </varlistentry>
 
+   <varlistentry id="guc-ssl-passphrase-command" xreflabel="ssl_passphrase_command">
+    <term><varname>ssl_passphrase_command</varname> (<type>string</type>)
+     <indexterm>
+      <primary><varname>ssl_passphrase_command</varname>設定パラメータ</primary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      秘密鍵などのSSLファイルを復号する際に、パスフレーズの入手が必要な時に起動される外部コマンドを設定します。
+      デフォルトではこのパラメータは空文字で、この場合はパスフレーズが要求されてもSSLファイルはロードされません。
+     </para>
+     <para>
+      このコマンドは、パスフレーズを標準出力に書き出し、コード0で終了しなければなりません。
+      パラメータの値の<literal>%p</literal>はプロンプト文字列に置き換えられます。
+      (<literal>%</literal>を使いたい場合は<literal>%%</literal>としてください。)
+      プロンプト文字列はおそらく空白文字を含むので、適切に引用符付けするように注意してください。
+      出力の最後に一個の改行があれば、削除されます。
+     </para>
+     <para>
+      このコマンドは実際にはパスフレーズ用にユーザにプロンプトを表示する必要はありません。
+      ファイルからパスフレーズが読めるなら、キーチェーン機構やその他から取得します。
+      選択された仕組みが適切にセキュアかどうかを確認するのはユーザ次第です。
+     </para>
+     <para>
+      このパラメータはサーバ起動時にのみ設定可能です。
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
  </sect2>
 
index 0b04b2448d689c6a69c53a937e9c5daad2ac4cc7..e413463bba83ad30d92fc02c9329d5a439748b8a 100644 (file)
     </listitem>
    </varlistentry>
 
+   <varlistentry id="guc-ssl-passphrase-command" xreflabel="ssl_passphrase_command">
+    <term><varname>ssl_passphrase_command</varname> (<type>string</type>)
+     <indexterm>
+      <primary><varname>ssl_passphrase_command</varname> configuration parameter</primary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      Sets an external command to be invoked when a passphrase for decrypting
+      an SSL file such as a private key needs to be obtained. By default,
+      this parameter is empty, which means SSL file will not be loaded if passphrase is required.
+     </para>
+     <para>
+      The command must print the passphrase to the standard output and
+      exit with code 0. In the parameter value, %p is replaced by a prompt
+      string. (Write %% for a literal %.) Note that the prompt string will probably
+      contain whitespace, so be sure to quote adequately. A single newline is stripped
+      from the end of the output if present.
+     </para>
+     <para>
+      The command does not actually have to prompt the user for a passphrase.
+      It can read it from a file, obtain it from a keychain facility, or similar.
+      It is up to the user to make sure the chosen mechanism is adequately secure.
+     </para>
+     <para>
+      This parameter can only be set at server start.
+     </para>
+    </listitem>
+   </varlistentry>
+
   </variablelist>
  </sect2>
 
index 8d02c31892fbb697a943501c0697a9f96649db16..809a052212b3a31aeffd058cd064b715a3773823 100644 (file)
@@ -1109,6 +1109,17 @@ static struct config_string ConfigureNamesString[] =
                NULL, NULL, NULL, NULL
        },
 
+       {
+               {"ssl_passphrase_command", CFGCXT_INIT, SSL_CONFIG,
+                       "Path to the Diffie-Hellman parameters contained file",
+                       CONFIG_VAR_TYPE_STRING, false, 0
+               },
+               &g_pool_config.ssl_passphrase_command,
+               "",
+               NULL, NULL, NULL, NULL
+       },
+
+
        {
                {"memqcache_oiddir", CFGCXT_INIT, CACHE_CONFIG,
                        "Tempory directory to record table oids.",
index a14ead1c4cb342cd14f0036ccc00c78be18bfd3e..218842633432577c0cc75a5228ebed3854c54a3d 100644 (file)
@@ -367,6 +367,7 @@ typedef struct
        bool            ssl_prefer_server_ciphers; /*Use SSL cipher preferences, rather than the client's*/
        char       *ssl_ecdh_curve; /* the curve to use in ECDH key exchange */
        char       *ssl_dh_params_file; /* path to the Diffie-Hellman parameters contained file */
+       char       *ssl_passphrase_command; /* path to the Diffie-Hellman parameters contained file */
        int64           relcache_expire;        /* relation cache life time in seconds */
        int                     relcache_size;  /* number of relation cache life entry */
        CHECK_TEMP_TABLE_OPTION         check_temp_table;       /* how to check temporary table */
index 6158ed89eec61630bfed47faed003b01a901a8c5..6eb146b631f01e1e4ee106d433c14fdb8de772c4 100644 (file)
@@ -144,6 +144,10 @@ ssl_ecdh_curve = 'prime256v1'
 ssl_dh_params_file = ''
                                    # Name of the file containing Diffie-Hellman parameters used
                                    # for so-called ephemeral DH family of SSL cipher.
+#ssl_passphrase_command=''
+                                   # Sets an external command to be invoked when a passphrase
+                                   # for decrypting an SSL file needs to be obtained
+                                   # (change requires restart)
 
 #------------------------------------------------------------------------------
 # POOLS
index de25b5697e6dd07ff197209a01970940cf98040d..16cca78a4305405413c47f29ec139a72a057357b 100644 (file)
@@ -145,6 +145,10 @@ ssl_ecdh_curve = 'prime256v1'
 ssl_dh_params_file = ''
                                    # Name of the file containing Diffie-Hellman parameters used
                                    # for so-called ephemeral DH family of SSL cipher.
+#ssl_passphrase_command=''
+                                   # Sets an external command to be invoked when a passphrase
+                                   # for decrypting an SSL file needs to be obtained
+                                   # (change requires restart)
 
 #------------------------------------------------------------------------------
 # POOLS
index b17912918cd63a5184bc67d6259c522cfc466401..0fc0ee274a1fdcb56b0617498bd012d090a573ca 100644 (file)
@@ -140,6 +140,10 @@ ssl_ecdh_curve = 'prime256v1'
 ssl_dh_params_file = ''
                                    # Name of the file containing Diffie-Hellman parameters used
                                    # for so-called ephemeral DH family of SSL cipher.
+#ssl_passphrase_command=''
+                                   # Sets an external command to be invoked when a passphrase
+                                   # for decrypting an SSL file needs to be obtained
+                                   # (change requires restart)
 
 #------------------------------------------------------------------------------
 # POOLS
index ef840938fd33a611e7b9fa0864317b5c8c7ac766..7abc58d40d152ed5584ec079c200b964f73ab284 100644 (file)
@@ -141,6 +141,10 @@ ssl_ecdh_curve = 'prime256v1'
 ssl_dh_params_file = ''
                                    # Name of the file containing Diffie-Hellman parameters used
                                    # for so-called ephemeral DH family of SSL cipher.
+#ssl_passphrase_command=''
+                                   # Sets an external command to be invoked when a passphrase
+                                   # for decrypting an SSL file needs to be obtained
+                                   # (change requires restart)
 
 #------------------------------------------------------------------------------
 # POOLS
index 2cc7bda592607bcbf25f5b17640c946ea64ffdde..26a9ff24975fcdbfe5e849d8791c662a5fcdf45c 100644 (file)
@@ -145,6 +145,10 @@ ssl_ecdh_curve = 'prime256v1'
 ssl_dh_params_file = ''
                                    # Name of the file containing Diffie-Hellman parameters used
                                    # for so-called ephemeral DH family of SSL cipher.
+#ssl_passphrase_command=''
+                                   # Sets an external command to be invoked when a passphrase
+                                   # for decrypting an SSL file needs to be obtained
+                                   # (change requires restart)
 
 #------------------------------------------------------------------------------
 # POOLS
index 126de0e6cfaedcda399cf2f4acc14361514ff6af..54eb8b70c71d57fdaac55962f3a60bf08af78186 100644 (file)
@@ -284,6 +284,11 @@ get_config(int *nrows)
        StrNCpy(status[i].desc, "path to the Diffie-Hellman parameters contained file", POOLCONFIG_MAXDESCLEN);
        i++;
 
+       StrNCpy(status[i].name, "ssl_passphrase_command", POOLCONFIG_MAXNAMELEN);
+       snprintf(status[i].value, POOLCONFIG_MAXVALLEN, "%s", pool_config->ssl_passphrase_command);
+       StrNCpy(status[i].desc, "external command to be invoked when a passphrase for decrypting an SSL file such as a private key needs to be obtained", POOLCONFIG_MAXDESCLEN);
+       i++;
+
        /* POOLS */
 
        /* - Pool size -  */
index 59f4dcf9a4fc93434d9bf570835904c92c142e47..cd99374c7e3b84381deff263d7be73c8cdcf0dfc 100644 (file)
@@ -40,8 +40,9 @@
 
 static SSL_CTX *SSL_frontend_context = NULL;
 static bool SSL_initialized = false;
-static bool ssl_passwd_cb_called = false;
-static int     ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
+static bool dummy_ssl_passwd_cb_called = false;
+static int  dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
+static int  ssl_external_passwd_cb(char *buf, int size, int rwflag, void *userdata);
 static int     verify_cb(int ok, X509_STORE_CTX *ctx);
 static const char *SSLerrmessage(unsigned long ecode);
 static void fetch_pool_ssl_cert(POOL_CONNECTION * cp);
@@ -49,6 +50,7 @@ static DH *load_dh_file(char *filename);
 static DH *load_dh_buffer(const char *, size_t);
 static bool initialize_dh(SSL_CTX *context);
 static bool initialize_ecdh(SSL_CTX *context);
+static int run_ssl_passphrase_command(const char *prompt, char *buf, int size);
 
 #define SSL_RETURN_VOID_IF(cond, msg) \
        do { \
@@ -474,16 +476,30 @@ fetch_pool_ssl_cert(POOL_CONNECTION * cp)
  * function that just returns an empty passphrase, guaranteeing failure.
  */
 static int
-ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
+dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
 {
        /* Set flag to change the error message we'll report */
-       ssl_passwd_cb_called = true;
+       dummy_ssl_passwd_cb_called = true;
        /* And return empty string */
        Assert(size > 0);
        buf[0] = '\0';
        return 0;
 }
 
+/*
+ *     Passphrase collection callback using ssl_passphrase_command
+ */
+static int
+ssl_external_passwd_cb(char *buf, int size, int rwflag, void *userdata)
+{
+       /* same prompt as OpenSSL uses internally */
+       const char *prompt = "Enter PEM pass phrase:";
+
+       Assert(rwflag == 0);
+
+       return run_ssl_passphrase_command(prompt, buf, size);
+}
+
 /*
  *     Certificate verification callback
  *
@@ -557,7 +573,10 @@ SSL_ServerSide_init(void)
        /*
         * prompt for password for passphrase-protected files
         */
-       SSL_CTX_set_default_passwd_cb(context, ssl_passwd_cb);
+       if(pool_config->ssl_passphrase_command && strlen(pool_config->ssl_passphrase_command))
+               SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb);
+       else
+               SSL_CTX_set_default_passwd_cb(context, dummy_ssl_passwd_cb);
 
        /*
         * Load and verify server's certificate and private key
@@ -611,13 +630,13 @@ SSL_ServerSide_init(void)
        /*
         * OK, try to load the private key file.
         */
-       ssl_passwd_cb_called = false;
+       dummy_ssl_passwd_cb_called = false;
 
        if (SSL_CTX_use_PrivateKey_file(context,
                                                                        pool_config->ssl_key,
                                                                        SSL_FILETYPE_PEM) != 1)
        {
-               if (ssl_passwd_cb_called)
+               if (dummy_ssl_passwd_cb_called)
                        ereport(WARNING,
                                        (errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase",
                                                        pool_config->ssl_key)));
@@ -905,6 +924,95 @@ load_dh_buffer(const char *buffer, size_t len)
        return dh;
 }
 
+/*
+ * Run ssl_passphrase_command
+ *
+ * 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, char *buf, int size)
+{
+       int                     loglevel = ERROR;
+       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 = pool_config->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 = popen(command.data, "r");
+       if (fh == NULL)
+       {
+               ereport(loglevel,
+                               (errmsg("could not execute command \"%s\": %m",
+                                               command.data)));
+               goto error;
+       }
+
+       if (!fgets(buf, size, fh))
+       {
+               if (ferror(fh))
+               {
+                       ereport(loglevel,
+                                       (errmsg("could not read from command \"%s\": %m",
+                                                       command.data)));
+                       goto error;
+               }
+       }
+
+       pclose_rc = pclose(fh);
+       if (pclose_rc == -1)
+       {
+               ereport(loglevel,
+                               (errmsg("could not close pipe to external command: %m")));
+               goto error;
+       }
+       else if (pclose_rc != 0)
+       {
+               ereport(loglevel,
+                               (errmsg("command \"%s\" failed",
+                                               command.data)));
+               goto error;
+       }
+
+       /* strip trailing newline */
+       len = strlen(buf);
+       if (len > 0 && buf[len - 1] == '\n')
+               buf[--len] = '\0';
+
+error:
+       pfree(command.data);
+       return len;
+}
+
 #else                                                  /* USE_SSL: wrap / no-op ssl functionality if
                                                                 * it's not available */