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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 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数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
存储 关系型数据库 数据库
用Patroni配置PostgreSQL高可用集群
Patroni是Zalando开发的数据库高可用管理软件,用于编排和自动化PostgreSQL集群的管理过程。Patroni 需要一系列其他组件的支持,通过利用第三方分布式一致性软件,组建并实现数据库高可用方案。
用Patroni配置PostgreSQL高可用集群
|
5月前
|
关系型数据库 MySQL 数据安全/隐私保护
windows mysql8 安装后 提示密码不对,修改下密码认证方式就可以了
windows mysql8 安装后 提示密码不对,修改下密码认证方式就可以了
1197 3
|
2月前
|
存储 关系型数据库 MySQL
基于案例分析 MySQL 权限认证中的具体优先原则
【10月更文挑战第26天】本文通过具体案例分析了MySQL权限认证中的优先原则,包括全局权限、数据库级别权限和表级别权限的设置与优先级。全局权限优先于数据库级别权限,后者又优先于表级别权限。在权限冲突时,更严格的权限将被优先执行,确保数据库的安全性与资源合理分配。
|
4月前
|
运维 监控 关系型数据库
【一文搞懂PGSQL】7. PostgreSQL + repmgr + witness 高可用架构
该文档介绍了如何构建基于PostgreSQL的高可用架构,利用repmgr进行集群管理和故障转移,并引入witness节点增强网络故障检测能力。repmgr是一款轻量级的开源工具,支持一键部署、自动故障转移及分布式节点管理。文档详细描述了环境搭建步骤,包括配置postgresql参数、安装与配置repmgr、注册集群节点以及配置witness节点等。此外,还提供了故障手动与自动切换的方法及常用命令,确保集群稳定运行。
|
监控 关系型数据库 Go
《打造高可用PostgreSQL:策略与工具》
《打造高可用PostgreSQL:策略与工具》
271 0
|
7月前
|
负载均衡 监控 关系型数据库
PostgreSQL从小白到高手教程 - 第48讲:PG高可用实现keepalived
PostgreSQL技术大讲堂 - 第48讲:PG高可用实现keepalived
247 1
|
7月前
|
关系型数据库 网络安全 数据安全/隐私保护
你会开启Postgresql 的SSL单向认证 配置?
你会开启Postgresql 的SSL单向认证 配置?
363 0
你会开启Postgresql 的SSL单向认证 配置?
|
关系型数据库 数据安全/隐私保护 PostgreSQL
基于Docker快速搭建 PostgreSQL 高可用方案
基于Docker快速搭建 PostgreSQL 高可用方案
770 0
|
存储 关系型数据库 数据库
PostgreSQL复制原理及高可用集群
文章来自: 朱贤文 | 成都文武信息技术有限公司 分析
324 1
|
存储 SQL Cloud Native
《阿里云认证的解析与实战-数据仓库ACP认证》——云原生数据仓库AnalyticDB MySQL版解析与实践(上)——一、产品概述
《阿里云认证的解析与实战-数据仓库ACP认证》——云原生数据仓库AnalyticDB MySQL版解析与实践(上)——一、产品概述

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版