Вот то, что я написал на этой неделе в качестве способа безопасного встраивания учетных данных (например, имени учетной записи и пароля) для предоставления программе, когда она помещается в другое место для запланированного запуска.
В моем случае это работает за счет того, что Python выдает команду на bash и собирает то, что приходит обратно, но это должно работать на любой системе с каким-либо видом оболочки и с установленными исполняемыми файлами SSL и SSH.
Пожалуйста, имейте в виду, что моей целью было получить что-то, что вообще работает — нет никаких претензий на то, что эта специфика является идеальной реализацией.
Ограниченный контекст
Это легко может показаться более впечатляющим, чем есть на самом деле, поэтому позвольте мне прояснить ограниченный контекст, в котором это полезно.
Моя ситуация заключается в том, что я пишу программу на Python, которая должна будет подключаться к нескольким удаленным системам, поэтому ей нужно будет обрабатывать учетные данные для каждой из этих систем.
Место, где программа в конечном итоге будет выполняться, не является местом, где я ее разрабатываю. Более того, место, где она будет запущена, будет мне неизвестно — возможно, это будет «служебная учетная запись» на какой-то виртуальной машине, которую я никогда не увижу.
Кроме того, я не хочу, чтобы человек, создающий эту учетную запись, организовывал технологию обработки учетных данных — по крайней мере, не в том смысле, что ему придется изменять мою программу в момент развертывания.
Бывает так, что во время разработки и тестирования у меня будут учетные записи на удаленных системах, которые я буду использовать, но это будут не те же самые учетные записи, которые будут использоваться при развертывании.
Подход
Вот что я придумал:
- во время выполнения моя программа будет использовать закрытый ключ SSH учетной записи, под которой она запущена, для расшифровки файла с парой учетных данных для каждой удаленной системы;
- Для этого есть сопутствующая программа, которая предоставляет способ использования назначенного открытого ключа SSH для шифрования необходимых пар учетных данных в файлах, которые идут вместе с развернутым кодом Python.
Это означает, что зашифрованные учетные данные могут быть расшифрованы только целевой учетной записью (хотя технически это означает, что также любой, кто имеет доступ к ее закрытому ключу).
Зашифрованные учетные данные могут быть сгенерированы тем, кто знает, что это такое, и получил открытый ключ целевой учетной записи.
Во время разработки я могу просто использовать свою собственную пару открытый/закрытый ключ для генерации и последующего использования набора файлов учетных данных.
Как это сделать
Есть две команды, каждая из которых реализована в двух уровнях кода.
- Нижний уровень — это две команды bash, одна для шифрования, другая для расшифровки.
- Внешний уровень — это обертка Python для создания и выполнения этих двух команд bash.
Примечание: хотя я реализовал это с помощью однострочных команд bash, любая другая оболочка и система с SSL и SSH, к которой она может обращаться, должны быть вполне выполнимы.
Примеры в bash
Я показываю два альтернативных режима работы:
- когда «секрет» уже находится в виде файла
- когда «секрет» будет строкой во время выполнения.
Файл — Файл — Файл
# Make a secret file
echo "the quick brown fox jumps over the lazy dog" > secret.key
# Encrypt it
openssl rsautl -encrypt -oaep -pubin -inkey <(ssh-keygen -e -f ~/.ssh/id_rsa.pub -m PKCS8) -in secret.key -out secret.key.enc
# Delete the secret
rm secret.key
# Decrypt
openssl rsautl -decrypt -oaep -inkey ~/.ssh/id_rsa -in secret.key.enc -out secret.key
Строка к файлу к строке
Здесь, помимо того, что не нужно работать с уже существующим файлом, я добавил идею base64 кодирования передаваемого файла. Это должно сделать его немного более устойчивым к различным режимам передачи файлов. Поэтому у цели есть соответствующий шаг декодирования base64.
# Storage
echo "The quick brown fox jumps over the lazy dog" | openssl rsautl -encrypt -oaep -pubin -inkey <(ssh-keygen -e -f ~/.ssh/id_rsa.pub -m PKCS8) | base64 > socret.key.enc.b64
# Retrieval
base64 -d socret.key.enc.b64 | openssl rsautl -decrypt -oaep -inkey ~/.ssh/id_rsa
Примеры в Python
Для этого мы будем использовать метод string-to-file-to-string, описанный выше. Это позволяет использовать функции Python еще большей абстракции для обработки любых желаемых структурирований «секретного» материала.
Все, что я написал для моего случая использования, это строка из двух строк текста, разделенных символом новой строки — т.е. как конструкция userid + n + password, которая после расшифровки была соответствующим образом разделена еще раз.
Но в зависимости от ваших потребностей, секретная посылка может быть вообще чем угодно — например, JSON, XML и т.д.
Обертка в Python для шифрования
import subprocess
def StoreEncryptedWithPublicKey( p_storeinto_pfn, p_secret, p_pubkey_pfn ):
prep_secret = p_secret #rework the secret to be suitable for a bash echo command
if len( p_pubkey_pfn.strip() ) == 0 :
p_pubkey_pfn = "~/.ssh/id_rsa.pub"
cmd_str = 'echo "' + prep_secret + '" | openssl rsautl -encrypt -oaep -pubin -inkey <(ssh-keygen -e -f ' + p_pubkey_pfn + ' -m PKCS8) | base64 > ' + p_storeinto_pfn
process = subprocess.Popen(['bash', '-c', cmd_str], stdout=subprocess.PIPE)
i_out, i_err = process.communicate()
Обертка на Python для расшифровки
def FetchDecryptingWithPrivateKey( p_pfn ):
i_secret = ""
cmd_str = "base64 -d " + p_pfn + " | openssl rsautl -decrypt -oaep -inkey ~/.ssh/id_rsa"
process = subprocess.Popen(['bash', '-c', cmd_str], stdout=subprocess.PIPE)
i_secret, err = process.communicate()
return i_secret
Благодарности
Хотя я перечитал довольно много литературы, чтобы прийти к этому небольшому решению, две идеи, которые я в конечном итоге использовал, появились благодаря адаптации предложений, найденных на этих двух страницах:
- Шифрование и расшифровка файла с помощью ключей SSH
- Запуск сложной командной строки в python
Причина, по которой я написал это сообщение, заключается в том, чтобы привести пример объединения всего этого в решение.