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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: PostgreSQL 如何配置AD域认证或LDAP认证,请参考:http://blog.163.com/digoal@126/blog/static/16387704020145914717111/http://blog.163.com/digoal@126/blog/static/1638770.

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  
......  
相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
9天前
|
关系型数据库 分布式数据库 数据库
成都晨云信息技术完成阿里云PolarDB数据库产品生态集成认证
近日,成都晨云信息技术有限责任公司(以下简称晨云信息)与阿里云PolarDB PostgreSQL版数据库产品展开产品集成认证。测试结果表明,晨云信息旗下晨云-站群管理系统(V1.0)与阿里云以下产品:开源云原生数据库PolarDB PostgreSQL版(V11),完全满足产品兼容认证要求,兼容性良好,系统运行稳定。
|
7月前
|
存储 关系型数据库 数据库
用Patroni配置PostgreSQL高可用集群
Patroni是Zalando开发的数据库高可用管理软件,用于编排和自动化PostgreSQL集群的管理过程。Patroni 需要一系列其他组件的支持,通过利用第三方分布式一致性软件,组建并实现数据库高可用方案。
646 4
用Patroni配置PostgreSQL高可用集群
|
7月前
|
存储 负载均衡 关系型数据库
PolarDB 高可用
PolarDB 高可用
43 0
|
9天前
|
Cloud Native 关系型数据库 分布式数据库
数据库性能诊断工具DBdoctor通过阿里云PolarDB产品生态集成认证
DBdoctor(V3.1.0)成功通过阿里云PolarDB分布式版(V2.3)集成认证,展现优秀兼容性和稳定性。此工具是聚好看科技的内核级数据库性能诊断产品,运用eBPF技术诊断SQL执行,提供智能巡检、根因分析和优化建议。最新版V3.1.1增加了对PolarDB-X和OceanBase的支持,以及基于cost的索引诊断功能。PolarDB-X是阿里巴巴的高性能云原生分布式数据库,兼容MySQL生态。用户可通过提供的下载地址、在线试用链接和部署指南体验DBdoctor。
166 0
|
9天前
|
负载均衡 监控 关系型数据库
PostgreSQL从小白到高手教程 - 第48讲:PG高可用实现keepalived
PostgreSQL技术大讲堂 - 第48讲:PG高可用实现keepalived
80 1
|
7月前
|
监控 关系型数据库 Go
《打造高可用PostgreSQL:策略与工具》
《打造高可用PostgreSQL:策略与工具》
103 0
|
9天前
|
关系型数据库 网络安全 数据安全/隐私保护
你会开启Postgresql 的SSL单向认证 配置?
你会开启Postgresql 的SSL单向认证 配置?
你会开启Postgresql 的SSL单向认证 配置?
|
9天前
|
关系型数据库
电子好书发您分享《阿里云认证的解析与实战-关系型数据库ACP认证》
电子好书发您分享《阿里云认证的解析与实战-关系型数据库ACP认证》
51 1
|
5月前
|
关系型数据库 数据安全/隐私保护 PostgreSQL
基于Docker快速搭建 PostgreSQL 高可用方案
基于Docker快速搭建 PostgreSQL 高可用方案
371 0
|
7月前
|
Kubernetes 关系型数据库 分布式数据库
kubeblocks完成阿里云PolarDB数据库产品生态集成认证
近日,杭州云猿生数据有限公司(以下简称云猿生)与阿里云PolarDB 开源数据库社区展开产品集成认证。测试结果表明,杭州云猿生数据有限公司旗下kubeblocks(V0.7.0)与阿里云以下产品:开源云原生数据库PolarDB 分布式版( V2.0 ),完全满足产品兼容认证要求,兼容性良好,系统运行稳定。

相关产品

  • 云原生数据库 PolarDB