Правильное использование ssh и sed в скрипте python с os.system

Sep 12 2020

Я пытаюсь запустить команду ssh в скрипте Python, используя os.systemдля добавления 0в конец полностью совпадающей строки на удаленном сервере, используя sshи sed.

У меня есть файл, который называется nodelistна удаленном сервере и выглядит следующим образом.

test-node-1
test-node-2
...
test-node-11
test-node-12
test-node-13
...
test-node-21

Я хочу использовать sed для внесения следующей модификации, я хочу выполнить поиск test-node-1, и когда будет найдено полное совпадение, я хочу добавить 0 в конце, файл должен в конечном итоге выглядеть следующим образом.

test-node-1 0
test-node-2
...
test-node-11
test-node-12
test-node-13
...
test-node-21

Однако когда я запускаю первую команду,

hostname = 'test-node-1'
function = 'nodelist'

os.system(f"ssh -i ~/.ssh/my-ssh-key username@serverlocation \"sed -i '/{hostname}/s/$/ 0/' ~/{function}.txt\"")

Результат становится таким,

test-node-1 0
test-node-2
...
test-node-11 0
test-node-12 0
test-node-13 0
...
test-node-21

Я попытался добавить \ b к такой команде,

os.system(f"ssh -i ~/.ssh/my-ssh-key username@serverlocation \"sed -i '/\b{hostname}\b/s/$/ 0/' ~/{function}.txt\"")

Команда вообще не работает.

Мне нужно вручную ввести имя узла вместо использования такой переменной,

os.system(f"ssh -i ~/.ssh/my-ssh-key username@serverlocation \"sed -i '/\btest-node-1\b/s/$/ 0/' ~/{function}.txt\"")

чтобы моя команда работала.

Что не так с моей командой, почему я не могу делать то, что хочу?

Ответы

5 CharlesDuffy Sep 13 2020 at 01:31

Этот код имеет серьезные проблемы с безопасностью; их исправление требует реинжиниринга с нуля. Сделаем это здесь:

#!/usr/bin/env python3
import os.path
import shlex  # note, quote is only here in Python 3.x; in 2.x it was in the pipes module
import subprocess
import sys

# can set these from a loop if you choose, of course
username = "whoever"
serverlocation = "whereever"
hostname = 'test-node-1'
function = 'somename'

desired_cmd = ['sed', '-i',
               f'/\\b{hostname}\\b/s/$/ 0/',
               f'{function}.txt']
desired_cmd_str = ' '.join(shlex.quote(word) for word in desired_cmd)
print(f"Remote command: {desired_cmd_str}", file=sys.stderr)

# could just pass the below direct to subprocess.run, but let's log what we're doing:
ssh_cmd = ['ssh', '-i', os.path.expanduser('~/.ssh/my-ssh-key'),
           f"{username}@{serverlocation}", desired_cmd_str]
ssh_cmd_str = ' '.join(shlex.quote(word) for word in ssh_cmd)
print(f"Local command: {ssh_cmd_str}", file=sys.stderr)  # log equivalent shell command
subprocess.run(ssh_cmd) # but locally, run without a shell

Если вы запустите это (за исключением subprocess.runконца, для которого потребуется реальный ключ SSH, имя хоста и т. Д.), Вывод будет выглядеть так:

Remote command: sed -i '/\btest-node-1\b/s/$/ 0/' somename.txt
Local command: ssh -i /home/yourname/.ssh/my-ssh-key whoever@whereever 'sed -i '"'"'/\btest-node-1\b/s/$/ 0/'"'"' somename.txt'

Это правильный / желаемый результат; забавная '"'"'идиома заключается в том, как безопасно вводить буквальную одинарную кавычку внутри строки в одинарных кавычках в POSIX-совместимой оболочке.


Что изменилось? Лоты:

  • Мы генерируем команды, которые хотим запускать как массивы , и позволяем Python выполнять работу по преобразованию этих массивов в строки, где это необходимо. Это позволяет избежать атак путем внедрения оболочки - очень распространенного класса уязвимостей системы безопасности.
  • Поскольку мы сами создаем списки, мы можем изменить то, как мы цитируем каждый из них: мы можем использовать f-строки, когда это уместно, необработанные строки, когда это необходимо, и т. Д.
  • Мы не переходим ~к удаленному серверу: это избыточно и ненужно, потому что ~это место по умолчанию для запуска сеанса SSH; и меры безопасности, которые мы используем (чтобы значения не были проанализированы как код оболочкой), не дают ему иметь никакого эффекта (поскольку замена ~на активное значение HOMEвыполняется не sedсама по себе, а вызывающей его оболочкой ; поскольку мы вообще не вызываем никакую локальную оболочку, нам также нужно было использовать, os.path.expanduserчтобы заставить ~in ~/.ssh/my-ssh-keyвыполняться).
  • Поскольку мы не используем необработанную строку, нам нужно удвоить обратную косую черту, \bчтобы гарантировать, что они обрабатываются Python как буквальные, а не синтаксические.
  • Важно отметить, что мы никогда не передаем данные в контексте, где они могут быть проанализированы как код любой оболочкой, локальной или удаленной .