pexpect и ssh: как отформатировать строку команд после su - root -c

Я пытаюсь просмотреть список серверов и паролей, чтобы изменить конфигурации sshd на группе серверов, чтобы я мог входить/выполнять команды через root, используя ключи SSH без пароля.

Я могу легко сделать это в bash, но я пытаюсь изучить Python и (очевидно) хотел бы отказаться от ввода паролей вручную.

Вот то, что я хочу сделать:

scp ~/.ssh/id_rsa.pub /etc/ssh/sshd_config USER@IP:/tmp/

ssh -o StrictHostKeyChecking=no -t USER@IP "su - root -c \"chown root:root /tmp/id_rsa.pub; chmod 600 /tmp/id_rsa.pub; chown root:root /tmp/sshd_config; mkdir /root/.ssh; chown root:root /root/.ssh; chmod 700 /root/.ssh; mv /tmp/id_rsa.pub /root/.ssh/authorized_keys; mv /tmp/sshd_config /etc/ssh/; service sshd reload\""

Я приблизился к тому, чтобы сделать это на Python с помощью pexpect:

import pexpect

USER="user"
HOST="192.168.1.1"
USERPASS="userpass" 
ROOTPASS="rootpass"

COMMAND1="scp /Users/user/.ssh/id_rsa.pub /Users/user/github/ssh-pexpect/sshd_config %s@%s:/tmp/" % (USER, HOST)

COMMAND2="ssh -o StrictHostKeyChecking=no -t %s@%s \"su - root -c \"chown root:root /tmp/id_rsa.pub; chmod 600 /tmp/id_rsa.pub; chown root:root /tmp/sshd_config; mkdir /root/.ssh; chown root:root /root/.ssh; chmod 700 /root/.ssh; mv /tmp/id_rsa.pub /root/.ssh/authorized_keys; mv /tmp/sshd_config /etc/ssh/; service sshd reload\"\"" % (USER, HOST)

child = pexpect.spawn(COMMAND1)
child.expect('password:')
child.sendline(USERPASS)
child.expect(pexpect.EOF)
print child.before

child = pexpect.spawn(COMMAND2)
child.expect('password:')
child.sendline(USERPASS)
child.expect('Password:')
child.sendline(ROOTPASS)
child.expect(pexpect.EOF)
print child.before

Когда я запускаю эту команду COMMAND1 (scp'ing), она работает нормально. Но COMMAND2 не работает:

server1:ssh-pexpect user$ python test4.py 

id_rsa.pub                                    100%  410     0.4KB/s   00:00    
sshd_config                                   100% 3498     3.4KB/s   00:00    

Traceback (most recent call last):
  File "test4.py", line 25, in <module>
    child.expect(pexpect.EOF)
  File "/Library/Python/2.7/site-packages/pexpect.py", line 1316, in expect
    return self.expect_list(compiled_pattern_list, timeout, searchwindowsize)
  File "/Library/Python/2.7/site-packages/pexpect.py", line 1330, in expect_list
    return self.expect_loop(searcher_re(pattern_list), timeout, searchwindowsize)
  File "/Library/Python/2.7/site-packages/pexpect.py", line 1414, in expect_loop
    raise TIMEOUT (str(e) + '\n' + str(self))
pexpect.TIMEOUT: Timeout exceeded in read_nonblocking().
<pexpect.spawn object at 0x102b796d0>
version: 2.4 ($Revision: 516 $)
command: /usr/bin/ssh
args: ['/usr/bin/ssh', '-o', 'StrictHostKeyChecking=no', '-t', '[email protected]', 'su - root -c chown', 'root:root', '/tmp/id_rsa.pub;', 'chmod', '600', '/tmp/id_rsa.pub;', 'chown', 'root:root', '/tmp/sshd_config;', 'mkdir', '/root/.ssh;', 'chown', 'root:root', '/root/.ssh;', 'chmod', '700', '/root/.ssh;', 'mv', '/tmp/id_rsa.pub', '/root/.ssh/authorized_keys;', 'mv', '/tmp/sshd_config', '/etc/ssh/;', 'service', 'sshd', 'reload']
searcher: searcher_re:
    0: EOF
buffer (last 100 chars): : Permission denied
mv: try to overwrite `/etc/ssh/sshd_config', overriding mode 0600 (rw-------)? 
before (last 100 chars): : Permission denied
mv: try to overwrite `/etc/ssh/sshd_config', overriding mode 0600 (rw-------)? 
after: <class 'pexpect.TIMEOUT'>
match: None
match_index: None
exitstatus: None
flag_eof: False
pid: 3612
child_fd: 4
closed: False
timeout: 30
delimiter: <class 'pexpect.EOF'>
logfile: None
logfile_read: None
logfile_send: None
maxread: 2000
ignorecase: False
searchwindowsize: None
delaybeforesend: 0.05
delayafterclose: 0.1
delayafterterminate: 0.1

Если я удалю файл /etc/ssh/sshd_config на удаленном сервере перед запуском скрипта, я получу:

server1:ssh-pexpect user$ python test4.py  
id_rsa.pub                                    100%  410     0.4KB/s   00:00    
sshd_config                                   100% 3498     3.4KB/s   00:00    


chown: missing operand
Try `chown --help' for more information.
chown: changing ownership of `/tmp/sshd_config': Operation not permitted
mkdir: cannot create directory `/root/.ssh': Permission denied
chown: cannot access `/root/.ssh': Permission denied
chmod: cannot access `/root/.ssh': Permission denied
mv: accessing `/root/.ssh/authorized_keys': Permission denied
mv: cannot move `/tmp/sshd_config' to `/etc/ssh/sshd_config': Permission denied
bash: service: command not found
Connection to 192.168.1.1 closed.

Я даже не знаю, как отладить это, чтобы увидеть, где это испортилось. Однако я не думаю, что он правильно анализирует COMMAND2. Довольно новичок в Python, поэтому любые советы приветствуются. Спасибо.


person senile_genius    schedule 03.02.2012    source источник
comment
Для надлежащей безопасности вы должны создать ограниченный каталог в /tmp на удаленном хосте, прежде чем что-либо копировать туда. Если злоумышленник создал символическую ссылку в /tmp с именем id_rsa.pub, он мог натворить разных гадостей. Для большей безопасности скопируйте файлы в свой домашний каталог, где, предположительно, у злоумышленника не будет никаких разрешений.   -  person tripleee    schedule 04.02.2012


Ответы (2)


У вас есть COMMAND2 в двойных кавычках и вы правильно экранируете любые встроенные двойные кавычки, но вам также нужно дважды экранировать любые уже экранированные двойные кавычки. Другими словами, это не совсем проблема Python. Однако вы можете переключиться на тройные кавычки Python для самых внешних кавычек. Читать тоже было бы легче.

Редактировать. На самом деле подойдет любое правильное устранение неоднозначности цитирования. Поскольку оболочка также предлагает одинарные кавычки, ваше решение с одинарными кавычками подойдет. Python позволит вам использовать одинарные кавычки или ряд других средств цитирования, которые я бы рекомендовал, если вы еще не решили проблему (потому что тогда вы можете выбрать кавычки, которые не требуют каких-либо изменений в самой строке; меньше места за ошибку).

Итак, любой из них должен быть в порядке:

COMMAND2='ssh -o StrictHostKeyChecking=no -t %s@%s "su - root -c \"chown root:root /tmp/id_rsa.pub; chmod 600 /tmp/id_rsa.pub; chown root:root /tmp/sshd_config; mkdir /root/.ssh; chown root:root /root/.ssh; chmod 700 /root/.ssh; mv /tmp/id_rsa.pub /root/.ssh/authorized_keys; mv /tmp/sshd_config /etc/ssh/; service sshd reload\""' % (USER, HOST)

COMMAND2="""ssh -o StrictHostKeyChecking=no -t %s@%s "su - root -c \"chown root:root /tmp/id_rsa.pub; chmod 600 /tmp/id_rsa.pub; chown root:root /tmp/sshd_config; mkdir /root/.ssh; chown root:root /root/.ssh; chmod 700 /root/.ssh; mv /tmp/id_rsa.pub /root/.ssh/authorized_keys; mv /tmp/sshd_config /etc/ssh/; service sshd reload\"" """ % (USER, HOST)

COMMAND2="ssh -o StrictHostKeyChecking=no -t %s@%s 'su - root -c \"chown root:root /tmp/id_rsa.pub; chmod 600 /tmp/id_rsa.pub; chown root:root /tmp/sshd_config; mkdir /root/.ssh; chown root:root /root/.ssh; chmod 700 /root/.ssh; mv /tmp/id_rsa.pub /root/.ssh/authorized_keys; mv /tmp/sshd_config /etc/ssh/; service sshd reload\"'" % (USER, HOST)

Мне нужно было добавить пробел к тройным кавычкам, чтобы устранить неоднозначность соседней двойной кавычки. Но вместо этого вы можете использовать тройные одинарные кавычки. Кроме того, тройные кавычки (одинарные или двойные) позволяют вставлять новые строки, что значительно улучшает читаемость:

COMMAND2='''ssh -o StrictHostKeyChecking=no -t %s@%s "su - root -c '
    chown root:root /tmp/id_rsa.pub
    chmod 600 /tmp/id_rsa.pub
    chown root:root /tmp/sshd_config
    mkdir /root/.ssh
    chown root:root /root/.ssh
    chmod 700 /root/.ssh
    mv /tmp/id_rsa.pub /root/.ssh/authorized_keys
    mv /tmp/sshd_config /etc/ssh/
    service sshd reload'"''' % (USER, HOST)
person tripleee    schedule 03.02.2012
comment
Спасибо @tripleee. Можете ли вы скопировать/вставить пример строки? Я пробовал несколько версий двойного экранирования и тройного цитирования самых внешних кавычек, и он все еще неправильно интерпретирует COMMAND2. Определенно проблема не в Python, а в моем мозгу. :) - person senile_genius; 03.02.2012
comment
Спасибо за уточнение и отличное объяснение. - person senile_genius; 05.02.2012

В итоге я выбрал одинарные кавычки вместо того, чтобы пытаться понять, как избежать двойных кавычек:

COMMAND2="ssh -o StrictHostKeyChecking=no -t %s@%s \"su - root -c 'chown root:root /tmp/id_rsa.pub; chmod 600 /tmp/id_rsa.pub; chown root:root /tmp/sshd_config; mkdir /root/.ssh; chown root:root /root/.ssh; chmod 700 /root/.ssh; mv /tmp/id_rsa.pub /root/.ssh/authorized_keys; mv /tmp/sshd_config /etc/ssh/; service sshd reload'\"" % (USER, HOST)
person senile_genius    schedule 03.02.2012