如何在 Paramiko 中运行 sudo?(Python)

19

我尝试过以下方法:

  1. invoke_shell() 然后发送 su 和密码结果没有获得 root 权限
  2. invoke_shell() 然后使用 channel.exec_command 结果出现了 "Channel Closed" 错误
  3. _transport.open_session() 然后使用 channel.exec_command 结果没有获得 root 权限
  4. invoke_shell() 然后写入 stdin 并刷新,结果没有获得 root 权限

2
为什么不使用 setuid?http://en.wikipedia.org/wiki/Setuid - Nick ODell
8个回答

27

10
如果您的sudoer需要密码,则此方法将无法使用: “sudo:no tty present and no askpass program specified” - Haroldo_OK
2
好的,您提供的链接中riskable的评论解决了上述问题:stdin、stdout、stderr = ssh.exec_command("sudo -S %s" % command) # 如果stdout仍然打开,则sudo正在请求我们输入密码 if stdout.channel.closed is False: stdin.write('%s\n' % password) stdin.flush() - Haroldo_OK
10
我的错,我没有检查标准错误输出,应该说明必须使用 get_pty=True,所以你需要使用:ssh.exec_command('你的命令', get_pty=True) - sliders_alpha
3
get_pty=True很有帮助。没有它,密码提示不会出现。 - VISHAL VIRADIA
1
@NomNomNom 我也正想发同样的内容!做得好! - David Golembiowski
显示剩余2条评论

8

invoke_shell 对我来说是这样工作的:

import paramiko, getpass, re, time

ssh_client = paramiko.SSHClient()   
ssh_client.connect( host )
sudo_pw = getpass.getpass("sudo pw for %s: " % host)
command = "sudo magicwand"

channel = ssh_client.invoke_shell() 
channel.send( command )       
# wait for prompt             
while not re.search(".*\[sudo\].*",channel.recv(1024)): time.sleep(1)
channel.send( "%s\n" % sudo_pw )

那个 while not loop 真的很有用!谢谢分享! :) - Paul Calabro
这并不起作用,getpass 也不安全,因为它会在历史记录中显示密码。 - Amir Hossein Baghernezad
这是唯一对我有效的解决方案。但是,我不得不在所有发送的数据(命令和密码)中添加\r。否则什么也不会发生。 - Adrian W
使用这个优雅的类 https://dev59.com/qFsV5IYBdhLWcg3wwhLw#36948840。 - Anum Sheraz

6

AlexS进行了微调之后的答案(现在我正在生产中使用)如下:

def sudo_run_commands_remote(command, server_address, server_username, server_pass, server_key_file):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname=server_address,
                username=server_username,
                password=server_pass,
                key_filename=server_key_file)
    session = ssh.get_transport().open_session()
    session.set_combine_stderr(True)
    session.get_pty()
    session.exec_command("sudo bash -c \"" + command + "\"")
    stdin = session.makefile('wb', -1)
    stdout = session.makefile('rb', -1)
    stdin.write(server_pass + '\n')
    stdin.flush()
    print(stdout.read().decode("utf-8"))

如果您不使用密钥文件,可以从 connect 方法中删除 key_filename 部分;反之,如果您只使用没有密码的密钥,请删除 password 部分。

需要注意的是,这是多命令能力。这意味着它作为 root 运行了一个 bash ,因此您可以在单个运行中尽可能多地运行命令,只需用 ;分隔即可。


我正在使用您的盐和SSH隧道方法,并在返回的值中找到密码。您能否请再次检查并分享您的经验?谢谢。 - Javier

4

您可以使用通道发送sudo密码:

  passwd = getpass.getpass()
  ssh = paramiko.client.SSHClient()
  ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
  ssh.load_system_host_keys()
  ssh.connect(host, allow_agent=True)
  chan = ssh.get_transport().open_session()
  chan.get_pty()
  chan.setblocking(1)

  chan.exec_command("sudo -k dmesg")

  while chan.recv_ready()==False:
      stdout=chan.recv(4096)
      if re.search('[Pp]assword', stdout):
          chan.send(passwd+'\n')
      time.sleep(1)
      
  while chan.recv_ready():
      stdout += chan.recv(20000)
  chan.close()
  ssh.close()

2

很抱歉我没有时间提供详细的解释,但是我成功地使用这个建议在paramiko上实现了sudo命令。

import paramiko
l_password = "yourpassword"
l_host = "yourhost"
l_user = "yourusername"
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(l_host, username=l_user, password=l_password)    
transport = ssh.get_transport()
session = transport.open_session()
session.set_combine_stderr(True)
session.get_pty()
#for testing purposes we want to force sudo to always to ask for password. because of that we use "-k" key
session.exec_command("sudo -k dmesg")
stdin = session.makefile('wb', -1)
stdout = session.makefile('rb', -1)
#you have to check if you really need to send password here 
stdin.write(l_password +'\n')
stdin.flush()
for line in stdout.read().splitlines():        
    print 'host: %s: %s' % (l_host, line)

0
在我看来,创建一个具有sudoer权限的脚本会更加容易和安全。
例如,将以下内容添加到sudoers中:
myuser  ALL=NOPASSWD:/home/myuser/somescript.sh

现在你可以通过 paramiko 在主机上调用脚本,然后就完成了。

要编辑sudoers,我们必须以root身份登录(或使用su|sudo)并编辑sudoers文件或运行脚本进行编辑。如果您只能通过paramiko访问远程系统,则可能很难或不可能实现。例如,您正在自动化某些操作,您不想为每个主机手动准备脚本。 - Jury
在大多数情况下,这是不可取的,因为这存在很高的安全风险。 - Appu Sidhardh

0
我想到了这个:

我想到了这个:

def ssh_command(ssh_client, command, sudo=False):
    """Like SSHClient.exec_command, but raises if the command fails.

    Otherwise, the stdout result is returned as a string.
    """
    nbytes = 4 * 2e20           # 4 MiB
    with ssh_client.get_transport().open_session() as channel:
        command = 'sudo ' + command if sudo else command

        if sudo:
            channel.set_combine_stderr(True)
            channel.get_pty()

        channel.exec_command(command)

        if sudo:
            while not channel.recv_ready():
                stdout = channel.recv(nbytes).decode('UTF-8')
                if re.search('[Pp]assword', stdout):
                    channel.send(ROBOT_PASSWORD + '\n')
                    time.sleep(1)

        status = channel.recv_exit_status()  # blocking call
        if status != 0:
            if sudo:
                output = channel.recv(nbytes).decode('UTF-8')
            else:
                output = channel.recv_stderr(nbytes).decode('UTF-8')
            raise RuntimeError(f'command {command} exited with {status}, '
                               f'output: {output}')
        return channel.recv(nbytes).decode('UTF-8')

我很惊讶这种基本功能被留给Paramiko用户自己重新发明。


-1

我能够在远程服务器上手动运行sudo cupsdisable命令,而无需在以管理员用户(非root)登录到该服务器时输入密码,但是当我使用stdin、stdout和stderr = client.exec_command("sudo cupsdisable <Printqueuename>")执行相同的命令时,它什么也不做。

对我有效的命令是:

stdin, stdout, stderr = client.exec_command("sudo -u root /usr/sbin/cupsdisable <printQueuename>")

这仅适用于上述情况。希望能对某些人有所帮助。


1
此答案特定于您的情况,而非一般性回答。它并未回答问题。 - Azsgy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接