如何保留我在ssh
(命令行 openssh 客户端通过 启动bash
)中使用的所有远程命令的本地带时间戳记录?
要求:
基本的:
- 100% 客户端,不依赖服务器日志记录
- 按用户配置或安装,日志存储在用户的主目录中。
- 支持区分与不同用户和主机的多个同时会话。
- 非侵入式(无需每次激活,不会显着干扰 ssh 的使用)
高优先级:
- 要么不记录输出,要么尽可能过滤掉输出
- 未记录密码条目或文件已加密
- 指示实际使用的命令(制表符/历史记录完成后、退格键、CTRL+C等...已被处理)
很高兴有:
- 还记录链接会话中的命令(在远程
ssh
或su <user>
会话期间输入的命令) - 应记录会话开始和结束
- 一个简单的
bash
基于非 root 的解决方案将是最好的(也许是命令的alias
或bash
包装脚本ssh
?)
- 还记录链接会话中的命令(在远程
我的技能水平:
- 我对编程并不陌生,但仍在学习
bash
“Linux 方式”,因此带有简短说明的代码示例将非常感激。
可能的策略
- 键盘记录器-- 问题:记录密码,不记录选项卡/历史记录完成(请参阅格伦的回答)
screen
每秒回滚一次并diff
在它们之间查找新的回滚行-- 问题:如何以有用的自动化方式实现这一点?ssh "$@" | tee >(some_cleaner_function >> $logfile)
-- 问题:无法处理链接会话中的多行命令或历史记录,需要仔细清理(请参阅我的答案)- 上述一些内容的组合
一个例子
以下 SSH 会话:
user@local:~$ ssh user@remote
Last login: Tue Jun 17 16:34:23 2014 from local
user@remote:~$ cd test
user@remote:~/test$ ls
a b
user@remote:~/test$ exit
~/logs/ssh.log
可能会产生如下日志:
2014-06-17 16:34:50 [user@remote - start]
2014-06-17 16:34:51 [user@remote] cd test
2014-06-17 16:34:52 [user@remote] ls
2014-06-17 16:34:53 [user@remote] exit
2014-06-17 16:34:53 [user@remote - end]
或者,可能会为每个会话创建一个单独的日志,并在文件顶部使用用于启动会话的命令行。
答案1
我对你的问题很感兴趣。我原本不想给出答案,但我被迷住了。
这expect
确实是一个键盘记录器。
#!/usr/bin/expect -f
proc log {msg} {
puts $::fh "[timestamp -format {%Y-%m-%d %H:%M:%S}]: $msg"
}
set ssh_host [lindex $argv 0]
set ::fh [open "sshlog.$ssh_host" a]
log "{session starts}"
spawn ssh $ssh_host
interact {
-re "(.)" {
set char $interact_out(1,string)
if {$char eq "\r"} {
log $keystrokes
set keystrokes ""
} else {
append keystrokes $char
}
send -- $char
}
eof
}
log "{session ends}"
笔记:
- 它附加到名称中包含 ssh 目标的文件
- 它是一个键盘记录器:如果您尚未设置 ssh 密钥,您可以在日志文件中获取用户的密码
- 它被制表符完成所挫败:如果用户键入uptTab(对于
uptime
命令),您将在日志文件中得到“upt\t”,而不是“uptime” - 它以“原始”模式获取字符:如果用户是一个糟糕的打字员,您将
^?
在日志文件中看到大量(退格字符)。
答案2
我目前正在使用下面的 bash 脚本。它有很多问题,但它是我发现的唯一解决方案,可以满足所有要求、优先级和“有好有坏”(至少在大多数情况下)。
这个答案讨论为什么在本地记录 ssh 会话如此困难。
到目前为止我发现的脚本问题:
多行命令会导致问题:
- 如果您翻阅远程历史记录中的多行项目(使用向上/向下键),它将记录历史记录项目而不是最新命令。您可以通过以下方式避免这种情况从 bash 历史记录中删除任何多行命令在使用后立即执行。
- 仅记录多行命令的第一行。
链接会话(在远程端使用
ssh
或su
命令)会导致历史记录滚动以记录滚动传递的命令,而不是实际使用的命令正则表达式可以改进,并且可能需要针对某些环境进行修改:
- 我通过在
cat -v
清洁之前转换非打印字符来作弊。因此,如果您曾经^[[
在命令中使用过字符串,则可能会删除有效内容。 - 有时,您会在命令之前获得额外的记录输入,例如如果您非常快速地翻阅历史记录。这通常在实际命令之前跟随一个“^M”,因此如果需要的话可以将其删除。
- 有时会出现其他控制字符。我暂时将它们全部留在里面,直到我知道哪些可以安全移除。我刚才提到的 ^M 对于检测无效的记录输入很有用,并且 ^C 会告诉您命令是否被中止。
- 提示正则表达式可能需要针对特定提示进行修改,并且我可以想象不同的远程环境可能具有不同的控制字符模式。
- 我通过在
没有 ssh 命令 bash 补全,例如主机名。ssh
如果将此脚本别名为with,则可以获得 bash 补全alias ssh="sshlog"
脚本来源及安装:
要安装,请将以下内容粘贴到 ~/bin/sshlog 并使其可执行。致电sshlog <ssh command options>
.可以选择在用户的 .bashrc 文件中将其别名为“ssh”。
#!/bin/bash
# A wrapper for the ssh command that produces a timestamped log of all ssh commands
declare -r logfile=~/logs/ssh.log
declare -r description="sshlog-${$} ${@}"
declare -r TAB=$'\t'
logdir=`dirname ${logfile}`
[ -d ${logdir} ] || mkdir "${logdir}";
clean_control_chars() {
while IFS= read -r line; do
# remove KNOWN control characters. Leave the rest for now.
# line=$(echo "${line}" | sed 's/\^\[\[K//g') # unkown control character: ^[[K
# line=$(echo "${line}" | sed 's/\^\[\[[0-9]\+[P]//g') # these are generated by up/down completion - e.g. ^[[2P
line=$(echo "${line}" | sed 's/\^\[\[[0-9]*[A-Z]//g') # all other ^[[..
# replay character deletions (backspaces)
while [[ $(echo "${line}" | grep -E --color=never '.\^H') != "" ]]; do
line=$(echo "${line}" | sed 's/.\^H//')
done
# remove common control characters
line=$(echo "${line}" | sed 's/\^M$//') # remove end of line marker from end
line=$(echo "${line}" | sed 's/^\^G//g') # remove start marker from start
# remove ^G from other locations - possibly a good idea
# line=$(echo "${line}" | sed 's/\^G//g')
# remove all other control characters - not recommended (many like ^C and ^M indicate which section was processed/ ignored)
# line=$(echo "${line}" | sed 's/\^[A-Z]//g')
echo ${line};
done
}
filter_output() {
while IFS= read -r line; do
# convert nonprinting characters and filter out non-prompt (in Ubuntu 14.04 tests, ^G indicates prompt start)
line=$(echo "${line}" | cat -v | grep -Eo '[\^][G].*[\$#].*')
[[ ${line} != "" ]] && echo "${line}"
done
}
format_line() {
while IFS= read -r line; do
raw=${line};
line=$(echo "${line}" | clean_control_chars);
prompt=$(echo "${line}" | grep -Po '^.*?(\$|#)[\s]*')
command=${line:${#prompt}}
timestamp=`date +"%Y-%m-%d %H:%M:%S %z"`
echo -e "${timestamp}${TAB}${description}${TAB}${prompt}${TAB}${command}"
done
}
echo "Logging ssh session: ${description}"
echo "[START]" | format_line >> ${logfile}
/usr/bin/ssh "$@" | tee >(filter_output | format_line >> ${logfile})
echo "[END]" | format_line >> ${logfile}
日志内容示例:
2014-06-29 23:04:06 -0700 sshlog-24176 remote [START]
2014-06-29 23:04:12 -0700 sshlog-24176 remote oleg@remote:~$ cd test
2014-06-29 23:04:13 -0700 sshlog-24176 remote oleg@remote:~/test$ ls
2014-06-29 23:04:14 -0700 sshlog-24176 remote oleg@remote:~/test$ exit
2014-06-29 23:04:14 -0700 sshlog-24176 remote [END]
答案3
怎么样strace -o /tmp/ssh_log -ff -s8192 -T -ttt -fp $(pidof sshd)
?这会记录所有 ssh 会话。您可能需要一个工具来解析日志,或者只是使用等等grep
。awk
-f
:追踪分叉的孩子-ff
:将每个孩子分别记录到ssh_log.PID
-s8192
:增加字符串记录限制(如果需要)-T -ttt
:自纪元以来以秒为单位的微秒标记-p N
: 附加到 pidN
答案4
我有一个不太复杂的答案,而且肯定不是键盘记录器。我不明白你的观点是独立于服务器日志(这意味着所有操作都需要对服务器进行,并且所有日志都是服务器端日志),因此我认为一个好主意是传递到系统范围的 bashrc提示命令如:
PROMPT_COMMAND='history -a >(tee -a ~/.bash_history | logger -t "$USER[$$] $SSH_CONNECTION")'
在 debian 中,您应该编辑文件:/etc/bash.bashrc;在 centos 中,您应该编辑文件:/etc/bashrc
如果您想开始记录您所在的会话,则必须获取您已编辑的文件,例如执行:
source /etc/bash.bashrc
在 Debian 系统中或
source /etc/bashrc
在centos系统中。
从现在开始,每个 ssh 会话的每个命令都将记录在/var/log/系统日志在 Debian 系统上,并且在/var/日志/消息在centos系统上。
如果您想将它们记录在单独的文件中并且不与其他日志文件混淆,您可以使用:
PROMPT_COMMAND='history -a >(tee -a ~/.bash_history | logger -p local6.info -t "$USER[$$] $SSH_CONNECTION")'
而不是前面的 PROMPT_COMMAND 示例,然后根据需要配置 rsyslogd。
例如在 Debian 系统中编辑/etc/rsyslog.conf文件:更改行:
.;auth,authpriv.none -/var/log/syslog
到
.;auth,authpriv.none,local6 -/var/log/syslog
并将以下行添加到文件末尾:
local6.info /var/log/history.log
然后执行:
touch /var/log/history.log && /etc/init.d/rsyslog restart