Client certificate authentication

Поиск
Список
Период
Сортировка
От Magnus Hagander
Тема Client certificate authentication
Дата
Msg-id 491C1E2D.8060905@hagander.net
обсуждение исходный текст
Ответы Re: Client certificate authentication  ("Alex Hunsaker" <badalex@gmail.com>)
Список pgsql-hackers
Attached patch implements client certificate authentication.

I kept this sitting in my tree without sending it in before the
commitfest because it is entirely dependent on the
not-yet-reviewed-and-applied patch for how to configure client
certificate requesting. But now that I learned how to do it right in
git, breaking it out was very easy :-) Good learning experience.

Anyway. Here it is. Builds on top of the "clientcert option for pg_hba"
patch already on the list.

//Magnus
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
***************
*** 388,393 **** hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
--- 388,403 ----
         </varlistentry>

         <varlistentry>
+         <term><literal>cert</></term>
+         <listitem>
+          <para>
+           Authenticate using SSL client certificates. See
+           <xref linkend="auth-cert"> for details.
+          </para>
+         </listitem>
+        </varlistentry>
+
+        <varlistentry>
          <term><literal>pam</></term>
          <listitem>
           <para>
***************
*** 1114,1119 **** ldapserver=ldap.example.net prefix="cn=" suffix="dc=example, dc=net"
--- 1124,1148 ----

    </sect2>

+   <sect2 id="auth-cert">
+    <title>Certificate authentication</title>
+
+    <indexterm zone="auth-cert">
+     <primary>Certificate</primary>
+    </indexterm>
+
+    <para>
+     This authentication method uses SSL client certificates to perform
+     authentication. It is therefore only available for SSL connections.
+     When using this authentication method, the server will require that
+     the client provide a certificate. No password prompt will be sent
+     to the client. The <literal>cn</literal> attribute of the certificate
+     will be matched with the username the user is trying to log in as,
+     and if they match the login will be allowed. Username mapping can be
+     used if the usernames don't match.
+    </para>
+   </sect2>
+
    <sect2 id="auth-pam">
     <title>PAM authentication</title>

*** a/doc/src/sgml/runtime.sgml
--- b/doc/src/sgml/runtime.sgml
***************
*** 1674,1684 **** $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput
    </para>

    <para>
!    <productname>PostgreSQL</> currently does not support authentication
!    using client certificates, since it cannot differentiate between
!    different users. As long as the user holds any certificate issued
!    by a trusted CA it will be accepted, regardless of what account the
!    user is trying to connect with.
    </para>
    </sect2>

--- 1674,1682 ----
    </para>

    <para>
!    You can use the authentication method <literal>cert</> to use the
!    client certificate for authenticating users. See
!    <xref linkend="auth-cert"> for details.
    </para>
    </sect2>

*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 110,115 **** ULONG(*__ldap_start_tls_sA) (
--- 110,123 ----
  static int    CheckLDAPAuth(Port *port);
  #endif /* USE_LDAP */

+ /*----------------------------------------------------------------
+  * Cert authentication
+  *----------------------------------------------------------------
+  */
+ #ifdef USE_SSL
+ static int    CheckCertAuth(Port *port);
+ #endif
+

  /*----------------------------------------------------------------
   * Kerberos and GSSAPI GUCs
***************
*** 420,425 **** ClientAuthentication(Port *port)
--- 428,441 ----
  #endif
              break;

+         case uaCert:
+ #ifdef USE_SSL
+             status = CheckCertAuth(port);
+ #else
+             Assert(false);
+ #endif
+             break;
+
          case uaTrust:
              status = STATUS_OK;
              break;
***************
*** 2072,2074 **** CheckLDAPAuth(Port *port)
--- 2088,2115 ----
  }
  #endif   /* USE_LDAP */

+
+ /*----------------------------------------------------------------
+  * SSL client certificate authentication
+  *----------------------------------------------------------------
+  */
+ #ifdef USE_SSL
+ static int
+ CheckCertAuth(Port *port)
+ {
+     Assert(port->ssl);
+
+     /* Make sure we have received a username in the certificate */
+     if (port->peer_cn == NULL ||
+         strlen(port->peer_cn) <= 0)
+     {
+         ereport(LOG,
+                 (errmsg("Certificate login failed for user \"%s\": client certificate contains no username",
+                         port->user_name)));
+         return STATUS_ERROR;
+     }
+
+     /* Just pass the certificate CN to the usermap check */
+     return check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false);
+ }
+ #endif
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 859,864 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
--- 859,870 ----
  #else
          unsupauth = "ldap";
  #endif
+     else if (strcmp(token, "cert") == 0)
+ #ifdef USE_SSL
+         parsedline->auth_method = uaCert;
+ #else
+         unsupauth = "cert";
+ #endif
      else
      {
          ereport(LOG,
***************
*** 893,898 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
--- 899,915 ----
          return false;
      }

+     if (parsedline->conntype != ctHostSSL &&
+         parsedline->auth_method == uaCert)
+     {
+         ereport(LOG,
+                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                  errmsg("cert authentication is only supported on hostssl connections"),
+                  errcontext("line %d of configuration file \"%s\"",
+                             line_num, HbaFileName)));
+         return false;
+     }
+
      /* Parse remaining arguments */
      while ((line_item = lnext(line_item)) != NULL)
      {
***************
*** 923,930 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
                  if (parsedline->auth_method != uaIdent &&
                      parsedline->auth_method != uaKrb5 &&
                      parsedline->auth_method != uaGSS &&
!                     parsedline->auth_method != uaSSPI)
!                     INVALID_AUTH_OPTION("map", "ident, krb5, gssapi and sspi");
                  parsedline->usermap = pstrdup(c);
              }
              else if (strcmp(token, "clientcert") == 0)
--- 940,948 ----
                  if (parsedline->auth_method != uaIdent &&
                      parsedline->auth_method != uaKrb5 &&
                      parsedline->auth_method != uaGSS &&
!                     parsedline->auth_method != uaSSPI &&
!                     parsedline->auth_method != uaCert)
!                     INVALID_AUTH_OPTION("map", "ident, krb5, gssapi, sspi and cert");
                  parsedline->usermap = pstrdup(c);
              }
              else if (strcmp(token, "clientcert") == 0)
***************
*** 957,963 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
--- 975,992 ----
                      parsedline->clientcert = true;
                  }
                  else
+                 {
+                     if (parsedline->auth_method == uaCert)
+                     {
+                         ereport(LOG,
+                                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                  errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
+                                  errcontext("line %d of configuration file \"%s\"",
+                                             line_num, HbaFileName)));
+                         return false;
+                     }
                      parsedline->clientcert = false;
+                 }
              }
              else if (strcmp(token, "pamservice") == 0)
              {
***************
*** 1021,1026 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
--- 1050,1063 ----
      {
          MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
      }
+
+     /*
+      * Enforce any parameters implied by other settings.
+      */
+     if (parsedline->auth_method == uaCert)
+     {
+         parsedline->clientcert = true;
+     }

      return true;
  }
*** a/src/backend/libpq/pg_hba.conf.sample
--- b/src/backend/libpq/pg_hba.conf.sample
***************
*** 35,41 ****
  # an IP address and netmask in separate columns to specify the set of hosts.
  #
  # METHOD can be "trust", "reject", "md5", "crypt", "password", "gss", "sspi",
! # "krb5", "ident", "pam" or "ldap".  Note that "password" sends passwords
  # in clear text; "md5" is preferred since it sends encrypted passwords.
  #
  # OPTIONS are a set of options for the authentication in the format
--- 35,41 ----
  # an IP address and netmask in separate columns to specify the set of hosts.
  #
  # METHOD can be "trust", "reject", "md5", "crypt", "password", "gss", "sspi",
! # "krb5", "ident", "pam", "ldap" or "cert". Note that "password" sends passwords
  # in clear text; "md5" is preferred since it sends encrypted passwords.
  #
  # OPTIONS are a set of options for the authentication in the format
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 26,32 **** typedef enum UserAuth
      uaGSS,
      uaSSPI,
      uaPAM,
!     uaLDAP
  } UserAuth;

  typedef enum ConnType
--- 26,33 ----
      uaGSS,
      uaSSPI,
      uaPAM,
!     uaLDAP,
!     uaCert
  } UserAuth;

  typedef enum ConnType

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Markus Wanner
Дата:
Сообщение: Re: Synchronization Primitives
Следующее
От: Magnus Hagander
Дата:
Сообщение: Re: 8.3 .4 + Vista + MingW + initdb = ACCESS_DENIED