# ssh-watch **Repository Path**: 170cmcurry/ssh-watch ## Basic Information - **Project Name**: ssh-watch - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-29 - **Last Updated**: 2026-11-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ssh-watch Go 编写的 Linux SSH 认证日志邮件告警程序。公开仓库只包含文档保留地址和虚构配置;实际配置、邮件凭据和日志禁止提交。 ## 当前能力 - 读取 journald 中 sshd、sshd-session 进程的 JSON 事件,支持 IPv4/IPv6。 - 成功登录必须同时匹配账户、公钥指纹和来源 CIDR;密码登录默认告警。 - 已撤销指纹出现在可解析的成功或失败认证事件时告警。日志没有指纹时无法识别该身份。 - SMTP 使用隐式 TLS(通常465),验证证书,最低TLS1.2;当前不支持587 STARTTLS。 - 保存 journal cursor;邮件失败每30秒重试,成功后才推进 cursor。进程重启可能重复发送,语义为至少一次,不保证恰好一次。 - 不自动封禁IP,不修改SSH授权。 ## 构建与配置 ```sh go test ./... go build -o ssh-watch . cp config.example.json config.json export SSH_WATCH_SMTP_PASSWORD='set-this-locally' ./ssh-watch -config config.json -check ./ssh-watch -config config.json -test-email ./ssh-watch -config config.json ``` 将示例账户、指纹、CIDR、邮箱替换为实际值。来源必须使用CIDR(单IPv4为/32,单IPv6为/128);每条规则只授权同一身份。SMTP密钥通过环境变量提供,不放入JSON或命令行。运行账号需能读取系统journal。 首次无cursor时从启动时刻开始,不扫描历史;已有cursor时补读后续日志。journal清理导致历史丢失时不能保证补回;读取器退出会失败并由systemd重启,须由外部监控检查服务失效。不能依赖本机程序对抗已经取得root的攻击者,建议独立日志接收端和外部存活监控。 ## systemd部署 创建无交互登录用户ssh-watch,安装二进制到/usr/local/bin/ssh-watch,配置到/etc/ssh-watch/config.json。配置仅允许root写、ssh-watch读;smtp.env由root拥有且0600,内容为SSH_WATCH_SMTP_PASSWORD=实际值。按示例安装服务并启用: ```sh sudo systemctl daemon-reload sudo systemctl enable --now ssh-watch sudo journalctl -u ssh-watch ``` 服务以普通账号运行,通过systemd-journal组读日志,只有StateDirectory用于持久化写入。实际安装前验证配置、journal权限和邮件投递。使用-test-email发送明确标为测试的邮件验证投递,不要在生产制造未授权登录来验证。 ## 限制与后续 当前首版不包含文件日志轮转读取、失败次数聚合或独立健康上报。普通失败且无已撤销指纹的事件不告警。邮件故障时读取暂停,积压依赖journal保留;大规模事件可能邮件过多。不同OpenSSH日志格式需通过样本验证,未知格式不会被识别。单实例运行,同一cursor文件不要给多个进程使用。未验证实际SMTP服务及生产部署,不宣称已上线。 ## 企业微信邮件模式 支持官方[发送普通邮件](https://developer.work.weixin.qq.com/document/path/97445)接口,不是群机器人Webhook或聊天应用消息。选择`mode: wecom_mail`时无需配置SMTP;旧配置不填写mode仍默认SMTP。 ```sh cp config.wecom.example.json config.json export SSH_WATCH_WECOM_SECRET='set-this-locally' ./ssh-watch -config config.json -check ./ssh-watch -config config.json -test-email ``` 填写企业ID、应用Secret环境变量名、收件邮箱或企业成员userid(至少一种)。自建应用必须具备邮件接口调用权限并配置到邮件的“可调用应用”列表;userid收件人需在应用可见范围内。还需按企业微信后台要求配置可信来源。发送者由应用邮箱配置决定,不沿用SMTP的from字段。 程序使用`/cgi-bin/gettoken`获取并缓存token,过期前刷新;遇40014或42001清空缓存并重试一次。发送到`/cgi-bin/exmail/app/compose_send`,正文明确使用text类型。HTTP成功但errcode非零仍视为失败,不推进日志cursor;日志不输出带Secret/token的URL或API原始errmsg。调用结果不代表邮件最终到达收件箱。 systemd仍读取`/etc/ssh-watch/smtp.env`(兼容原安装路径),企业微信模式在该文件放`SSH_WATCH_WECOM_SECRET=实际值`即可;无需SMTP密码。不要将真实配置、Secret或token提交到公开仓库。当前每进程独立缓存;多实例应遵循平台token管理和频率限制要求。 本功能通过内存模拟HTTP接口验证请求、token刷新、缓存和错误处理。实际投递仍须使用本机私有配置执行-test-email,并确认接口成功及收件箱收到。若返回权限错误,先核对邮件“可调用应用”、应用可见范围和后台要求,不能将取得token等同于有发信权限。 ## 企业微信群消息 Webhook `mode: wecom_webhook`对应官方[消息推送配置说明](https://developer.work.weixin.qq.com/document/path/91770),向指定群发消息,不修改群公告。不需要企业ID、应用Secret或邮件权限。 复制`config.webhook.example.json`,将真实Webhook地址单独保存为`webhook.url_file`指定的文件(一行,权限0600)。只接受官方HTTPS域名和发送接口,不跟随重定向;URL带有凭据,不要放入公开配置、日志或命令行。设置`mentioned_list: ["@all"]`可以提醒全体,默认空列表。 ```sh ./ssh-watch -config config.json -check ./ssh-watch -config config.json -test-alert ``` 测试通知不包含真实主机数据且始终不@任何成员。正式文本限制在2048字节内。系统服务将URL文件放在/etc/ssh-watch内、允许服务用户只读,不要指向ProtectHome禁止访问的用户目录。该模式不需要smtp.env里的密码;现有服务模板仍要求EnvironmentFile存在,可使用空的受限文件。 需遵守平台推送频率限制;不同事件首次不合并,频繁异常可能触发限流,业务错误会触发原有重试。接口成功表示平台接受,不代表所有成员已读。 ## 群告警显示 群告警使用中文,服务器默认自动获取出站路由本地IP,也可由`server_ip`覆盖,不写业务名称;示例使用文档保留地址。时间显示UTC+08:00。旧`host_label`配置仍可加载,但不作为IP。自动获取仅选择本地路由源地址,不发送UDP数据或查询外部公网IP服务;NAT后显示内网IP,多网卡需指定IP时使用server_ip。无路由且存在多个候选地址时拒绝启动,不任意选择。IP在启动时确定,网络变化后需重启服务。测试消息不会携带服务器信息或提醒成员。 ## 重复告警冷却 默认同来源IP、账户、指纹、认证结果及原因的告警首次立即发送,随后按3、5、10、30分钟逐级退避,持续发生时保持30分钟。连续30分钟没有相同事件则重置。cooldown_seconds为0或省略使用退避,正整数保留兼容固定秒数冷却。窗口后再次触发时附带被抑制次数,无后续事件时不主动发汇总。计数保存在cursor路径加.cooldown的文件,重启保留;新来源或身份首次立即告警。投递失败仍每30秒重试。测试通知包含自动获取的服务器IP,仍不提醒群成员。