实现SSH登录消息推送

最近有一些安全焦虑,虽然在我的服务器上一般都做了如下配置:

  • ufw limit ssh
  • fail2ban sshd
  • sshd_config禁用密码登录、对于root实行prohibit-password

但是还是想做一个SSH登录后自动向我推送消息的功能。经过一番调查发现PAM Plugin可以实现,但是自己用C写一个模组感觉比较麻烦,于是选择了现成的项目:jeroennijhof/pam_script。pam script是一个现成的pam plugin,可以在配置文件中指定被调用时需要执行的脚本/程式位置,同时通过环境变量传入远端IP等信息。结合fly.io上托管的Webhook接口,就可以比较方便实现SSH登录推送。

流程图

整体来看技术难度并不高,以Ubuntu 22.04为例需要做如下配置:

  • 安装libpam-script
  • 修改/etc/pam.d/common-auth,在底部添加:auth optional pam_script.so
  • 将需要执行的脚本或者二进制文件放置在/usr/share/libpam-script/pam_script_ses_open

一个Rust的二进制例子如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use serde::{Deserialize, Serialize};
use reqwest;

#[derive(Serialize, Deserialize)]
struct Payload {
    hostname: String,
    pam_service: String,
    pam_type: String,
    pam_user: String,
    pam_ruser: String,
    pam_rhost: String,
    pam_tty: String,
}

#[tokio::main]
async fn main() {
    let payload = Payload {
        hostname: gethostname::gethostname().into_string().unwrap(),
        pam_service: std::env::var("PAM_SERVICE").unwrap_or("".to_string()),
        pam_type: std::env::var("PAM_TYPE").unwrap_or("".to_string()),
        pam_user: std::env::var("PAM_USER").unwrap_or("".to_string()),
        pam_ruser: std::env::var("PAM_RUSER").unwrap_or("".to_string()),
        pam_rhost: std::env::var("PAM_RHOST").unwrap_or("".to_string()),
        pam_tty: std::env::var("PAM_TTY").unwrap_or("".to_string()),
    };

    let client = reqwest::Client::new();
    let _ = client.post(env!("WEBHOOK_URL"))
        .json(&payload)
        .send()
        .await;
}

而部署在FaaS平台上的Webhook编程语言我使用的是C#,可以比较简单实现多线程以及跨线程的消息队列。同时使用了IPinfo.io的SDK来获取IP对应的物理地址一并推送。这个方案有个缺点是除了ssh以外,cron等服务也会调用pam进行验证从而推送消息,可能尝试在服务端过滤PAM_SERVICE。

Licensed under CC BY-SA 4.0