【问题标题】:run remote program with root priviledge using paramiko ssh channel使用 paramiko ssh 通道以 root 权限运行远程程序
【发布时间】:2013-10-16 01:12:38
【问题描述】:

我想以root权限远程执行程序tcp_sender ,以下函数用于建立ssh连接

    def connect(hostname):
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostname, username='usr', pkey=paramiko.RSAKey.from_private_key(open('id_rsa'), 'psw'), timeout = 240.0)
            return ssh

那么我有 3 个解决方案:

解决方案A)

    ssh = connect(hostname)
    chan = ssh.invoke_shell()
    chan.send('sudo ./tcp_sender\n')

用这个解决方案,远程tcp_sender没有执行,我用ps -ef|grep "tcp_sender"检查,没有进程

我试过chan.send('sudo ./tcp_sender > log 2>&1\n') 在日志中,它说:

sudo: no tty present and no askpass program specified

解决方案B)

    ssh = connect(hostname)
    (stdin, stdout, stderr) = ssh.exec_command("[ -f tcp_sender ]  && echo 1 || echo 0")
    res = stdout.readlines()
    print hostname,res[0]
    if res[0] == '0\n':
            UnusedHostFile.write(hostname+'no tcp_sender exists\n')
    else:
            chan = ssh.invoke_shell()
            chan.send("sudo chmod 777 tcp_sender\n")
            # if a tcp_sender is runnning, kill it
            chan.send('x=`ps -ef|grep "tcp_sender"|grep -v "grep"|awk \'{print $2}\'`; [ -n "${x}" ] && sudo kill -9 $x\n')
            time.sleep(4)
            while not chan.recv_ready():
                    time.sleep(1)
            buf = ''
            buf +=chan.recv(9999)
            print buf
            chan.send('sudo ./tcp_sender\n')

使用此解决方案,我只需添加一些不相关的行,然后远程tcp_sender 正在运行,类似于:

bash-4.0# ps -ef|grep "sender"
root      9348  9325  0 Apr07 ?        00:00:00 sudo ./tcp_sender
root      9349  9348  0 Apr07 ?        00:00:00 ./tcp_sender

但是,它无法正常运行(如预期的那样)。在tcp_sender中,有一个fork(),可能是因为这个吧?

我试过chan.send('sudo ./tcp_sender > log 2>&1\n') 在日志中,它是空的。因为我的tcp_sender程序中有很多错误检查相关的printf,所以我认为日志中应该有printf的结果,但它是空的。

另外,我注意到一个现象,如果我kill -9 9348,这两个进程都结束了。 但是对于下一个解决方案C,进程9349将被移交给系统init进程1。

解决方案C):

通过这个解决方案,我可以正确运行远程tcp_sender。但是python脚本会被远程程序阻塞,直到它退出。我不希望我的脚本等待远程退出。

    log = open('log','a+')
    ssh = connect(hostname)
    (stdin, stdout, stderr) = ssh.exec_command("[ -f tcp_sender ] && echo 1 || echo 0")
    res = stdout.readlines()
    print hostname,res[0]
    if res[0] == '0\n':
            UnusedHostFile.write(hostname+"tcp_sender doesn't exists\n")
    else:
            chan = ssh.invoke_shell()
            chan.send("sudo chmod 777 tcp_sender\n")
            chan.send('x=`ps -ef|grep "tcp_sender"|grep -v "grep"|awk \'{print $2}\'`; [ -n "${x}" ] && sudo kill -9 $x\n')
            time.sleep(4)
            while not chan.recv_ready():
                    time.sleep(1)
            buf = ''
            buf +=chan.recv(9999)
            print buf
            chan.send('sudo ./tcp_sender\n')
            #chan.send('sudo whoami\n')
            time.sleep(2)
            (stdin, stdout, stderr) = ssh.exec_command("ps -ef|grep 'tcp_sender'|grep -v 'grep'|wc -l")
            res = stdout.readlines()
            while res[0].strip() != '0':
                    time.sleep(3)
                    (stdin, stdout, stderr) = ssh.exec_command("ps -ef|grep 'tcp_sender'|grep -v 'grep'|wc -l")
                    res = stdout.readlines()
                    print res[0].strip()
            while not chan.recv_ready():
                    time.slepp(1)
            buf = ''
            buf += chan.recv(9999)
            log.write(hostname+': '+''.join(str(elem) for elem in buf)+'\n\n')
    log.close()

那么造成这种现象的潜在原因是什么? 谁能给点建议?谢谢!

【问题讨论】:

    标签: linux ssh paramiko


    【解决方案1】:

    你正在混合你可能应该分开的东西。

    首先,在远程端编写一个脚本,usr(= 您给 paramiko 的用户名)可以执行并且可以使用 sudo 正确启动 tcp_sender 而无需输入密码等。

    在脚本中,使用nohup作为后台进程启动sudo

    nohup sudo ./tcp_sender
    

    nohup 确保新的子进程正确分离,以便在连接丢失/断开时保持活动状态。

    当此脚本运行时,使用 ssh.exec_command('script') 启动新脚本

    推理:使用 shell 和聪明的 Python 代码可能可以做你想做的事,就像你在输入命令一样驱动 shell。但它总是很脆弱,很难测试 - 它是 God object 的变体。

    相反,您可以将您的问题分解为可以独立开发和测试的小而不同的问题。你需要解决三个问题:

    1. tcp_sender 自己。
    2. 开始tcp_sender
    3. 远程启动

    所以使用三种不同的工具来解决它们。

    【讨论】:

    • 谢谢你,我按照你的程序,但是raise_on_stderr(ssh.exec_command("bash tcp_exec.sh")),我得到经典错误Exception: sudo: no tty present and no askpass program specified
    • 正如我所说:您必须以sudo 不必询问密码的方式配置您的系统。我的猜测是,对于您的测试,sudo 在本地缓存密码。见这里:cyberciti.biz/tips/…
    • sudo不需要密码,问题是tty
    • @misteryes:不。正如错误消息所说,指定了“没有 askpass 程序”。所以你要么需要一个TTY(你没有)或者你需要一个“askpass programm”或者你需要配置sudo,这样它就可以在不需要密码的情况下执行命令(因此没有 TTY 或 askpass 程序)
    • 以防万一,TTY 是终端/控制台,即以某种方式连接到键盘的东西。 en.wikipedia.org/wiki/Tty_(Unix)
    【解决方案2】:

    同意其他评论 - 您正在混合可以单独解决的问题。很多人(包括我自己)都犯了这个错误。

    Paramiko 功能强大且智能:它可以导航和响应 SSH 用户名和密码提示。但这是一个特例。大多数情况下,当您需要在 Paramiko 中响应 PROMPT 时,您基本上是等待然后在假定的提示处发送文本。这很混乱。

    这也和你真正的问题完全无关。

    您要做的是编辑 /etc/sudoers 文件,以便您的自动化测试用户或组能够使用 NOPASSWD 运行您想要的精确命令。

    假设我想在主机“ServerB”上远程 grep /var/log/auth.log。虽然 grep 可以由任何用户运行,但众所周知,此系统上的 auth.log 只能由用户 root 读取。所以:

    1) 我的测试用户是“scott”,是“adm”组的成员。请参阅 /etc/groups 和 /etc/passwd。基本内容。

    2) /etc/sudoers: %adm ALL=(ALL)NOPASSWD:/bin/grep

    3) 从远程系统,我运行: $ ssh scott@ServerB "sudo grep 接受 /var/log/auth.log" 2013-10-14T21:17:54+00:00 proc4-01-us1 sshd [28873]:接受来自 x.x.x.x 端口 56799 ssh2 的 scott 公钥 2013-10-14T21:19:16+00:00 proc4-01-us1 sshd[29367]:接受来自 x.x.x.x 端口 56804 ssh2 的 scott 公钥 2013-10-14T21:19:21+00:00 proc4-01-us1 sshd[29519]:从 x.x.x.x 端口 56805 ssh2 接受 scott 的公钥

    砰,你已经完成了。

    注意事项 请务必使用您在 sudoers 中指定的脚本的绝对文件系统路径。

    请务必使用 SSH 密钥。如果您愿意,可以使用密钥 + 密码。 (请记住,Paramiko 可以回答登录提示)但这也意味着将您的密码存储在脚本中......

    请考虑安全性。如果您将此权限附加给特殊用户,您并没有真正在此处学习安全性。当然,我描述的方法比将 sudo 密码硬编码到脚本中更安全。

    不要使用 NOPASSWD:ALL 除非在测试中。明确指定允许的内容。

    请考虑对这些用户可以运行的内容添加限制。例如,如果我总是从 EC2 机器运行此测试,我将只允许该用户从该 EC2 IP 进行连接。在对话中,我可以通过在“授权”键中添加前缀来限制用户可以运行的命令(即,如果我想避免全职运行 rsync 服务器,则限制该用户只能运行 rsync 命令示例)。

    【讨论】:

      最近更新 更多