pgpool2.git / summary / log / commit / refs

commit    c18c12f86d58c501feee68645c8be55ad213d9e9
Author:   Takuma Hoshiai <hoshiai@sraoss.co.jp>
Date:     Fri Jun 26 08:29:44 2020 +0000

    Add support for LDAP authentication between client and pgpool.
    
    This commit supports LDAP authentication between application and Pgpool.
    LDAP is used only to validate the user name/password pairs. LDAP parameter
    (such as ldapserver,port, ldapbinddn, etc..) is specified in a row of
    pool_hba.conf. When user connection is matched by ldap authentication rows
    in pool_hba.conf, access ldapserver and search matching informatinonfor
    username/passord.
    
    pgpool has 'backend_use_password' option which is particular parameter
    in auth-options. It is able to reuse authentication password which was
    used between application and pgpool to postgres authentication.
    
    If we use this feature, Pgpool-II must be build with --with-ldap support.


configure.ac | 12 + doc.ja/src/sgml/client-auth.sgml | 481 ++++++++++++++++++++- doc/src/sgml/client-auth.sgml | 330 +++++++++++++- src/auth/pool_auth.c | 4 + src/auth/pool_hba.c | 897 ++++++++++++++++++++++++++++++++++++++- src/include/auth/pool_hba.h | 22 + src/include/config.h.in | 12 + src/pgpool.spec | 1 + 8 files changed, 1741 insertions(+), 18 deletions(-) diff --git a/configure.ac b/configure.ac index a9193e596..b1c6bf51f 100644 --- a/configure.ac +++ b/configure.ac @@ -348,6 +348,18 @@ if test "$with_openssl" = yes || test "$with_openssl" = auto; then AC_CHECK_LIB(ssl, SSL_new, [], [AC_MSG_ERROR([library 'ssl' is required for OpenSSL])]) fi +AC_ARG_WITH(ldap, + [ --with-ldap build with LDAP support], + [AC_DEFINE([USE_LDAP], 1, [Define to 1 to build with LDAP support. (--with-ldap)])]) +if test "$with_ldap" = yes ; then + AC_CHECK_LIB(ldap, ldap_bind, [], + [AC_MSG_ERROR([library 'ldap' is required for LDAP])], + [$EXTRA_LDAP_LIBS]) + AC_CHECK_HEADERS(ldap.h, [], + [AC_MSG_ERROR([header file <ldap.h> is required for LDAP])]) + AC_CHECK_FUNCS([ldap_initialize]) +fi + AC_ARG_WITH(pam, [ --with-pam build with PAM support], [AC_DEFINE([USE_PAM], 1, [Define to 1 to build with PAM support. (--with-pam)])]) diff --git a/doc.ja/src/sgml/client-auth.sgml b/doc.ja/src/sgml/client-auth.sgml index c9550f164..485ea2744 100644 --- a/doc.ja/src/sgml/client-auth.sgml +++ b/doc.ja/src/sgml/client-auth.sgml @@ -584,11 +584,31 @@ </para> </listitem> </varlistentry> - </variablelist> - </para> - </listitem> - </varlistentry> + <varlistentry> + <term><literal>ldap</literal></term> + <listitem> + <para> + <!-- + Authenticate using LDAP server. + See <xref linkend="auth-cert"> for more details. + --> + LDAPサーバを使って認証します。 + 詳細は<xref linkend="auth-ldap">を参照してください。 + </para> + <para> + <!-- + To enable LDAP support the <productname>Pgpool-II</productname> + must be configured with <command>"&#045;&#045;with-ldap"</command> + --> + LDAPサポートを有効にするには、<productname>Pgpool-II</productname>が<command>"--with-ldap"</command>をつけてconfigureされていなければなりません。 + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </listitem> + </varlistentry> <varlistentry> <term><replaceable>auth-options</replaceable></term> @@ -1048,6 +1068,459 @@ </para> </note> </sect2> + + <sect2 id="auth-ldap"> + <!-- + <title>LDAP Authentication</title> + --> + <title>LDAP認証</title> + + <indexterm zone="auth-ldap"> + <primary>LDAP</primary> + </indexterm> + + <para> + <!-- + This authentication method uses LDAP as the password cerification method. + LDAP is used only to validate the user name/password pairs. Therefore the user must + already exist in the database before LDAP can be used for authentication. + --> +この認証方式はパスワード確認にLDAPを使用します。 +LDAPはユーザの名前とパスワードの組み合わせのみに使用されます。 +そのため、LDAPを使用して認証を行うようにする前に、ユーザはデータベースに存在しなければなりません。 + </para> + + <para> + <!-- + LDAP authentication can operate in two modes. In the first mode, which we + will call the simple bind mode, the server will bind to the distinguished + name constructed as + <replaceable>prefix</replaceable> <replaceable>username</replaceable> <replaceable>suffix</replaceable>. + Typically, the <replaceable>prefix</replaceable> parameter is used to specify + <literal>cn=</literal>, or <replaceable>DOMAIN</replaceable><literal>\</literal> + in an Active Directory environment. <replaceable>suffix</replaceable> is used + to specify the remaining part of the DN in a non-Active Directory environment. + --> +LDAP認証は2つのモードで動作します。1つ目のモードでは、それは単なるバインド・モードを呼び出すものですが、サーバは<replaceable>prefix</replaceable> <replaceable>username</replaceable> <replaceable>suffix</replaceable>として区別された名前にバインドします。 +一般的に、<replaceable>prefix</replaceable>パラメータはActive Directory環境での<literal>cn=</literal>や<replaceable>DOMAIN</replaceable><literal>\</literal>を特定するために使用されます。 +<replaceable>suffix</replaceable>は、Active Directory環境ではない場合でのDNの残りの部分を特定するために使用されます。 + </para> + + <para> + <!-- + In the second mode, which we will call the search+bind mode, the server first + binds to the LDAP directory with a fixed user name and password, specified + with <replaceable>ldapbinddn</replaceable> and <replaceable>ldapbindpasswd</replaceable>, + and performs a search for the user trying to log in to the database. If no + user and password is configured, an anonymous bind will be attempted to the + directory. The search will be performed over the subtree at + <replaceable>ldapbasedn</replaceable>, and will try to do an exact match of + the attribute specified in <replaceable>ldapsearchattribute</replaceable>. + Once the user has been found in this search, the server disconnects and + re-binds to the directory as this user, using the password specified by the + client, to verify that the login is correct. This mode is the same as that + used by LDAP authentication schemes in other software, such as Apache + <literal>mod_authnz_ldap</literal> and <literal>pam_ldap</literal>. This + method allows for significantly more flexibility in where the user objects + are located in the directory, but will cause two separate connections to the + LDAP server to be made. + --> +2つ目のモードでは、それはsearch/bindモードを呼び出すもので、サーバは最初に<replaceable>ldapbinddn</replaceable>と<replaceable>ldapbindpasswd</replaceable>で指定された、固定されたユーザ名とパスワードを使用してLDAPディレクトリにバインドします。 +それからデータベースにログインしようとしているユーザを検索します。 +もしユーザとパスワードが指定されていなかった場合、ディレクトリに対して匿名でバインドします。 +検索は<replaceable>ldapbasedn</replaceable>のサブツリーまで行われ、<replaceable>ldapsearchattribute</replaceable>で指定された属性に正確に一致するかどうかまで行われます。 +この検索において、一度ユーザが見つかるとサーバは切断して、クライアントで指定されたパスワードを使用してこのユーザとして再度ディレクトリにバインドします。 +これはそのログインが正しいかどうかを検証するためです。 +このモードはApache <literal>mod_authzn_heap</literal>および<literal>pam_ldap</literal>のように他のソフトウェアと同じように、LDAP認証の仕組みで使用されるものと同じです。 +この方法は、ユーザオブジェクトがディレクトリに配置されている場合に、かなりの柔軟性があります。 +しかし、LDAPサーバへの2つの分離した接続が作成されます。 + </para> + + <para> + <!-- + The following configuration options are used in both modes: + --> +次の設定オプションは両方のモードで使用されます。 + <variablelist> + <varlistentry> + <term><literal>ldapserver</literal></term> + <listitem> + <para> + <!-- + Names or IP addresses of LDAP servers to connect to. Multiple servers + may be specified, separated by spaces. + --> +接続するLDAPサーバの名称もしくはIPアドレスの名称。空白で区切ることで複数のサーバを指定できます。 + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapport</literal></term> + <listitem> + <para> + <!-- + Port number on LDAP server to connect to. If no port is specified, the + LDAP library's default port setting will be used. + --> +LDAPサーバに接続するためのポート番号。 +もしポートが指定されていない場合はLDAPライブラリ内のデフォルトポート設定が使用されます。 + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapscheme</literal></term> + <listitem> + <para> + <!-- + Set to <literal>ldaps</literal> to use LDAPS. This is a non-standard + way of using LDAP over SSL, supported by some LDAP server implementations. + See also the <literal>ldaptls</literal> option for an alternative. + --> +<literal>ldaps</literal>に設定するとLDAPSを使用します。 +これはいくつかのLDAPサーバーの実装でサポートされている、SSL経由のLDAPを使用する非標準の方法です。 +代替方法については、<literal>ldaptls</literal>オプションを参照してください。 + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldaptls</literal></term> + <listitem> + <para> + <!-- + Set to 1 to make the connection between Pgpool-II and the LDAP server + use TLS encryption. This uses the <literal>StartTLS</literal> operation + per RFC 4513. See also the <literal>ldapscheme</literal> option for an alternative. + --> +1に設定すると、Pgpool-IIとLDAPサーバ間の接続にTLSによる暗号化を使用します。 +これはRFC 4513の<literal>StartTLS</literal>操作を使用します。 +代替方法については、<literal>ldapscheme</literal>オプションを参照して下さい。 + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + <!-- + Note that using <literal>ldapscheme</literal> or + <literal>ldaptls</literal> only encrypts the traffic between the + Pgpool-II server and the LDAP server. The connection between the + Pgpool-II server and the client will still be unencrypted + unless SSL is used there as well. + --> +<literal>ldapscheme</literal>や<literal>ldaptls</literal>を使うときにはPgpool-IIサーバとLDAPサーバ間のトラフィックのみが暗号化されることに注意して下さい。 +SSLがそこでも使用されていない限り、Pgpool-IIサーバとクライアントとの接続は、暗号化されません。 + </para> + + <para> + <!-- + The following options are used in simple bind mode only: + --> +以下のオプションは単純バインド・モードのみで使用されます。 + <variablelist> + <varlistentry> + <term><literal>ldapprefix</literal></term> + <listitem> + <para> + <!-- + String to prepend to the user name when forming the DN to bind as, + when doing simple bind authentication. + --> +単純なバインド認証を行う場合のDNを生成する際にユーザ名の前に追加する文字列 + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapsuffix</literal></term> + <listitem> + <para> + <!-- + String to append to the user name when forming the DN to bind as, + when doing simple bind authentication. + --> +単純なバインド認証を行う場合のDNを生成する際にユーザ名の後に追加する文字列 + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + <!-- + The following options are used in search+bind mode only: + --> +以下のオプションはsearch/bindモードのみで使用されます。 + <variablelist> + <varlistentry> + <term><literal>ldapbasedn</literal></term> + <listitem> + <para> + <!-- + Root DN to begin the search for the user in, when doing search+bind + authentication. + --> +検索とバインドの認証を行う場合のユーザ名がログインするための検索を始めるためのルートDN + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapbinddn</literal></term> + <listitem> + <para> + <!-- + DN of user to bind to the directory with to perform the search when + doing search+bind authentication. + --> +検索とバインドの認証を行う場合のディレクトリと検索をバインドするためのユーザのDN + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapbindpasswd</literal></term> + <listitem> + <para> + <!-- + Password for user to bind to the directory with to perform the search + when doing search+bind authentication. + --> +検索とバインドの認証を行う場合のディレクトリと検索をバインドするためのユーザのパスワード + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapsearchattribute</literal></term> + <listitem> + <para> + <!-- + Attribute to match against the user name in the search when doing + search+bind authentication. If no attribute is specified, the + <literal>uid</literal> attribute will be used. + --> +検索とバインドの認証を行う場合の検索時のユーザ名に対して一致させる属性。 +属性が指定されない場合、属性<literal>uid</literal>が使用されます。 + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapsearchfilter</literal></term> + <listitem> + <para> + <!-- + The search filter to use when doing search+bind authentication. + Occurrences of <literal>$username</literal> will be replaced with the + user name. This allows for more flexible search filters than + <literal>ldapsearchattribute</literal>. + --> +search/bind認証を行うときに使用する検索フィルタです。 +<literal>$username</literal>の出現はユーザ名に置き換えられます。 +これにより<literal>ldapsearchattribute</literal>よりも柔軟な検索フィルタが可能になります。 + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapurl</literal></term> + <listitem> + <para> + <!-- + An RFC 4516 LDAP URL. This is an alternative way to write some of the + other LDAP options in a more compact and standard form. The format is + --> +RFC 4516 LDAP URL。これはその他いくつかのLDAPオプションをより簡潔、かつ一般的な形式で記述する別の方法です。 +フォーマットは以下のようになっています。 +<synopsis> +ldap[s]://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replaceable>basedn</replaceable>[?[<replaceable>attribute</replaceable>][?[<replaceable>scope</replaceable>][?[<replaceable>filter</replaceable>]]]] +</synopsis> + <!-- + <replaceable>scope</replaceable> must be one of <literal>base</literal>, + <literal>one</literal>, <literal>sub</literal>, typically the last. + (The default is <literal>base</literal>, which is normally not useful + in this application.) <replaceable>attribute</replaceable> can nominate + a single attribute, in which case it is used as a value for <literal>ldapsearchattribute</literal>. + If <replaceable>attribute</replaceable> is empty then <replaceable>filter</replaceable> + can be used as a value for <literal>ldapsearchfilter</literal>. + --> +<replaceable>scope</replaceable>は<literal>base</literal>、<literal>one</literal>、<literal>sub</literal>のいずれかでなくてはならず、一般的には最後のものです。 + (デフォルトは<literal>base</literal>です。これは通常このアプリケーションでは役に立ちません。) +<replaceable>attribute</replaceable>は単一の属性を指定できます。その場合、それは<literal>ldapsearchattribute</literal>の値として使用されます。 +もし<replaceable>attribute</replaceable>が空の場合は、<literal>ldapsearchfilter</literal>の値として<replaceable>filter</replaceable>を使用することができます。 + </para> + + <para> + <!-- + The URL scheme <literal>ldaps</literal> chooses the LDAPS method for + making LDAP connections over SSL, equivalent to using <literal>ldapscheme=ldaps</literal>. + To use encrypted LDAP connections using the <literal>StartTLS</literal> + operation, use the normal URL scheme <literal>ldap</literal> and specify the + <literal>ldaptls</literal> option in addition to <literal>ldapurl</literal>. + --> + URLスキーム<literal>ldaps</literal>は、<literal>ldapscheme=ldaps</literal>を使用するのと同じ、SSL上のLDAP接続をするLDAPS方式を選択します。 +<literal>StartTLS</literal>操作による暗号化されたLDAP接続を使用するには、通常のURLスキーム<literal>ldap</literal>を使用し、<literal>ldapurl</literal>に加え<literal>ldaptls</literal>オプションを使用しなければなりません。 + </para> + + <para> + <!-- + For non-anonymous binds, <literal>ldapbinddn</literal> and + <literal>ldapbindpasswd</literal> must be specified as separate options. + --> +非匿名バインド(non-anonymous bind)に対し、<literal>ldapbinddn</literal>および<literal>ldapbindpasswd</literal>は個別のオプションとして指定されなければなりません。 + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>usebackendpasswd</literal></term> + <listitem> + <para> + <!-- + Set to 1 to make the password used for LDAP authentication use authetication + between <productname>Pgpool-II</productname> and backend. + --> +1に設定すると、LDAP認証で使用されたパスワードを<productname>Pgpool-II</productname>とバックエンド間の認証に使用します。 + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + <!-- + It is an error to mix configuration options for simple bind with options + for search+bind. + --> +seartch/bindオプションと単純バインドに対するオプションの設定を混在させるのはエラーです。 + </para> + + <para> + <!-- + When using search+bind mode, the search can be performed using a single + attribute specified with <literal>ldapsearchattribute</literal>, or using + a custom search filter specified with + <literal>ldapsearchfilter</literal>. + Specifying <literal>ldapsearchattribute=foo</literal> is equivalent to + specifying <literal>ldapsearchfilter="(foo=$username)"</literal>. If neither + option is specified the default is + <literal>ldapsearchattribute=uid</literal>. + --> +search/bindモードを使用するときは、<literal>ldapsearchattribute</literal>で指定される単一の属性を使って、あるいは<literal>ldapsearchfilter</literal>で指定されるカスタム検索フィルターを使って、検索を実行できます。 +<literal>ldapsearchattribute=foo</literal>の指定は、<literal>ldapsearchfilter="(foo=$username)"</literal>と同等です。 +どちらのオプションもない場合は、<literal>ldapsearchattribute=uid</literal>がデフォルトです。 + </para> + + <para> + <!-- + If <productname>Pgpool-II</productname> was compiled with + <productname>OpenLDAP</productname> as the LDAP client library, the + <literal>ldapserver</literal> setting may be omitted. In that case, a + list of host names and ports is looked up via RFC 2782 DNS SRV records. + The name <literal>_ldap._tcp.DOMAIN</literal> is looked up, where + <literal>DOMAIN</literal> is extracted from <literal>ldapbasedn</literal>. + --> +<productname>Pgpool-II</productname>が、LDAPクライアントライブラリとして<productname>OpenLDAP</productname>を使用するようにコンパイルされていた場合、<literal>ldapserver</literal>の設定は省略出来ます。 +その場合、ホスト名とポート番号のリストは、RFC 2782 DNS SRVレコードを使用して検索されます。 +<literal>_ldap._tcp.DOMAIN</literal>という名前が検索され、<literal>ldapbasedn</literal>から<literal>DOMAIN</literal>が抽出されます。 + </para> + + <para> + <!-- + Here is an example for a simple-bind LDAP configuration: + --> +以下に単純バインドLDAP設定の例を示します。 +<programlisting> +host ... ldap ldapserver=ldap.example.net ldapprefix="cn=" ldapsuffix=", dc=example, dc=net" +</programlisting> + <!-- + When a connection to the database server as database + user <literal>foo</literal> is requested, Pgpool-II will attempt to + bind to the LDAP server using the DN <literal>cn=foo, dc=example, + dc=net</literal> and the password provided by the client. If that connection + succeeds, the database access is granted. + --> +データベースのユーザ、<literal>foo</literal>からデータベースサーバに接続を要求された場合、Pgpool-IIはDN <literal>cn=foo, dc=example, dc=net</literal>およびクライアントから提供されたパスワードを用いてLDAPサーバにバインドを試みます。 +その接続が成功すればデータベースへのアクセスが認められます。 + </para> + + <para> + <!-- + Here is an example for a search+bind configuration: + --> +以下はsearch/bind設定の例です。 +<programlisting> +host ... ldap ldapserver=ldap.example.net ldapbasedn="dc=example, dc=net" ldapsearchattribute=uid +</programlisting> + <!-- + When a connection to the database server as database + user <literal>foo</literal> is requested, Pgpool-II will attempt to + bind anonymously (since <literal>ldapbinddn</literal> was not specified) to + the LDAP server, perform a search for <literal>(uid=foo)</literal> + under the specified base DN. If an entry is found, it will then attempt to + bind using that found information and the password supplied by the client. + If that second connection succeeds, the database access is granted. + --> +データベースユーザ<literal>foo</literal>としてデータベースに接続するとき、Pgpool-IIは(<literal>ldapbinddn</literal>が指定されていないので)匿名的にバインドを試み、指定されたベースDNの基で<literal>(uid=foo)</literal>の検索を行います。あるエントリが見つかると、見つかった情報とクライアントから与えられたパスワードを用いて、その結果バインドを試みます。その二番目の接続が成功するとデータベースアクセスが認められます。 + </para> + + <para> + <!-- + Here is the same search+bind configuration written as a URL: + --> +URLとして記述した同じsearch/bind設定の例です。 +<programlisting> +host ... ldap ldapurl="ldap://ldap.example.net/dc=example,dc=net?uid?sub" +</programlisting> + <!-- + Some other software that supports authentication against LDAP uses the + same URL format, so it will be easier to share the configuration. + --> +LDAPに対し認証をサポートする幾つかの他のソフトウェアは同じURLフォーマットを使用します。 +従って、設定をより簡易に共有することができます。 + </para> + + <para> + <!-- + Here is an example for a search+bind configuration that uses + <literal>ldapsearchfilter</literal> instead of + <literal>ldapsearchattribute</literal> to allow authentication by + user ID or email address: + --> +<literal>ldapsearchattribute</literal>の代わりに<literal>ldapsearchfilter</literal>を使用してユーザーIDまたは電子メールアドレスによる認証を可能にするsearch/bind設定の例です。 +<programlisting> +host ... ldap ldapserver=ldap.example.net ldapbasedn="dc=example, dc=net" ldapsearchfilter="(|(uid=$username)(mail=$username))" +</programlisting> + </para> + + <para> + <!-- + Here is an example for a search+bind configuration that uses DNS SRV + discovery to find the host name(s) and port(s) for the LDAP service for the + domain name <literal>example.net</literal>: + --> +DNS SRV検出を使用してドメイン名<literal>example.net</literal>のLDAPサービスのホスト名とポート番号を検索する、search/bind設定の例です。 +<programlisting> +host ... ldap ldapbasedn="dc=example,dc=net" +</programlisting> + </para> + + <tip> + <para> + <!-- + Since LDAP often uses commas and spaces to separate the different + parts of a DN, it is often necessary to use double-quoted parameter + values when configuring LDAP options, as shown in the examples. + --> +LDAPはDNの異なる構成要素を区別するため往々にしてコンマとスペースを使用します。 +例で示されたように、LDAPオプションを設定する場合、二重引用符で括られたパラメータ値を使用することが必須となることがしばしば必須となります。 + </para> + </tip> + + <note> + <para> + <!-- + To enable LDAP support the <productname>Pgpool-II</productname> + must be configured with <command>"&#045;&#045;with-ldap"</command> + --> + LDAPサポートを有効にするには、<productname>Pgpool-II</productname>が<command>"--with-ldap"</command>をつけてconfigureされていなければなりません。 + </para> + </note> + </sect2> </sect1> <sect1 id="auth-different-auth-method"> diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index 72b52660b..11ee3e757 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -444,11 +444,24 @@ </para> </listitem> </varlistentry> - </variablelist> - </para> - </listitem> - </varlistentry> + <varlistentry> + <term><literal>ldap</literal></term> + <listitem> + <para> + Authenticate using LDAP server. + See <xref linkend="auth-ldap"> for more details. + </para> + <para> + To enable LDAP support the <productname>Pgpool-II</productname> + must be configured with <command>"&#045;&#045;with-ldap"</command> + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </listitem> + </varlistentry> <varlistentry> <term><replaceable>auth-options</replaceable></term> @@ -772,6 +785,315 @@ </para> </note> </sect2> + + <sect2 id="auth-ldap"> + <title>LDAP Authentication</title> + + <indexterm zone="auth-ldap"> + <primary>LDAP</primary> + </indexterm> + + <para> + This authentication method uses LDAP as the password cerification method. + LDAP is used only to validate the user name/password pairs. Therefore the user must + already exist in the database before LDAP can be used for authentication. + </para> + + <para> + LDAP authentication can operate in two modes. In the first mode, which we + will call the simple bind mode, the server will bind to the distinguished + name constructed as + <replaceable>prefix</replaceable> <replaceable>username</replaceable> <replaceable>suffix</replaceable>. + Typically, the <replaceable>prefix</replaceable> parameter is used to specify + <literal>cn=</literal>, or <replaceable>DOMAIN</replaceable><literal>\</literal> + in an Active Directory environment. <replaceable>suffix</replaceable> is used + to specify the remaining part of the DN in a non-Active Directory environment. + </para> + + <para> + In the second mode, which we will call the search+bind mode, the server first + binds to the LDAP directory with a fixed user name and password, specified + with <replaceable>ldapbinddn</replaceable> and <replaceable>ldapbindpasswd</replaceable>, + and performs a search for the user trying to log in to the database. If no + user and password is configured, an anonymous bind will be attempted to the + directory. The search will be performed over the subtree at + <replaceable>ldapbasedn</replaceable>, and will try to do an exact match of + the attribute specified in <replaceable>ldapsearchattribute</replaceable>. + Once the user has been found in this search, the server disconnects and + re-binds to the directory as this user, using the password specified by the + client, to verify that the login is correct. This mode is the same as that + used by LDAP authentication schemes in other software, such as Apache + <literal>mod_authnz_ldap</literal> and <literal>pam_ldap</literal>. This + method allows for significantly more flexibility in where the user objects + are located in the directory, but will cause two separate connections to the + LDAP server to be made. + </para> + + <para> + The following configuration options are used in both modes: + <variablelist> + <varlistentry> + <term><literal>ldapserver</literal></term> + <listitem> + <para> + Names or IP addresses of LDAP servers to connect to. Multiple servers + may be specified, separated by spaces. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapport</literal></term> + <listitem> + <para> + Port number on LDAP server to connect to. If no port is specified, the + LDAP library's default port setting will be used. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapscheme</literal></term> + <listitem> + <para> + Set to <literal>ldaps</literal> to use LDAPS. This is a non-standard + way of using LDAP over SSL, supported by some LDAP server implementations. + See also the <literal>ldaptls</literal> option for an alternative. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldaptls</literal></term> + <listitem> + <para> + Set to 1 to make the connection between Pgpool-II and the LDAP server + use TLS encryption. This uses the <literal>StartTLS</literal> operation + per RFC 4513. See also the <literal>ldapscheme</literal> option for an alternative. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + Note that using <literal>ldapscheme</literal> or + <literal>ldaptls</literal> only encrypts the traffic between the + Pgpool-II server and the LDAP server. The connection between the + Pgpool-II server and the client will still be unencrypted + unless SSL is used there as well. + </para> + + <para> + The following options are used in simple bind mode only: + <variablelist> + <varlistentry> + <term><literal>ldapprefix</literal></term> + <listitem> + <para> + String to prepend to the user name when forming the DN to bind as, + when doing simple bind authentication. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapsuffix</literal></term> + <listitem> + <para> + String to append to the user name when forming the DN to bind as, + when doing simple bind authentication. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + The following options are used in search+bind mode only: + <variablelist> + <varlistentry> + <term><literal>ldapbasedn</literal></term> + <listitem> + <para> + Root DN to begin the search for the user in, when doing search+bind + authentication. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapbinddn</literal></term> + <listitem> + <para> + DN of user to bind to the directory with to perform the search when + doing search+bind authentication. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapbindpasswd</literal></term> + <listitem> + <para> + Password for user to bind to the directory with to perform the search + when doing search+bind authentication. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapsearchattribute</literal></term> + <listitem> + <para> + Attribute to match against the user name in the search when doing + search+bind authentication. If no attribute is specified, the + <literal>uid</literal> attribute will be used. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapsearchfilter</literal></term> + <listitem> + <para> + The search filter to use when doing search+bind authentication. + Occurrences of <literal>$username</literal> will be replaced with the + user name. This allows for more flexible search filters than + <literal>ldapsearchattribute</literal>. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>ldapurl</literal></term> + <listitem> + <para> + An RFC 4516 LDAP URL. This is an alternative way to write some of the + other LDAP options in a more compact and standard form. The format is +<synopsis> +ldap[s]://<replaceable>host</replaceable>[:<replaceable>port</replaceable>]/<replaceable>basedn</replaceable>[?[<replaceable>attribute</replaceable>][?[<replaceable>scope</replaceable>][?[<replaceable>filter</replaceable>]]]] +</synopsis> + <replaceable>scope</replaceable> must be one of <literal>base</literal>, + <literal>one</literal>, <literal>sub</literal>, typically the last. + (The default is <literal>base</literal>, which is normally not useful + in this application.) <replaceable>attribute</replaceable> can nominate + a single attribute, in which case it is used as a value for <literal>ldapsearchattribute</literal>. + If <replaceable>attribute</replaceable> is empty then <replaceable>filter</replaceable> + can be used as a value for <literal>ldapsearchfilter</literal>. + </para> + + <para> + The URL scheme <literal>ldaps</literal> chooses the LDAPS method for + making LDAP connections over SSL, equivalent to using <literal>ldapscheme=ldaps</literal>. + To use encrypted LDAP connections using the <literal>StartTLS</literal> + operation, use the normal URL scheme <literal>ldap</literal> and specify the + <literal>ldaptls</literal> option in addition to <literal>ldapurl</literal>. + </para> + + <para> + For non-anonymous binds, <literal>ldapbinddn</literal> and + <literal>ldapbindpasswd</literal> must be specified as separate options. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>backendusepasswd</literal></term> + <listitem> + <para> + Set to 1 to make the password used for LDAP authentication use authetication + between <productname>Pgpool-II</productname> and backendd. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + + <para> + It is an error to mix configuration options for simple bind with options + for search+bind. + </para> + + <para> + When using search+bind mode, the search can be performed using a single + attribute specified with <literal>ldapsearchattribute</literal>, or using + a custom search filter specified with + <literal>ldapsearchfilter</literal>. + Specifying <literal>ldapsearchattribute=foo</literal> is equivalent to + specifying <literal>ldapsearchfilter="(foo=$username)"</literal>. If neither + option is specified the default is + <literal>ldapsearchattribute=uid</literal>. + </para> + + <para> + If <productname>Pgpool-II</productname> was compiled with + <productname>OpenLDAP</productname> as the LDAP client library, the + <literal>ldapserver</literal> setting may be omitted. In that case, a + list of host names and ports is looked up via RFC 2782 DNS SRV records. + The name <literal>_ldap._tcp.DOMAIN</literal> is looked up, where + <literal>DOMAIN</literal> is extracted from <literal>ldapbasedn</literal>. + </para> + + <para> + Here is an example for a simple-bind LDAP configuration: +<programlisting> +host ... ldap ldapserver=ldap.example.net ldapprefix="cn=" ldapsuffix=", dc=example, dc=net" +</programlisting> + When a connection to the database server as database + user <literal>foo</literal> is requested, Pgpool-II will attempt to + bind to the LDAP server using the DN <literal>cn=foo, dc=example, + dc=net</literal> and the password provided by the client. If that connection + succeeds, the database access is granted. + </para> + + <para> + Here is an example for a search+bind configuration: +<programlisting> +host ... ldap ldapserver=ldap.example.net ldapbasedn="dc=example, dc=net" ldapsearchattribute=uid +</programlisting> + When a connection to the database server as database + user <literal>foo</literal> is requested, Pgpool-II will attempt to + bind anonymously (since <literal>ldapbinddn</literal> was not specified) to + the LDAP server, perform a search for <literal>(uid=foo)</literal> + under the specified base DN. If an entry is found, it will then attempt to + bind using that found information and the password supplied by the client. + If that second connection succeeds, the database access is granted. + </para> + + <para> + Here is the same search+bind configuration written as a URL: +<programlisting> +host ... ldap ldapurl="ldap://ldap.example.net/dc=example,dc=net?uid?sub" +</programlisting> + Some other software that supports authentication against LDAP uses the + same URL format, so it will be easier to share the configuration. + </para> + + <para> + Here is an example for a search+bind configuration that uses + <literal>ldapsearchfilter</literal> instead of + <literal>ldapsearchattribute</literal> to allow authentication by + user ID or email address: +<programlisting> +host ... ldap ldapserver=ldap.example.net ldapbasedn="dc=example, dc=net" ldapsearchfilter="(|(uid=$username)(mail=$username))" +</programlisting> + </para> + + <para> + Here is an example for a search+bind configuration that uses DNS SRV + discovery to find the host name(s) and port(s) for the LDAP service for the + domain name <literal>example.net</literal>: +<programlisting> +host ... ldap ldapbasedn="dc=example,dc=net" +</programlisting> + </para> + + <tip> + <para> + Since LDAP often uses commas and spaces to separate the different + parts of a DN, it is often necessary to use double-quoted parameter + values when configuring LDAP options, as shown in the examples. + </para> + </tip> + + <note> + <para> + To enable LDAP support the <productname>Pgpool-II</productname> + must be configured with <command>"&#045;&#045;with-ldap"</command> + </para> + </note> + </sect2> </sect1> <sect1 id="auth-different-auth-method"> diff --git a/src/auth/pool_auth.c b/src/auth/pool_auth.c index f2fddea9b..bc0462680 100644 --- a/src/auth/pool_auth.c +++ b/src/auth/pool_auth.c @@ -1478,6 +1478,10 @@ authenticate_frontend(POOL_CONNECTION * frontend) case uaPAM: break; #endif /* USE_PAM */ +#ifdef USE_LDAP + case uaLDAP: + break; +#endif /* USE_LDAP */ } } diff --git a/src/auth/pool_hba.c b/src/auth/pool_hba.c index 616306457..d008f1969 100644 --- a/src/auth/pool_hba.c +++ b/src/auth/pool_hba.c @@ -51,6 +51,10 @@ #include "auth/pool_passwd.h" #include "protocol/pool_process_query.h" +#ifdef USE_LDAP +#include <ldap.h> +#endif + #define MULTI_VALUE_SEP "\001" /* delimiter for multi-valued column strings */ #define MAX_TOKEN 256 @@ -152,13 +156,6 @@ static POOL_STATUS CheckUserExist(char *username); static POOL_STATUS CheckPAMAuth(POOL_CONNECTION * frontend, char *user, char *password); static int pam_passwd_conv_proc(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr); -/* - * recv_password_packet is usually used with authentications that require a client - * password. However, pgpool's hba function only uses it for PAM authentication, - * so declare a prototype here in "#ifdef USE_PAM" to avoid compilation warning. - */ -static char *recv_password_packet(POOL_CONNECTION * frontend); - static struct pam_conv pam_passw_conv = { &pam_passwd_conv_proc, NULL @@ -170,6 +167,28 @@ static POOL_CONNECTION * pam_frontend_kludge; /* Workaround for passing * into pam_passwd_conv_proc */ #endif /* USE_PAM */ +#ifdef USE_LDAP +#include <ldap.h> + +static POOL_STATUS CheckLDAPAuth(POOL_CONNECTION *frontend); + +/* LDAP_OPT_DIAGNOSTIC_MESSAGE is the newer spelling */ +#ifndef LDAP_OPT_DIAGNOSTIC_MESSAGE +#define LDAP_OPT_DIAGNOSTIC_MESSAGE LDAP_OPT_ERROR_STRING +#endif + +#endif /* USE_LDAP */ + + +#if defined(USE_PAM) || defined(USE_LDAP) +/* + * recv_password_packet is usually used with authentications that require a client + * password. However, pgpool's hba function uses it for PAM or LDAP authentication, + * so declare a prototype here in "#if defined(USE_PAM or USE_LDAP)" to avoid + * compilation warning. + */ +static char *recv_password_packet(POOL_CONNECTION * frontend); +#endif /* USE_PAM or USE_LDAP */ /* * Read the config file and create a List of HbaLine records for the contents. @@ -275,6 +294,56 @@ load_hba(char *hbapath) return true; } +/* + * Macros used to check and report on invalid configuration options. + * On error: log a message at level elevel, set *err_msg, and exit the function. + * These macros are not as general-purpose as they look, because they know + * what the calling function's error-exit value is. + * + * INVALID_AUTH_OPTION = reports when an option is specified for a method where it's + * not supported. + * REQUIRE_AUTH_OPTION = same as INVALID_AUTH_OPTION, except it also checks if the + * method is actually the one specified. Used as a shortcut when + * the option is only valid for one authentication method. + * MANDATORY_AUTH_ARG = check if a required option is set for an authentication method, + * reporting error if it's not. + */ +#define INVALID_AUTH_OPTION(optname, validmethods) \ +do { \ + ereport(elevel, \ + (errcode(ERRCODE_CONFIG_FILE_ERROR), \ + /* translator: the second %s is a list of auth methods */ \ + errmsg("authentication option \"%s\" is only valid for authentication methods %s", \ + optname, _(validmethods)), \ + errcontext("line %d of configuration file \"%s\"", \ + line_num, HbaFileName))); \ + *err_msg = psprintf("authentication option \"%s\" is only valid for authentication methods %s", \ + optname, validmethods); \ + return false; \ +} while (0) + +#define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) \ +do { \ + if (hbaline->auth_method != methodval) \ + INVALID_AUTH_OPTION(optname, validmethods); \ +} while (0) + +#define MANDATORY_AUTH_ARG(argvar, argname, authname) \ +do { \ + if (argvar == NULL) { \ + ereport(elevel, \ + (errcode(ERRCODE_CONFIG_FILE_ERROR), \ + errmsg("authentication method \"%s\" requires argument \"%s\" to be set", \ + authname, argname), \ + errcontext("line %d of configuration file \"%s\"", \ + line_num, HbaFileName))); \ + *err_msg = psprintf("authentication method \"%s\" requires argument \"%s\" to be set", \ + authname, argname); \ + return NULL; \ + } \ +} while (0) + + static HbaLine * parse_hba_line(TokenizedLine *tok_line, int elevel) { @@ -633,6 +702,10 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) #ifdef USE_PAM else if (strcmp(token->string, "pam") == 0) parsedline->auth_method = uaPAM; +#endif +#ifdef USE_LDAP + else if (strcmp(token->string, "ldap") == 0) + parsedline->auth_method = uaLDAP; #endif else { @@ -685,6 +758,71 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) } } +#ifdef USE_LDAP + /* + * Check if the selected authentication method has any mandatory arguments + * that are not set. + */ + if (parsedline->auth_method == uaLDAP) + { +#ifndef HAVE_LDAP_INITIALIZE + /* Not mandatory for OpenLDAP, because it can use DNS SRV records */ + MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap"); +#endif + + /* + * LDAP can operate in two modes: either with a direct bind, using + * ldapprefix and ldapsuffix, or using a search+bind, using + * ldapbasedn, ldapbinddn, ldapbindpasswd and one of + * ldapsearchattribute or ldapsearchfilter. Disallow mixing these + * parameters. + */ + if (parsedline->ldapprefix || parsedline->ldapsuffix) + { + if (parsedline->ldapbasedn || + parsedline->ldapbinddn || + parsedline->ldapbindpasswd || + parsedline->ldapsearchattribute || + parsedline->ldapsearchfilter) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix"; + return NULL; + } + } + else if (!parsedline->ldapbasedn) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"; + return NULL; + } + + /* + * When using search+bind, you can either use a simple attribute + * (defaulting to "uid") or a fully custom search filter. You can't + * do both. + */ + if (parsedline->ldapsearchattribute && parsedline->ldapsearchfilter) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("cannot use ldapsearchattribute together with ldapsearchfilter"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "cannot use ldapsearchattribute together with ldapsearchfilter"; + return NULL; + } + } +#endif + return parsedline; } @@ -700,6 +838,10 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, { int line_num = hbaline->linenumber; +#ifdef USE_LDAP + hbaline->ldapscope = LDAP_SCOPE_SUBTREE; +#endif + if (strcmp(name, "pamservice") == 0) { #ifdef USE_PAM @@ -751,6 +893,258 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, *err_msg = "pamservice authentication option cannot be used because PAM is not supported by this build"; #endif + } + else if (strcmp(name, "ldapurl") == 0) + { +#ifdef USE_LDAP +#ifdef LDAP_API_FEATURE_X_OPENLDAP + LDAPURLDesc *urldata; + int rc; +#endif + REQUIRE_AUTH_OPTION(uaLDAP, "ldapurl", "ldap"); + +#ifdef LDAP_API_FEATURE_X_OPENLDAP + rc = ldap_url_parse(val, &urldata); + if (rc != LDAP_SUCCESS) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc)))); + *err_msg = psprintf("could not parse LDAP URL \"%s\": %s", + val, ldap_err2string(rc)); + return false; + } + + if (strcmp(urldata->lud_scheme, "ldap") != 0 && + strcmp(urldata->lud_scheme, "ldaps") != 0) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme))); + *err_msg = psprintf("unsupported LDAP URL scheme: %s", + urldata->lud_scheme); + ldap_free_urldesc(urldata); + return false; + } + + if (urldata->lud_scheme) + hbaline->ldapscheme = pstrdup(urldata->lud_scheme); + if (urldata->lud_host) + hbaline->ldapserver = pstrdup(urldata->lud_host); + hbaline->ldapport = urldata->lud_port; + if (urldata->lud_dn) + hbaline->ldapbasedn = pstrdup(urldata->lud_dn); + + if (urldata->lud_attrs) + hbaline->ldapsearchattribute = pstrdup(urldata->lud_attrs[0]); /* only use first one */ + hbaline->ldapscope = urldata->lud_scope; + if (urldata->lud_filter) + hbaline->ldapsearchfilter = pstrdup(urldata->lud_filter); + ldap_free_urldesc(urldata); +#else /* not OpenLDAP */ + ereport(elevel, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("LDAP URLs not supported on this platform"))); + *err_msg = "LDAP URLs not supported on this platform"; +#endif /* not OpenLDAP */ +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldaptls") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap"); + if (strcmp(val, "1") == 0) + hbaline->ldaptls = true; + else + hbaline->ldaptls = false; +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapscheme") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapscheme", "ldap"); + if (strcmp(val, "ldap") != 0 && strcmp(val, "ldaps") != 0) + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid ldapscheme value: \"%s\"", val), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + hbaline->ldapscheme = pstrdup(val); +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapserver") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap"); + hbaline->ldapserver = pstrdup(val); +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapport") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap"); + hbaline->ldapport = atoi(val); + if (hbaline->ldapport == 0) + { + ereport(elevel, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid LDAP port number: \"%s\"", val), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = psprintf("invalid LDAP port number: \"%s\"", val); + return false; + } +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapbinddn") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap"); + hbaline->ldapbinddn = pstrdup(val); +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapbindpasswd") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap"); + hbaline->ldapbindpasswd = pstrdup(val); +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapsearchattribute") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap"); + hbaline->ldapsearchattribute = pstrdup(val); +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapsearchfilter") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchfilter", "ldap"); + hbaline->ldapsearchfilter = pstrdup(val); +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapbasedn") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap"); + hbaline->ldapbasedn = pstrdup(val); +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapprefix") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap"); + hbaline->ldapprefix = pstrdup(val); +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "ldapsuffix") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap"); + hbaline->ldapsuffix = pstrdup(val); +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ + } + else if (strcmp(name, "backend_use_passwd") == 0) + { +#ifdef USE_LDAP + REQUIRE_AUTH_OPTION(uaLDAP, "backend_use_passwd", "ldap"); + if (strcmp(val, "1") == 0) + hbaline->backend_use_passwd = true; + else + hbaline->backend_use_passwd = false; +#else /* USE_LDAP */ + ereport(elevel, + (errmsg("ldap authentication option can only be configured for authentication method \"ldap\""), + errhint("Compile with --with-ldap to use LDAP authentication."), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + *err_msg = "ldap authentication option cannot be used because LDAP is not supported by this build"; +#endif /* USE_LDAP */ } else { @@ -897,6 +1291,12 @@ ClientAuthentication(POOL_CONNECTION * frontend) break; #endif /* USE_PAM */ +#ifdef USE_LDAP + case uaLDAP: + status = CheckLDAPAuth(frontend); + break; +#endif /* USE_LDAP */ + case uaTrust: status = POOL_CONTINUE; break; @@ -960,7 +1360,7 @@ sendAuthRequest(POOL_CONNECTION * frontend, AuthRequest areq) } -#ifdef USE_PAM /* see the prototype comment */ +#if defined(USE_PAM) || defined(USE_LDAP) /* * Collect password response packet from frontend. @@ -1012,7 +1412,7 @@ recv_password_packet(POOL_CONNECTION * frontend) return returnVal; } -#endif /* USE_PAM */ +#endif /* USE_PAM or USE_LDAP */ /* * Tell the user the authentication failed. @@ -1084,7 +1484,14 @@ auth_failed(POOL_CONNECTION * frontend) "PAM authentication with pgpool failed for user \"%s\"", frontend->username); break; -#endif /* USE_PAM */ +#endif /* USE_LDAP */ +#ifdef USE_LDAP + case uaLDAP: + snprintf(errmessage, messagelen, + "LDAP authentication with pgpool failed for user \"%s\"", + frontend->username); + break; +#endif /* USE_LDAP */ default: snprintf(errmessage, messagelen, "authentication with pgpool failed for user \"%s\": invalid authentication method", @@ -2044,6 +2451,476 @@ static POOL_STATUS CheckPAMAuth(POOL_CONNECTION * frontend, char *user, char *pa #endif /* USE_PAM */ +#ifdef USE_LDAP + +/* + * Add a detail error message text to the current error if one can be + * constructed from the LDAP 'diagnostic message'. + */ +static int +errdetail_for_ldap(LDAP *ldap) +{ + char *message; + int rc; + + rc = ldap_get_option(ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &message); + if (rc == LDAP_SUCCESS && message != NULL) + { + errdetail("LDAP diagnostics: %s", message); + ldap_memfree(message); + } + + return 0; +} + + +static int +InitializeLDAPConnection(POOL_CONNECTION *frontend, LDAP **ldap) +{ + const char *scheme; + int ldapversion = LDAP_VERSION3; + int r; + + scheme = frontend->pool_hba->ldapscheme; + if (scheme == NULL) + scheme = "ldap"; +#ifdef HAVE_LDAP_INITIALIZE + + /* + * OpenLDAP provides a non-standard extension ldap_initialize() that takes + * a list of URIs, allowing us to request "ldaps" instead of "ldap". It + * also provides ldap_domain2hostlist() to find LDAP servers automatically + * using DNS SRV. They were introduced in the same version, so for now we + * don't have an extra configure check for the latter. + */ + { + StringInfoData uris; + char *hostlist = NULL; + char *p; + bool append_port; + + /* We'll build a space-separated scheme://hostname:port list here */ + initStringInfo(&uris); + + /* + * If pg_hba.conf provided no hostnames, we can ask OpenLDAP to try to + * find some by extracting a domain name from the base DN and looking + * up DSN SRV records for _ldap._tcp.<domain>. + */ + if (!frontend->pool_hba->ldapserver || frontend->pool_hba->ldapserver[0] == '\0') + { + char *domain; + + /* ou=blah,dc=foo,dc=bar -> foo.bar */ + if (ldap_dn2domain(frontend->pool_hba->ldapbasedn, &domain)) + { + ereport(LOG, + (errmsg("could not extract domain name from ldapbasedn"))); + return -1; + } + + /* Look up a list of LDAP server hosts and port numbers */ + if (ldap_domain2hostlist(domain, &hostlist)) + { + ereport(LOG, + (errmsg("LDAP authentication could not find DNS SRV records for \"%s\"", + domain), + (errhint("Set an LDAP server name explicitly.")))); + ldap_memfree(domain); + return -1; + } + ldap_memfree(domain); + + /* We have a space-separated list of host:port entries */ + p = hostlist; + append_port = false; + } + else + { + /* We have a space-separated list of hosts from pg_hba.conf */ + p = frontend->pool_hba->ldapserver; + append_port = true; + } + + /* Convert the list of host[:port] entries to full URIs */ + do + { + size_t size; + + /* Find the span of the next entry */ + size = strcspn(p, " "); + + /* Append a space separator if this isn't the first URI */ + if (uris.len > 0) + appendStringInfoChar(&uris, ' '); + + /* Append scheme://host:port */ + appendStringInfoString(&uris, scheme); + appendStringInfoString(&uris, "://"); + appendBinaryStringInfo(&uris, p, size); + if (append_port) + appendStringInfo(&uris, ":%d", frontend->pool_hba->ldapport); + + /* Step over this entry and any number of trailing spaces */ + p += size; + while (*p == ' ') + ++p; + } while (*p); + + /* Free memory from OpenLDAP if we looked up SRV records */ + if (hostlist) + ldap_memfree(hostlist); + + /* Finally, try to connect using the URI list */ + r = ldap_initialize(ldap, uris.data); + pfree(uris.data); + if (r != LDAP_SUCCESS) + { + ereport(LOG, + (errmsg("could not initialize LDAP: %s", + ldap_err2string(r)))); + + return -1; + } + } +#else + if (strcmp(scheme, "ldaps") == 0) + { + ereport(LOG, + (errmsg("ldaps not supported with this LDAP library"))); + + return -1; + } + *ldap = ldap_init(frontend->pool_hba->ldapserver, frontend->pool_hba->ldapport); + if (!*ldap) + { + ereport(LOG, + (errmsg("could not initialize LDAP: %m"))); + + return -1; + } +#endif + + if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS) + { + ereport(LOG, + (errmsg("could not set LDAP protocol version: %s", + ldap_err2string(r)), + errdetail_for_ldap(*ldap))); + ldap_unbind(*ldap); + return -1; + } + + if (frontend->pool_hba->ldaptls) + { + if ((r = ldap_start_tls_s(*ldap, NULL, NULL)) != LDAP_SUCCESS) + { + ereport(LOG, + (errmsg("could not start LDAP TLS session: %s", + ldap_err2string(r)), + errdetail_for_ldap(*ldap))); + ldap_unbind(*ldap); + return -1; + } + } + + return 0; +} + +/* Placeholders recognized by FormatSearchFilter. For now just one. */ +#define LPH_USERNAME "$username" +#define LPH_USERNAME_LEN (sizeof(LPH_USERNAME) - 1) + +/* Not all LDAP implementations define this. */ +#ifndef LDAP_NO_ATTRS +#define LDAP_NO_ATTRS "1.1" +#endif + +/* Not all LDAP implementations define this. */ +#ifndef LDAPS_PORT +#define LDAPS_PORT 636 +#endif + +/* + * Return a newly allocated C string copied from "pattern" with all + * occurrences of the placeholder "$username" replaced with "user_name". + */ +static char * +FormatSearchFilter(const char *pattern, const char *user_name) +{ + StringInfoData output; + + initStringInfo(&output); + while (*pattern != '\0') + { + if (strncmp(pattern, LPH_USERNAME, LPH_USERNAME_LEN) == 0) + { + appendStringInfoString(&output, user_name); + pattern += LPH_USERNAME_LEN; + } + else + appendStringInfoChar(&output, *pattern++); + } + + return output.data; +} + + + +/* + * Check authentication against LDAP. + */ +static POOL_STATUS CheckLDAPAuth(POOL_CONNECTION * frontend) +{ + char *passwd; + LDAP *ldap; + int r; + char *fulluser; + const char *server_name; + +#ifdef HAVE_LDAP_INITIALIZE + + /* + * For OpenLDAP, allow empty hostname if we have a basedn. We'll look for + * servers with DNS SRV records via OpenLDAP library facilities. + */ + if ((!frontend->pool_hba->ldapserver || frontend->pool_hba->ldapserver[0] == '\0') && + (!frontend->pool_hba->ldapbasedn || frontend->pool_hba->ldapbasedn[0] == '\0')) + { + ereport(LOG, + (errmsg("LDAP server not specified, and no ldapbasedn"))); + return -1; + } +#else + if (!frontend->pool_hba->ldapserver || frontend->pool_hba->ldapserver[0] == '\0') + { + ereport(LOG, + (errmsg("LDAP server not specified"))); + return -1; + } +#endif + + /* + * If we're using SRV records, we don't have a server name so we'll just + * show an empty string in error messages. + */ + server_name = frontend->pool_hba->ldapserver ? frontend->pool_hba->ldapserver : ""; + + if (frontend->pool_hba->ldapport == 0) + { + if (frontend->pool_hba->ldapscheme != NULL && + strcmp(frontend->pool_hba->ldapscheme, "ldaps") == 0) + frontend->pool_hba->ldapport = LDAPS_PORT; + else + frontend->pool_hba->ldapport = LDAP_PORT; + } + + sendAuthRequest(frontend, AUTH_REQ_PASSWORD); + + passwd = recv_password_packet(frontend); + if (passwd == NULL) + return -2; /* client wouldn't send password */ + + if (InitializeLDAPConnection(frontend, &ldap) == -1) + { + /* Error message already sent */ + pfree(passwd); + return -1; + } + + if (frontend->pool_hba->backend_use_passwd) + { + frontend->pwd_size = strlen(passwd); + memcpy(frontend->password, passwd, frontend->pwd_size); + frontend->passwordType = PASSWORD_TYPE_PLAINTEXT; + } + + if (frontend->pool_hba->ldapbasedn) + { + /* + * First perform an LDAP search to find the DN for the user we are + * trying to log in as. + */ + char *filter; + LDAPMessage *search_message; + LDAPMessage *entry; + char *attributes[] = {LDAP_NO_ATTRS, NULL}; + char *dn; + char *c; + int count; + + /* + * Disallow any characters that we would otherwise need to escape, + * since they aren't really reasonable in a username anyway. Allowing + * them would make it possible to inject any kind of custom filters in + * the LDAP filter. + */ + for (c = frontend->username; *c; c++) + { + if (*c == '*' || + *c == '(' || + *c == ')' || + *c == '\\' || + *c == '/') + { + ereport(LOG, + (errmsg("invalid character in user name for LDAP authentication"))); + ldap_unbind(ldap); + pfree(passwd); + return -1; + } + } + + /* + * Bind with a pre-defined username/password (if available) for + * searching. If none is specified, this turns into an anonymous bind. + */ + r = ldap_simple_bind_s(ldap, + frontend->pool_hba->ldapbinddn ? frontend->pool_hba->ldapbinddn : "", + frontend->pool_hba->ldapbindpasswd ? frontend->pool_hba->ldapbindpasswd : ""); + if (r != LDAP_SUCCESS) + { + ereport(LOG, + (errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s", + frontend->pool_hba->ldapbinddn ? frontend->pool_hba->ldapbinddn : "", + server_name, + ldap_err2string(r)), + errdetail_for_ldap(ldap))); + ldap_unbind(ldap); + pfree(passwd); + return -1; + } + + /* Build a custom filter or a single attribute filter? */ + if (frontend->pool_hba->ldapsearchfilter) + filter = FormatSearchFilter(frontend->pool_hba->ldapsearchfilter, frontend->username); + else if (frontend->pool_hba->ldapsearchattribute) + filter = psprintf("(%s=%s)", frontend->pool_hba->ldapsearchattribute, frontend->username); + else + filter = psprintf("(uid=%s)", frontend->username); + + r = ldap_search_s(ldap, + frontend->pool_hba->ldapbasedn, + frontend->pool_hba->ldapscope, + filter, + attributes, + 0, + &search_message); + + if (r != LDAP_SUCCESS) + { + ereport(LOG, + (errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s", + filter, server_name, ldap_err2string(r)), + errdetail_for_ldap(ldap))); + ldap_unbind(ldap); + pfree(passwd); + pfree(filter); + return -1; + } + + count = ldap_count_entries(ldap, search_message); + if (count != 1) + { + if (count == 0) + ereport(LOG, + (errmsg("LDAP user \"%s\" does not exist", frontend->username), + errdetail("LDAP search for filter \"%s\" on server \"%s\" returned no entries.", + filter, server_name))); + else + ereport(LOG, + (errmsg("LDAP user \"%s\" is not unique", frontend->username), + errdetail_plural("LDAP search for filter \"%s\" on server \"%s\" returned %d entry.", + "LDAP search for filter \"%s\" on server \"%s\" returned %d entries.", + count, + filter, server_name, count))); + + ldap_unbind(ldap); + pfree(passwd); + pfree(filter); + ldap_msgfree(search_message); + return -1; + } + + entry = ldap_first_entry(ldap, search_message); + dn = ldap_get_dn(ldap, entry); + if (dn == NULL) + { + int error; + + (void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error); + ereport(LOG, + (errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s", + filter, server_name, + ldap_err2string(error)), + errdetail_for_ldap(ldap))); + ldap_unbind(ldap); + pfree(passwd); + pfree(filter); + ldap_msgfree(search_message); + return -1; + } + fulluser = pstrdup(dn); + + pfree(filter); + ldap_memfree(dn); + ldap_msgfree(search_message); + + /* Unbind and disconnect from the LDAP server */ + r = ldap_unbind_s(ldap); + if (r != LDAP_SUCCESS) + { + ereport(LOG, + (errmsg("could not unbind after searching for user \"%s\" on server \"%s\"", + fulluser, server_name))); + pfree(passwd); + pfree(fulluser); + return -1; + } + + /* + * Need to re-initialize the LDAP connection, so that we can bind to + * it with a different username. + */ + if (InitializeLDAPConnection(frontend, &ldap) == -1) + { + pfree(passwd); + pfree(fulluser); + + /* Error message already sent */ + return -1; + } + } + else + fulluser = psprintf("%s%s%s", + frontend->pool_hba->ldapprefix ? frontend->pool_hba->ldapprefix : "", + frontend->username, + frontend->pool_hba->ldapsuffix ? frontend->pool_hba->ldapsuffix : ""); + + r = ldap_simple_bind_s(ldap, fulluser, passwd); + + if (r != LDAP_SUCCESS) + { + ereport(LOG, + (errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s", + fulluser, server_name, ldap_err2string(r)), + errdetail_for_ldap(ldap))); + ldap_unbind(ldap); + pfree(passwd); + pfree(fulluser); + return -1; + } + + ldap_unbind(ldap); + pfree(passwd); + pfree(fulluser); + + return 0; +} + + +#endif /* USE_LDAP */ + #ifdef NOT_USED static POOL_STATUS CheckUserExist(char *username) { diff --git a/src/include/auth/pool_hba.h b/src/include/auth/pool_hba.h index 77cad9d7f..8c9e1ab0b 100644 --- a/src/include/auth/pool_hba.h +++ b/src/include/auth/pool_hba.h @@ -29,6 +29,10 @@ #include "parser/pg_list.h" #include "pool.h" +#ifdef USE_LDAP +#include <ldap.h> +#endif + /* UserAuth type used for HBA which indicates the authentication method */ typedef enum UserAuth { @@ -46,6 +50,9 @@ typedef enum UserAuth #ifdef USE_PAM ,uaPAM #endif /* USE_PAM */ +#ifdef USE_LDAP + ,uaLDAP +#endif /* USE_LDAP */ } UserAuth; @@ -79,6 +86,21 @@ struct HbaLine UserAuth auth_method; char *pamservice; bool pam_use_hostname; + + bool ldaptls; + char *ldapscheme; + char *ldapserver; + int ldapport; + char *ldapbinddn; + char *ldapbindpasswd; + char *ldapsearchattribute; + char *ldapsearchfilter; + char *ldapbasedn; + int ldapscope; + char *ldapprefix; + char *ldapsuffix; + /* Additional LDAPl option with pgpool */ + bool backend_use_passwd; /* If true, pgpool use same password to auth backend */ }; extern bool load_hba(char *hbapath); diff --git a/src/include/config.h.in b/src/include/config.h.in index 3b61a735c..dcb2f7373 100644 --- a/src/include/config.h.in +++ b/src/include/config.h.in @@ -46,6 +46,12 @@ /* Define to 1 if you have the <inttypes.h> header file. */ #undef HAVE_INTTYPES_H +/* Define to 1 if you have the <ldap.h> header file. */ +#undef HAVE_LDAP_H + +/* Define to 1 if you have the `ldap_initialize' function. */ +#undef HAVE_LDAP_INITIALIZE + /* Define to 1 if you have the `BSD' library (-lBSD). */ #undef HAVE_LIBBSD @@ -64,6 +70,9 @@ /* Define to 1 if you have the `lc' library (-llc). */ #undef HAVE_LIBLC +/* Define to 1 if you have the `ldap' library (-lldap). */ +#undef HAVE_LIBLDAP + /* Define to 1 if you have the `m' library (-lm). */ #undef HAVE_LIBM @@ -322,6 +331,9 @@ (--enable-float8-byval) */ #undef USE_FLOAT8_BYVAL +/* Define to 1 to build with LDAP support. (--with-ldap) */ +#undef USE_LDAP + /* Define to 1 to build with memcached support */ #undef USE_MEMCACHED diff --git a/src/pgpool.spec b/src/pgpool.spec index cd4742dcd..925eca82b 100644 --- a/src/pgpool.spec +++ b/src/pgpool.spec @@ -103,6 +103,7 @@ Postgresql extensions libraries and sql files for pgpool-II. --disable-static \ --with-pam \ --with-openssl \ + --with-ldap \ --with-memcached=%{_usr} \ --disable-rpath \ --sysconfdir=%{_sysconfdir}/%{short_name}/ [parent: 8b472278ecea]