postgresql配置session_exec 达到锁定登录失败用户目的

1 说明

由于公司的某SaaS平台要进行等保3.0评测,与之对应的postgresql数据库也要进行相关的安全设置,对其配置锁定用户是必要操作之一。
如下步骤是在postgresql12.8版本上配置session_exec的过程,如下步骤在12.8版本可以完美运行,无坑。

本文涉及的服务和服务器的基本信息

维度说明
系统版本Centos 7.9
postgresql版本12.8
postgresql安装方式源码安装
postgresql运行用户postgres

为了避免权限错乱导致服务异常,安装后的操作基本上都是在postgres用户下操作的。

2 配置session_exec

2.1 安装postgresql

如下是我的安装步骤 可以参考 命令简单粘贴一下

yum -y install gcc gcc-c++ readline-devel zlib-devel libuuid-devel uuid uuid-devel openssl openssl-devel
useradd postgres
mv postgresql-12.8.tar.gz /home/postgres
mkdir /data/app/postgresql /data/appData/postgresql /data/logs/postgresql/postgresql -p
chown -R postgres:postgres /data/app/postgresql /data/appData/postgresql /data/logs/postgresql /home/postgres
su  - postgres
tar -xf postgresql-12.8.tar.gz
cd postgresql-12.8/
./configure --prefix=/data/app/postgresql --with-ossp-uuid --with-openssl --with-zlib
make -j4 && make install
echo -e "export PGHOME=/data/app/postgresql\nexport PATH=\$PATH:\$HOME/.local/bin:\$HOME/bin:\$PGHOME/bin" >> ~/.bashrc
source ~/.bashrc
initdb -D /data/appData/postgresql/

2.2 安装file_fdw扩展

[postgres@localhost postgresql-12.8]$ ls
aclocal.m4  config.log     configure     contrib    doc          GNUmakefile.in  INSTALL   README
config      config.status  configure.in  COPYRIGHT  GNUmakefile  HISTORY         Makefile  src
[postgres@localhost postgresql-12.8]$ cd contrib/
[postgres@localhost contrib]$ file_fdw/
[postgres@localhost file_fdw]$ make
[postgres@localhost file_fdw]$ make install

2.3 安装session_exec

这个需要去github下载然后 安装

https://github.com/okbob/session_exec 点击Code 点击Download ZIP

unzip session_exec-master.zip
cd session_exec-master/
make pg_config=/data/app/postgresql/bin/pg_config
make pg_config=/data/app/postgresql/bin/pg_config install

2.4 修改配置文件 添加session_exec

logging_collector = on # 有了就不用再添加了
log_destination = 'csvlog' # 有了就不用再添加了
session_preload_libraries='session_exec'
session_exec.login_name='login'

重启 让配置生效

session_preload_libraries参数:一个或者多个要在连接开始时预载入的共享库。
shared_preload_libraries参数:一个或者多个要在服务器启动时预载入的共享库。

2.5 创建外部表postgres_log

create extension file_fdw;

CREATE SERVER pglog FOREIGN DATA WRAPPER file_fdw;

CREATE FOREIGN TABLE public.postgres_log(
log_time timestamp(3) with time zone,
  user_name text,
  database_name text,
  process_id integer,
  connection_from text,
  session_id text,
  session_line_num bigint,
  command_tag text,
  session_start_time timestamp with time zone,
  virtual_transaction_id text,
  transaction_id bigint,
  error_severity text,
  sql_state_code text,
  message text,
  detail text,
  hint text,
  internal_query text,
  internal_query_pos integer,
  context text,
  query text,
  query_pos integer,
  location text,
  application_name text
) SERVER pglog
OPTIONS ( program 'find /data/logs/postgresql -type f -name "*.csv" -mtime -1 -exec cat {} \;', format 'csv' );

grant SELECT on postgres_log to PUBLIC;

2.6 创建登录验证函数login

如下设置的是5次 5次登录失败就锁定用户 具体的次数可以根据需要进行设置

create or replace function public.login() returns void as $$
declare
res record;
failed_login_times int = 5;
failed_login int = 0;
begin
--获取数据库中所有可连接数据库的用户
for res in select rolname from pg_catalog.pg_roles where rolcanlogin= 't' and rolname !='postgres'
loop
  raise notice 'user: %!',res.rolname;
  --获取当前用户最近连续登录失败次数
  select count(*) from (select log_time,user_name,error_severity,message,detail from public.postgres_log where command_tag = 'authentication' and user_name = res.rolname and (detail is null or detail not like 'Role % does not exist.%') order by log_time desc limit failed_login_times) A WHERE A.error_severity='FATAL' into  failed_login ;
  raise notice 'failed_login_times: %! failed_login: %!',failed_login_times,failed_login;
  --用户最近密码输入错误次数达到5次或以上
  if failed_login >= failed_login_times then
    --锁定用户
    EXECUTE format('alter user %I nologin',res.rolname);
    raise notice 'Account % is locked!',res.rolname;
  end if;
end loop;
end;
$$ language plpgsql strict security definer set search_path to 'public';

2.7 验证登录失败效果

修改pg_hba.conf 添加对测试用户的密码策略 然后重启pg

host    all             test1             127.0.0.1/32          md5

创建测试用户

create user test1 encrypted password 'Rootmaster@777';

前5次输入错误密码 如下

[postgres@localhost ~]$ psql -Utest1 -h127.0.0.1 -p18126 -d postgres
Password for user test1:
psql: error: FATAL:  password authentication failed for user "test1"
[postgres@localhost ~]$ psql -Utest1 -h127.0.0.1 -p18126 -d postgres
Password for user test1:
psql: error: FATAL:  password authentication failed for user "test1"
[postgres@localhost ~]$ psql -Utest1 -h127.0.0.1 -p18126 -d postgres
Password for user test1:
psql: error: FATAL:  password authentication failed for user "test1"
[postgres@localhost ~]$ psql -Utest1 -h127.0.0.1 -p18126 -d postgres
Password for user test1:
psql: error: FATAL:  password authentication failed for user "test1"
[postgres@localhost ~]$ psql -Utest1 -h127.0.0.1 -p18126 -d postgres
Password for user test1:

查询test1锁定状态 如下 可以发现已经不可登录

[postgres@localhost session_exec-master]$ psql -Upostgres -h127.0.0.1 -p18126 -d postgres
NOTICE:  user: test1!
NOTICE:  failed_login_times: 5! failed_login: 5!
NOTICE:  Account test1 is locked!
psql (12.8)
Type "help" for help.

postgres=# select rolcanlogin from pg_catalog.pg_roles where rolname='test1';
 rolcanlogin
-------------
 f
(1 row)

test1再次登录 提示如下

[postgres@localhost session_exec-master]$ psql -Utest1 -h127.0.0.1 -p18126 -d postgres
Password for user test1:
psql: error: FATAL:  role "test1" is not permitted to log in

具体的用户 解锁命令如下

[postgres@localhost ~]$ psql -Upostgres -h127.0.0.1 -p18126 -d testdb1
psql (12.8)
Type "help" for help.

testdb1=# alter user test1 login;
ALTER ROLE

3 问题与改进

3.1 问题说明

如2中的步骤 确实可以达到登录失败5次就锁定用户 但是 每次都是全量查询postgres_log表
举个例子 如果你已经登录失败了5次 现在给用户解锁 下次再登录的时候 sql的逻辑是从postgres_log表按照log_time 倒序
取前5个 正常分析 那你这次解锁后的第一次登录 你的失败次数已经是5了
从功能上来说 确实是实现了 但是关于已经失败的次数 不准确 也没有时间限制 时间范围是过去的所有的时间

如下的操作 通过引入用户登录表来解决上述不准确的问题 给登录失败添加了时间 2小时内失败超过5次就锁定用户

这部分的内容是由公司ITSM研发Frank完成的 感谢大佬鼎力相助 让session_exec的方案更加完善 致敬 😄

这部分的具体逻辑都在最后的login函数里 读者可以自己研读 做如下几点提示 如果直接阅读有困难 可以参考

  • login函数先计算2小时之前的时间点 作为检查时间段的起始时间
  • 查询postgres_log表里大于起始一时间的登录失败记录 选择最后5条 将数据插入到用户登录表 用以记录登录失败的用户(注意 t_login表里的都是登录失败的记录)
  • 用户登录表里flag字段默认值是0 表示未过期 也就是说表示在两小时内
  • 执行update操作 更新用户登录表的flag字段 把超过2小时的登录请求 flag设置为1 表示已经失效了 超过两小时了 后续不拿这些数据做分析、计算
  • 从用户登录表里获取用户最后一次登录失败的时间(上边说过 用户登录表里都是用户登录失败的记录 所以去最后一条 就是用户最后登录失败的记录)
  • 取出用户登录失败的次数(flag为0的表示在有效期内的 直接从用户登录表count就行)
  • 比较登录失败的此处跟限定的次数 达到条件就锁定用户

3.2 操作步骤

如下操作步骤 是基于2的操作之上的

重建日志表

当前操作环境pg位14 如果版本低于14 执行login函数的时候可能会报错 如果报错 去看下一行日志的字段个数跟这个表的字段格式是否相等 extra_parama和extra_paramb是笔者自己添加的 14版本的pg 不增加两个字段 会出错


DROP FOREIGN TABLE public.postgres_log;

CREATE FOREIGN TABLE public.postgres_log (
        log_time timestamp(3) NULL,
        user_name text NULL,
        database_name text NULL,
        process_id int4 NULL,
        connection_from text NULL,
        session_id text NULL,
        session_line_num int8 NULL,
        command_tag text NULL,
        session_start_time timestamptz NULL,
        virtual_transaction_id text NULL,
        transaction_id int8 NULL,
        error_severity text NULL,
        sql_state_code text NULL,
        message text NULL,
        detail text NULL,
        hint text NULL,
        internal_query text NULL,
        internal_query_pos int4 NULL,
        context text NULL,
        query text NULL,
        query_pos int4 NULL,
        "location" text NULL,
        application_name text NULL,
        process_name text NULL,
        extra_parama text NULL,
        extra_paramb int8 NULL
)
SERVER pglog
OPTIONS (program 'find /data/logs/pg -type f -name "*.csv" -mtime -1 -exec cat {} \;', format 'csv');

创建用户登录表

CREATE TABLE public.t_login (
        login_time timestamp with time zone NOT NULL,
        user_name text NOT NULL,
        flag int4 NULL DEFAULT 0
);
CREATE INDEX t_login_time_idx ON public.t_login (login_time);
CREATE INDEX t_login_user_name_flag_idx ON public.t_login (user_name,flag);
COMMENT ON TABLE public.t_login IS '用户登录记录表';

-- Column comments

COMMENT ON COLUMN public.t_login.login_time IS '登录时间';
COMMENT ON COLUMN public.t_login.user_name IS '登录用户';
COMMENT ON COLUMN public.t_login.flag IS '过期表示:0未过期,1过期';

重建login

create or replace function public.login() returns void as $$
declare
res record;
failed_login_times int = 5;
failed_login int = 0;
-- 用户登录最近时间
failed_login_last_time timestamp(3);
-- 过期时间
expiration_time timestamp(3);
begin
        
-- 设置登录失败检查时段的起始时间
SELECT now()::timestamp + '-2 hour' into expiration_time;
raise notice 'expiration_start_time: %!', expiration_time;

-- 获取数据库中所有可连接数据库的用户
for res in select rolname from pg_catalog.pg_roles where rolcanlogin= 't' and rolname !='postgres'
loop
  raise notice 'user: %!', res.rolname;

  -- 获取用户上次登录过期时间
  select login_time from public.t_login where user_name=res.rolname
     order by login_time desc limit 1 into failed_login_last_time;
  if failed_login_last_time is null then 
    failed_login_last_time = expiration_time;
  end if;
  raise notice 'failed_login_last_time: %!', failed_login_last_time;
 
  -- 将最新的数据插入t_login表
  insert into public.t_login(login_time,user_name)
  select log_time,user_name from public.postgres_log
      where command_tag='authentication' and error_severity= 'FATAL'
        and user_name = res.rolname and log_time > failed_login_last_time
        and (detail is null or detail not like 'Role % does not exist.%')
        order by log_time desc limit failed_login_times;
 
  -- 距离现在超过2小时的设置为过期数据
  update public.t_login set flag=1 where user_name=res.rolname and flag=0 and login_time < expiration_time;
 
  -- 获取当前用户最近连续登录失败次数
  select count(1) from public.t_login WHERE user_name=res.rolname and flag=0 into failed_login ;
     
  raise notice 'failed_login_times: %! failed_login: %!',failed_login_times,failed_login;
  --用户最近密码输入错误次数达到5次或以上
  if failed_login >= failed_login_times then
    --锁定用户
    EXECUTE format('alter user %I nologin',res.rolname);
    raise notice 'Account % is locked!',res.rolname;
    -- 设置当前表中数据全部过期,重新计算过期次数
    update public.t_login set flag=1 where user_name=res.rolname and flag=0;
  end if;
end loop;
end;
$$ language plpgsql strict security definer set search_path to 'public';
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值