使用pgsql客户端连接postgresql服务器
2021-03-20 14:27:15 阿炯

psql客户端程序可以在命令行中被调用,在调用时可以定义相关参数,参数可以通过命令行指定,也可以使用参数文件来指定。网络的Socket不在此处讲解,本文重点讲解通过本地Socket(Unix域套接字)的相关连接方式方法。

Socket这种连接方式,在PostgreSQL有一种对该连接方式的别称:local(peer)认证方式,即这种认证方式是通过Unix Domain Socket连接方式认证的。

Unix Domain Socket 与 TCP/UDP Socket

看到Socket很多人可能会联想到网络编程常见的TCP/UDP Socket。本质上他们的作用都一样,都是为了网络通信而生。Unix Domain Socket是在此基础之上发展的IPC机制:在本机两个进程之间直接建立通信方式,不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,所以性能上和效率上比TCP/UDP Socket高一些,并且Unix Domain Socket是一种可靠的通信机制,而network socket是不可靠的通信,可能发生丢包、乱序等现象。

Unix Domain Socket的表现形式


是以文件形式存放在本机上,且通常不占用容量。以大多数运维人员非常熟悉的MySQL服务为例,默认配置下MySQL服务运行时会创建一个这样的文件:
# ll /var/lib/mysql/mysql.sock
srwxrwxrwx 1 mysql mysq 12月 28 21:30 /var/lib/mysql/mysql.sock

注意:第一个file类型标记是s,即socket。

# file /var/lib/mysql/mysql.sock
/var/lib/mysql/mysql.sock : socket

不同的设置下这个文件位置或文件名略有不同,具体位置可参考MySQL的配置文件中的socket配置项。

socket类似于命名管道(named pipe),且作用也相近。但是命名管道只能支持字节流,而Unix Domain Socket既能支持字节流,又能支持队列。另外Socket使用系统文件的地址来作为自己的身份,所以可以用于权限认证(PostgreSQL的Peer认证),它可以被系统进程引用。所以可以支持不同的进程同时使用,而命名管道是没有这种功能的。

Unix Domain Socket的使用

很多运行于Linux/Unix上的服务不止可以监听一个TCP/UDP端口,还支持监听一个Unix Domain Socket。如mysql的socket配置,redis的socket配置,nginx的listen配置,postgresql的unix_socket_directories配置等等。只要客户端支持Unix Domain Socket,就可以直接使用这种方式连接。

如MySQL的[client]配置段下面socket配置就直接指向unix domain socket地址,代表默认使用unix domain socket连接。psql默认连接方式也是如此,默认会在/var/run/postgresql/目录下查找.s.PGSQL.5432这个socket,可以通过psql -h path_of_socket_directory -p port连接其它目录下的socket文件,从而连接到非标准的5432端口的服务实例。另外不得不说一下,大多数使用C/C++语言开发的client也都支持Unix Domain Socket。

unix_socket_directories parameter in postgresql.conf (/tmp is still needed not to break psql and other programs):
unix_socket_directories = '/tmp,/var/pgsql'

PostgreSQL Unix domain socket (/tmp/.s.PGSQL.5432).

Configuration:

pg_hba.conf:
# TYPE   DATABASE  USER      ADDRESS             METHOD  OPTION
local    all       all                           peer    map=default

pg_ident.conf:
# MAPNAME       SYSTEM-USERNAME         PG-USERNAME
default         postgres                postgres
default         freeoa                  project_great

postgresql.conf:
...
port = 5432
unix_socket_directories = '/var/run/postgresql'
...

Here are the permissions in my socket directory:

$ ll /var/run/postgresql/
-rw-r--r--  1 postgres postgres   5 May 17 00:20 9.3-main.pid
srwxrwxrwx  1 postgres postgres   0 May 17 00:20 .s.PGSQL.5432
-rw-------  1 postgres postgres  70 May 17 00:20 .s.PGSQL.5432.lock

The PostgreSQL user project_great has been granted all privileges on the database project_great and both the user and the database exist.

这里并没有使用用户个人的配置文件:~/.pgpass。

机器上有两个postgres的实例时:
root@freeoa:~# ls -la /tmp/
total 20
...
srwxrwxrwx  1 postgres daemon    0 3月  19 21:43 .s.PGSQL.5432
-rw-------  1 postgres daemon   49 3月  19 21:43 .s.PGSQL.5432.lock
srwxrwxrwx  1 postgres daemon    0 3月  19 22:06 .s.PGSQL.5433
-rw-------  1 postgres daemon   40 3月  19 22:06 .s.PGSQL.5433.lock


Unix Domain Socket的限制

Unix域套接字的性能很高,安全性好,在network socket端口有限的情况下,Unix Domain Socket无需占用有限的TCP/UDP端口。但很明显的可以看到两个限制:

1)、只能本机访问,不能用于远程访问(Docker可以利用挂载的形式实现本机不同容器或容器与宿主机之前的访问,但还是在同一台主机上)

2)、只在POSIX兼容的系统有实现,意味着Windows下没有对应的实现

像Java这种编程语言为了简化跨平台的兼容性问题,底层也不提供Unix Domain Socket的支持。比如JDBC就没有Unix Domain Socket的连接实现,所以jdbc url在连接本机服务的时候,也只能写127.0.0.1,不能写Unix Domain Socket路径。


PostgreSQL的local连接与peer认证

说清楚了Unix Domain Socket之后,PostgreSQL的peer认证(对等认证)就很容易理解了。有关postgresql完整的认证方式,参见官方文档:
http://www.postgres.cn/docs/12/client-authentication.html
https://www.postgresql.org/docs/current/auth-methods.html

我们先看看默认安装下的postgresql的pg_hba.conf文件(已过滤空行和注释行):
# TYPE   DATABASE  USER      ADDRESS             METHOD  OPTION
local   all             postgres                                peer
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5
local   replication     all                                     peer
host    replication     all             127.0.0.1/32            md5
host    replication     all             ::1/128                 md5

其中host代表client的访问方式是TCP,而local就代表client的访问方式是Unix Domain Socket。所以可以很明显的看到local行的配置是没有ip配置段的。

那么peer认证方式是怎么一回事呢?之前提到过,Unix Domain Socket是可以取到用户身份信息的,这个在Unix/Linux系统中就是用户账号,而peer则代表着用户执行psql命令以user身份运行,连接到数据库上对应的用户身份就是user这种方式。所以peer认证只支持local连接方式,因此在windows上不起作用。如果以myuser这个账号登录到Linux系统中,运行psql命令,那么连接到数据库上就是myuser这个用户,不需要密码认证。如果postgresql数据库中没有myuser这个用户,就会报错找不到这个用户;如果设定为trust,就会用postgres这个用户来直接登录,这会在本地使用上可能会导致一些不安全因素。

再看看postgres这个用户默认配置下限定local连接方式,认证方式是peer。代表postgres(默认的postgresql的管理员账户)必须以Unix Domain Socket方式连接,并且使用操作系统中的postgres用户身份运行。由于这个账户默认在Linux系统中是不允许直接登录的,所以我们需要su或sudo命令切换用户身份运行:
su - postgres
sudo -u postgres

由于psql默认就是以Unix Domain Socket方式连接(Windows系统则以TCP方式),所以无需指定其他连接参数。再比如在postgresql上创建了一个用户foaprx,但是我们的操作系统中没有foaprx这个账户,那么此时我们可以host或local的方式连接:
$ psql -U foaprx -h 127.0.0.1 -p 5433 freadb
psql (9.6.10, 服务器 9.6.5)
freadb=> \conninfo
以用户 "foaprx" 的身份, 在主机"127.0.0.1", 端口"5433"连接到数据库 "freadb

$ psql -U foaprw pdb
psql (9.6.10)
pdb=# \conninfo
以用户 "foaprw" 的身份,通过套接字"/tmp"在端口"5432"连接到数据库 "pdb"

psql -h /tmp/ -p 5433 -U foaprx freadb
freadb=> \conninfo
以用户 "foaprx" 的身份,通过套接字"/tmp/"在端口"5433"连接到数据库 "freadb"

直接指定-h参数为127.0.0.1或localhost,限定psql使用TCP方式连接postgresql,则可以命中配置文件中的'host all all 127.0.0.1/32 trust'这个配置,于是连接到数据库中。在对本地的连接默认是非常信任的,直接连接即可。

$ psql -h /tmp/ -p 5433 -U foaprx freadb
psql: 致命错误:  没有用于主机 "[local]", 用户 "foaprx", 数据库 "freadb", SSL 关闭 的 pg_hba.conf 记录

这是指定local的方法为peer的情况下所报出的错误,而设定为trust的连接结果正常的情况在上面有示例。


psql使用unix domain socket连接本机其他postgresql实例的方案:
psql -h path_of_socket_dir -p other_port

$ psql
psql (9.6.10)
postgres@[local]@postgres# \conninfo
以用户 "postgres" 的身份,通过套接字"/tmp"在端口"5432"连接到数据库 "postgres"

$ psql -h /tmp/ -p 5433
psql (9.6.10, 服务器 9.6.5)
postgres@[local:/tmp/]@postgres# \conninfo
以用户 "postgres" 的身份,通过套接字"/tmp/"在端口"5433"连接到数据库 "postgres"


那如果操作系统与数据库系统在用户名没有能对等使用呢?这里就可以使用pg_ident来开启的用户映射,并在对等认证上来验证一下。

注意:这可能需要在postgresql.conf的配置文件打开ident相关选项。

pg_ident.conf:
# MAPNAME    SYSTEM-USERNAME    PG-USERNAME
pgmapx    freeoa    foapro


pg_ident.conf:
local   all    all    peer    map=pgmapx

添加加完成后,保存并重启pg服务器,在Linux中进行测试:

freeoa@wklin:~$ psql -h /tmp/ -p 5433 freadb
psql:致命错误:对用户"postgres"的对等认证失败

freeoa@wklin:~$ psql -h /tmp/ -p 5433 -U foaprw freadb
psql:致命错误:对用户"foaprw"的对等认证失败

freeoa@wklin:~$ psql -h /tmp/ -p 5433 -U foapro freadb
psql (9.6.10, 服务器 9.6.5)
freadb=> \conninfo
以用户 "foapro" 的身份,通过套接字"/tmp/"在端口"5433"连接到数据库 "freadb"

相关的日志记录如下:
日志:  对于以"freeoa"身份认证为的用户"foaprx",在用户映射"pgmapx"中没有匹配
致命错误:对用户"foaprx"的对等认证失败
详细信息:与Connection相匹配的文件行位于 pg_hba.conf 85: "local   all    all    peer    map=pgmapx"
日志:  所提供的用户名(postgres)和被认证的用户名(freeoa)不匹配


修改数据库的用户密码

首先要知道对于大多数Unix发行版,默认Postgres用户既不需要也不使用密码进行身份验证;根据PostgreSQL最初的安装方式和使用的版本,默认的身份验证方法将是ident或peer(pg_hba.conf)。

ident身份验证使用在TCP端口113上运行的操作系统的标识服务器来验证用户的凭据。另一方面,对等身份验证用于本地连接,并验证操作系统的登录用户名是否与Postgres数据库的用户名匹配。

对于大多数系统,默认的PostgreSQL用户是postgres,身份验证不需要密码。因此要添加密码就必须首先以postgres用户身份登录和连接。如果成功连接并正在查看psql提示,如果收到一个错误,说明数据库postgres不存在,请尝试连接到template1数据库,如果成功,继续更改密码。

pg_hba.conf文件是身份验证规则的列表,大致内容有如下:
# TYPE  DATABASE    USER    ADDRESS    METHOD
# "local" is for Unix domain socket connections only
local all postgres peer

# IPv4 local connections:
host    all    all    127.0.0.1/32    trust
host    all    all    10.206.1.0/24    password

这个身份验证规则只是告诉Postgres,对于为用户postgres建立的到所有数据库的本地连接,使用对等协议进行身份验证。

注意:一些旧版本的Postgres更喜欢默认的身份验证方法ident,但是大多数现代安装将使用上面指定的peer。如果结果不同则可能需要测试两者。

现在更新配置文件后,重复登录并以默认用户身份连接部分中的步骤,尝试以默认postgres用户身份连接到,成功后来更改密码。

postgres=# ALTER USER postgres PASSWORD 'myPassword';
# ALTER USER 'username' WITH PASSWORD 'newPassword';
# ALTER USER username WITH ENCRYPTED PASSWORD 'password';

ALTER USER is an alias for ALTER ROLE

当然最方便最安全的方式在是终端交互界面下输入
\password
\password username
sudo -u postgres psql -c "\password"

要输入两次,完成后用\q退出后验证是否修改成功。


解决本地用户登录的安全问题

安装完成后Pg服务器后,默认任何用户默认都可以调用psql指令时以postgres的身份登录数据库服务器(不考虑上面的对等认证):
freeoa@wklin:~$ psql
postgres=#

[hto@wklin:~]$ psql
postgres@[local]@postgres# \conninfo
以用户 "postgres" 的身份,通过套接字"/tmp"在端口"5432"连接到数据库 "postgres"

为postgres用户设置密码后,退出后重新登录时依然不会有提示密码输入。是因为没有修改pg_hba.conf中的访问来源设定,如下的地方都要被注释或设定密码为非'trust':
# "local" is for Unix domain socket connections only
#local   all    all    trust
# IPv4 local connections:
#host    all        all    127.0.0.1/32    trust

注释后的普通用户访问时报:
[hto@wklin:~]$ psql
psql: 致命错误:  没有用于主机 "[local]", 用户 "postgres", 数据库 "postgres", SSL 关闭 的 pg_hba.conf 记录

上面对local这行的注释基本上就禁止了通过本地socket连接服务器的可能。是否有一种只允许postgres通过Unix domain socket无密码登录服务器?
有,可以通过设定其socket的权限来实现,默认情况的socket在/tmp目录下,带有隐藏属性:
srwxrwxrwx  1 postgres daemon     0 5月  18 18:28 .s.PGSQL.5432

在postgresql.conf中,修改此行(假设用户组没有争议)
#unix_socket_permissions = 0740

同时开启(pg_hba.conf)
# "local" is for Unix domain socket connections only
local   all    all    trust

重启测试如下:
[hto@wklin:~]$ psql -h /tmp -p 5432
psql: 无法联接到服务器: Permission denied
 服务器是否在本地运行并且在 Unix 域套接字
 "/tmp/.s.PGSQL.5432"上准备接受联接?

而postgres用户却能正常登录,没有报拒绝连接的错误。如果还不放心本地的连接,可将pg_hba.conf中的local行的用户列指定修改为postgres。

通过IP地址登录的情况:
[hto@wklin:~]$ psql -h172.18.0.8
口令:
postgres@172.18.0.8@postgres# \conninfo
以用户 "postgres" 的身份, 在主机"172.18.0.8", 端口"5432"连接到数据库 "postgres"

用户要知道postgres的密码才能连接,安全性高了许多。
[hto@wklin:~]$ psql -h127.0.0.1
口令:
postgres@127.0.0.1@postgres# \conninfo
以用户 "postgres" 的身份, 在主机"127.0.0.1", 端口"5432"连接到数据库 "postgres"

小结

默认情况下几乎所有的本地用户都可以以postgres的用户身份连接到postgres库,且为最高权限。这个虽然没有对网络开启,但也存存相当的未授权访问问题,在一些安全检测的情况下是不可能通过的,因此对其的安全加固是不可少的。