PostgreSQL 如何提升LDAP或AD域认证的 高可用

PostgreSQL 如何配置AD域认证或LDAP认证,请参考:
http://blog.163.com/digoal@126/blog/static/16387704020145914717111/
http://blog.163.com/digoal@126/blog/static/1638770402014563264469/

引入LDAP,AD认证,可能会增加故障点,当认证服务器出现故障时,认证将失败。
本文主要介绍一下PostgreSQL是如何解决这个问题的,以及部分代码的分析。

当用户选择了使用AD域或者LDAP进行认证时,可以选择使用单机或多机的配置,多机主要是为了防止LDAP,AD认证服务器的单点故障。
单机模式

# simple bind :   
host all new 0.0.0.0/0 ldap ldapserver=172.16.3.150 ldapport=389 ldapprefix="uid=" ldapsuffix=",ou=People,dc=my-domain,dc=com"  
# search bind :   
host all new 0.0.0.0/0 ldap ldapserver=172.16.3.150 ldapport=389 ldapsearchattribute="uid" ldapbasedn="ou=People,dc=my-domain,dc=com"  

多机模式,使用空格隔开,可以在ldapserver中设置,覆盖ldapport中的设置。

# simple bind :   
host all new 0.0.0.0/0 ldap ldapserver="172.16.3.150 172.16.3.151:388 10.1.1.1" ldapport=389 ldapprefix="uid=" ldapsuffix=",ou=People,dc=my-domain,dc=com"  
# search bind :   
host all new 0.0.0.0/0 ldap ldapserver="172.16.3.150 172.16.3.151:388 10.1.1.1" ldapport=389 ldapsearchattribute="uid" ldapbasedn="ou=People,dc=my-domain,dc=com"  

防止LDAP,AD认证服务器的单点故障还有一种解法,使用域名。但是也有一些注意事项,如下:
在域名服务器中为一个域名配置多台主机地址,这是非常惯用的手法,但是这种方法也有一定的问题。
例如某个企业在全国各地的IDC机房都有对应的AD服务器,使用域名的方式,如果将这些AD服务器的IP都指给一个域名,在DNS响应gethostbyname请求时,一般是以轮询的方式返回列表。
例如:
某次请求返回

IP_A, IP_B, IP_C  

当本地的DNS cache TTL超时后,接下来的请求可能返回

IP_B, IP_C, IP_A  

客户端在拿到这些地址信息后,通常取的是第一个IP hostent->h_addr_list[0] 作为解析出来的IP拿来使用。
那么就存在一个问题,在进行AD域认证时,可能有时候取到的是本IDC的AD域服务器,有时候取到的是其他IDC的AD域服务器。
怎么让DNS返回的就是本地IDC的AD域服务器呢?
常用的手法是使用智能DNS,根据来源IP,返回地址。

gethostbyname代码:

NAME  
       gethostbyname, gethostbyaddr, sethostent, gethostent, endhostent, h_errno, herror, hstrerror, gethostbyaddr_r, gethostbyname2, gethostbyname2_r, gethostbyname_r, gethostent_r - get network host entry  
  
SYNOPSIS  
       #include <netdb.h>  
       extern int h_errno;  
  
       struct hostent *gethostbyname(const char *name);  
......  
  
       The hostent structure is defined in <netdb.h> as follows:  
  
           struct hostent {  
               char  *h_name;            /* official name of host */  
               char **h_aliases;         /* alias list */  
               int    h_addrtype;        /* host address type */  
               int    h_length;          /* length of address */  
               char **h_addr_list;       /* list of addresses */  
           }  
           #define h_addr h_addr_list[0] /* for backward compatibility */  
  
       The members of the hostent structure are:  
  
       h_name The official name of the host.  
  
       h_aliases  
              An array of alternative names for the host, terminated by a NULL pointer.  
  
       h_addrtype  
              The type of address; always AF_INET or AF_INET6 at present.  
  
       h_length  
              The length of the address in bytes.  
  
       h_addr_list  
              An array of pointers to network addresses for the host (in network byte order), terminated by a NULL pointer.  
  
       h_addr The first address in h_addr_list for backward compatibility.  

src/backend/libpq/auth.c

/*  
 * Initialize a connection to the LDAP server, including setting up  
 * TLS if requested.  
 */  
static int  
InitializeLDAPConnection(Port *port, LDAP **ldap)  
{  
        int                     ldapversion = LDAP_VERSION3;  
        int                     r;  
  
        *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);  
        if (!*ldap)  
        {  
#ifndef WIN32  
                ereport(LOG,  
                                (errmsg("could not initialize LDAP: %m")));  
#else  
                ereport(LOG,  
                                (errmsg("could not initialize LDAP: error code %d",  
                                                (int) LdapGetLastError())));  

man ldap_init

NAME  
       ldap_init, ldap_initialize, ldap_open - Initialize the LDAP library and open a connection to an LDAP server  
  
SYNOPSIS  
       #include <ldap.h>  
  
       LDAP *ldap_open(host, port)  
       char *host;  
       int port;  
  
       LDAP *ldap_init(host, port)  
       char *host;  
       int port;  
  
DESCRIPTION  
       ldap_open() opens a connection to an LDAP server and allocates an LDAP structure which is used to identify the connection and to maintain per-connection information.    
       ldap_init() allocates an LDAP structure but does not open an initial connection.    
       ldap_initialize() allocates an LDAP structure but does not open an initial connection.    
       ldap_init_fd() allocates an LDAP structure using an existing  connection on the provided socket.    
       One of these routines must be called before any operations are attempted.  
  
       ldap_open()  takes  host, the hostname on which the LDAP server is running, and port, the port number to which to connect.    
       If the default IANA-assigned port of 389 is desired, LDAP_PORT should be specified for port.    
  
       The host parameter may contain a blank-separated list of hosts to try to connect to, and each host may optionally by of the form host:port.    
       If present, the :port overrides the port parameter  to ldap_open().     
  
       Upon  successfully  making a connection to an LDAP server, ldap_open() returns a pointer to an opaque LDAP structure, which should be passed to subsequent calls to ldap_bind(), ldap_search(),  
       etc.   
  
       Certain fields in the LDAP structure can be set to indicate size limit, time limit, and how aliases are handled during operations;    
       read  and  write  access  to  those  fields  must  occur  by  calling ldap_get_option(3) and ldap_set_option(3) respectively, whenever possible.  
  
       ldap_init() acts just like ldap_open(), but does not open a connection to the LDAP server.  The actual connection open will occur when the first operation is attempted.  

感兴趣的童鞋可以下载openldap的源码看看。
yum install -y openldap-debuginfo

PostgreSQL 的ldap server配置说明,指定多台主机时,空格隔开即可,与ldap_init介绍一致。
http://www.postgresql.org/docs/9.5/static/auth-methods.html#AUTH-LDAP

ldapserver  
    Names or IP addresses of LDAP servers to connect to. Multiple servers may be specified, separated by spaces.  
  
ldapport  
    Port number on LDAP server to connect to. If no port is specified, the LDAP library's default port setting will be used.  

PostgreSQL 使用HbaLine存储pg_hba.conf中的数据结构。与LDAP认证相关的ldapserver和ldapport都在其中。
src/include/libpq/hba.h

typedef struct HbaLine  
{  
    int            linenumber;  
    char       *rawline;  
    ConnType    conntype;  
    List       *databases;  
    List       *roles;  
    struct sockaddr_storage addr;  
    struct sockaddr_storage mask;  
    IPCompareMethod ip_cmp_method;  
    char       *hostname;  
    UserAuth    auth_method;  
  
    char       *usermap;  
    char       *pamservice;  
    bool        ldaptls;  
    char       *ldapserver;  
    int            ldapport;  
    char       *ldapbinddn;  
    char       *ldapbindpasswd;  
    char       *ldapsearchattribute;  
    char       *ldapbasedn;  
    int            ldapscope;  
    char       *ldapprefix;  
    char       *ldapsuffix;  
    bool        clientcert;  
    char       *krb_realm;  
    bool        include_realm;  
    char       *radiusserver;  
    char       *radiussecret;  
    char       *radiusidentifier;  
    int            radiusport;  
}  

语义解析,判断是否使用LDAP认证的部分:

/*  
 * Parse one tokenised line from the hba config file and store the result in a  
 * HbaLine structure, or NULL if parsing fails.  
 *  
 * The tokenised line is a List of fields, each field being a List of  
 * HbaTokens.  
 *  
 * Note: this function leaks memory when an error occurs.  Caller is expected  
 * to have set a memory context that will be reset if this function returns  
 * NULL.  
 */  
static HbaLine *  
parse_hba_line(List *line, int line_num, char *raw_line)  
{  
......  
#endif  
        else if (strcmp(token->string, "ldap") == 0)  
#ifdef USE_LDAP  
                parsedline->auth_method = uaLDAP;  
#else  
                unsupauth = "ldap";  
#endif  
......  
        /*  
         * Check if the selected authentication method has any mandatory arguments  
         * that are not set.  
         */  
        if (parsedline->auth_method == uaLDAP)  
        {  
                MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");  
  
                /*  
                 * 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 ldapsearchattribute.  
                 * Disallow mixing these parameters.  
                 */  
                if (parsedline->ldapprefix || parsedline->ldapsuffix)  
                {  
                        if (parsedline->ldapbasedn ||  
                                parsedline->ldapbinddn ||  
                                parsedline->ldapbindpasswd ||  
                                parsedline->ldapsearchattribute)  
                        {  
                                ereport(LOG,  
                                                (errcode(ERRCODE_CONFIG_FILE_ERROR),  
                                                 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix"),  
                                                 errcontext("line %d of configuration file \"%s\"",  
                                                                        line_num, HbaFileName)));  
                                return NULL;  
                        }  
                }  
                else if (!parsedline->ldapbasedn)  
                {  
                        ereport(LOG,  
                                        (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)));  
                        return NULL;  
                }  
        }  
  
......  

LDAP认证方法配置的option的语义解析部分:

/*  
 * Parse one name-value pair as an authentication option into the given  
 * HbaLine.  Return true if we successfully parse the option, false if we  
 * encounter an error.  
 */  
static bool  
parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)  
{  
......  
        else if (strcmp(name, "ldapurl") == 0)  
        {  
#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(LOG,  
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),  
                                         errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc))));  
                        return false;  
                }  
  
                if (strcmp(urldata->lud_scheme, "ldap") != 0)  
                {  
                        ereport(LOG,  
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),  
                        errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme)));  
                        ldap_free_urldesc(urldata);  
                        return false;  
                }  
  
                hbaline->ldapserver = pstrdup(urldata->lud_host);  
                hbaline->ldapport = urldata->lud_port;  
                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)  
                {  
                        ereport(LOG,  
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),  
                                         errmsg("filters not supported in LDAP URLs")));  
                        ldap_free_urldesc(urldata);  
                        return false;  
                }  
                ldap_free_urldesc(urldata);  
#else                                                   /* not OpenLDAP */  
                ereport(LOG,  
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),  
                                 errmsg("LDAP URLs not supported on this platform")));  
#endif   /* not OpenLDAP */  
        }  
        else if (strcmp(name, "ldaptls") == 0)  
        {  
                REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");  
                if (strcmp(val, "1") == 0)  
                        hbaline->ldaptls = true;  
                else  
......  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值