最近有一些安全焦虑,虽然在我的服务器上一般都做了如下配置:
- 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。