Почему ssh-agent и ssh-add не работают вместе в одном bash-скрипте?

Почему ssh-agent и ssh-add не работают вместе в одном bash-скрипте?

Я написал один скрипт, в котором я хотел бы запустить первый ssh-agent, чтобы запустить агента в фоновом режиме, и установить соответствующие переменные среды для текущего экземпляра оболочки. Однако во второй части скрипта я также хотел бы добавить свой закрытый ключ SSH, чтобы подключиться к моему серверу.

В настоящее время ни одна из команд в скрипте не работает друг с другом. Может ли кто-нибудь помочь мне правильно понять, что я делаю неправильно?

#!/bin/bash

exec ssh-agent bash
sleep 5s
ssh-add /media/MyUSB/.ssh/id_00123 &

Кроме того, при использовании встроенного отладчика bashя вижу, что работает только первая часть скрипта (т. е. exec ssh-agent bash).

решение1

Столько интересных аспектов в таком маленьком сценарии.


Пониманиеssh-agent

Давайте начнем с того, ssh-agentдля чего он предназначен. Вы запускаете ssh-agent, когда вам нужен процесс, который сидит там, слушает какой-то сокет (это типфайлдля двусторонней межпроцессной коммуникации) и обслуживает запросы от программ, таких как ssh-addили ssh, которые подключаются к сокету. Программы будут общаться с агентом и хранить, манипулировать или использовать закрытые ключи.

Любая программа, которая хочет использовать агента, должна знать путь к сокету, который слушает агент. Если программа знает путь, то она может использовать сокет для связи с агентом.

Когда-то было принято решение о дизайне: любая программа, которая хочет узнать путь к сокету агента аутентификации, должна проверить SSH_AUTH_SOCKпеременную в своей собственной среде, значение переменной — это путь. Это было решение (я имею в виду, что вещи можно было бы спроектировать и по-другому, например, программы можно было бы спроектировать так, чтобы они принимали этот путь через аргументы командной строки каждый раз), но это было очень хорошее решение.

Это было очень хорошее решение, поскольку среда по умолчанию наследуется. Это означает, что вам нужно задать SSH_AUTH_SOCKпеременную среды для одного процесса (например, оболочки), и все его потомки унаследуют ее (если только некоторые из них намеренно не решат изменить свою среду или не создадут потомка с измененной средой). Для сравнения: передача пути в качестве аргумента командной строки каждый раз, когда вы хотите запустить что-то, что должно общаться с агентом, требует дополнительного ввода; и вам захочется где-то сохранить путь, так что, вероятно, в переменной в любом случае. Итак, вот, имя переменной стандартизировано, и заинтересованные программы проверяют его автоматически.

Другой вариант — сохранить путь в текстовом файле в фиксированном месте или даже создать сокет в фиксированном месте изначально. Но иногда вы хотите, чтобы некоторые программы использовали одного агента (один сокет), а некоторые другие программы использовали другого агента (другой сокет). Заставить две программы видеть разные файлы в одном месте сложно. Заставить две программы видеть разные переменные окружения легко.

Итак, заинтересованные программы должны проверить SSH_AUTH_SOCKв своей среде. Как мы можем или что-то еще установить эту переменную на правильное значение в среде процесса? Без отладчика есть два способа:

  1. Либо родитель знает значение, и когда он порождает дочерний элемент, он устанавливает SSH_AUTH_SOCKправильное значение в среде для дочернего элемента (акт наследования без изменений SSH_AUTH_SOCKот родителя может быть интерпретирован как «родитель устанавливает это, ничего не делая»);

  2. или процесс узнает значение каким-то другим способом и изменяет свою собственную среду.

Поэтому ssh-agentподдерживаются два способа запуска:

  1. ssh-agent command …
    

    Здесь ssh-agentсоздается сокет и подготавливается к обслуживанию будущих программ, подключающихся к сокету. Затем он запускается command …как его потомок с SSH_AUTH_SOCKправильным значением в среде для потомка. Потомок (или любой потомок, который наследует переменную) может легко найти сокет, но другие процессы не так легко. Когда завершается command, то же самое происходит ssh-agent(даже если есть внуки).

  2. ssh-agent   # but don't use it exactly this way
    

    Здесь ssh-agentпроисходит ответвление в фоновый режим, т. е. он создает дочернюю копию себя и не ждет ее выхода. Дочерний процесс отсоединяется от стандартных потоков родителя и от терминала, он не выйдет сам по себе. Дочерний процесс будет настоящим агентом, который останется. Родительский процесс выйдет сам по себе, но прежде чем это произойдет, печатается код оболочки. Код оболочки, когда оценивается оболочкой, заставляет оболочку изменять свою собственную среду, поэтому SSH_AUTH_SOCKс правильным значением помещается туда.Но оболочка должнаоцениватьвывод, а не просто запускssh-agent, поэтому правильный способ такой:

    eval "$(ssh-agent)"
    

    После этого запущенная оболочка evalимеет правильную переменную (фактически: переменные) в своей среде, и с этого момента команды типа ssh-addrun из этой оболочки будут находить агента, поскольку они наследуют переменную. Выход из оболочки не завершает работу агента, поэтому в какой-то момент перед выходом из оболочки вы можете вызвать ssh-agent -k(или, если вы также хотите сбросить переменные: eval "$(ssh-agent -k)"). Агент, для которого нет процесса, содержащего правильное значение , SSH_AUTH_SOCKпрактически бесполезен.


Что не так с вашим сценарием?

И теперь – наконец – ваш сценарий. Это ваш сценарий:

#!/bin/bash

exec ssh-agent bash
sleep 5s
ssh-add /media/MyUSB/.ssh/id_00123 &

Первое, что делает скрипт, это exec ssh-agent bash. execсообщает оболочке, интерпретирующей скрипт, заменить себя командой, которая есть ssh-agent bash. Оболочка делает это и становится , ssh-agentкоторая начинает новый bash(это метод 1 из выше). Он bashсодержит правильное значение SSH_AUTH_SOCK, он интерактивный, он выводит приглашение и позволяет вам запускать команды (включая команды, которым нужно SSH_AUTH_SOCK). Если ваша исходная интерактивная оболочка была , bashто вы можете упустить тот факт, что теперь вы находитесь в отдельном bash. Вы можете интерпретировать существование SSH_AUTH_SOCKкак подтверждение того, что ssh-agentизменило среду вашей исходной оболочки. Нет, вы все еще находитесь в середине своего скрипта.

Ну, не совсем посередине. Если выйти из этого bash, то sleepи остальное не будет выполнено, потому что интерпретирующая скрипт оболочка заменила себя на ssh-agent. В каком-то смысле вы один exitперед концом скрипта.

Если ваш метод запуска скрипта был как ./myscript, то exitвернет вас в исходную оболочку. Если ваш метод был как. ./myscriptилиsource myscriptто exitбудет действовать так, как будто вы вышли из исходной оболочки, поскольку исходная оболочка была оболочкой, интерпретирующей сценарий, и заменила себя на ssh-agentоболочку, которая собирается выйти exitиз текущей оболочки; это может усилить впечатление, что вы находились в исходной оболочке (и теперь выходите из нее).


Исправление

В вопросе вы четко обозначили свою цель:

[…] соответствующие переменные среды для текущего экземпляра оболочки. […]

Чтобы изменить окружение текущей оболочки, скрипт должен использовать метод 2 из вышеприведенного. Текущая оболочка должна быть интерпретирующей оболочкой, т. е. скрипт должен быть исходным. Оболочка не должна ни execк чему, так как вы не хотите, чтобы оболочка была заменена чем-либо. Пример исправления:

#!/usr/bin/false
[ -n "$SSH_AUTH_SOCK" ] || eval "$(ssh-agent)"
ssh-add /media/MyUSB/.ssh/id_00123

Вот еще кое-что, что было улучшено:

  • #!/usr/bin/falseтак как shebang гарантирует, что скрипт ничего не сделает и не выполнит свою задачу, если вы (непреднамеренно) запустите его вместо того, чтобы получить его. Другие стратегии здесь:Стратегия, позволяющая забыть запустить скриптsource.Без всяких уловокили с shebang, указывающим на bash, shили другую совместимую оболочку, выполненный скрипт (не исходный) запустит нового агента, добавит к нему ключ и завершит работу. Все это не затрагивает среду вашей текущей оболочки, поэтому агент будет сидеть там напрасно, почти недоступный. Вам нужно будет приложить некоторые усилия, чтобы найти и убить его, или некоторые усилия, чтобы найти его сокет и вручную настроить SSH_AUTH_SOCKв среде вашей оболочки; или вы просто оставите его в покое. falseпоскольку shebang предотвращает этот неудобный сценарий.

  • [ -n "$SSH_AUTH_SOCK" ]проверяет, $SSH_AUTH_SOCKрасширяется ли до непустой строки. Пустая строка указывает на отсутствие доступного агента, а непустая строка указывает на то, что агент, вероятно, есть. Скрипт начинает новый, ssh-agentтолько если строка пуста. Это базовая мера предосторожности против сценария, когда вы (непреднамеренно) вызываете скрипт во второй раз, создаете нового агента аутентификации и теряете переменные, связанные с предыдущим агентом, который будет продолжать работать бесполезно.

  • Нет необходимости в том, чтобы sleep. ssh-agentв нашем скрипте выходы происходили, когда агент (т.е. его потомок в фоновом режиме) готов. Вы можете сделать это ssh-addсразу.

  • ssh-addздесь как синхронная команда. Запуск ее асинхронно (с помощью &, как вы пытались сделать) вряд ли сэкономит вам много времени. Вы можете попробовать. Но вы, скорее всего, получите скрипт из интерактивной оболочки с включенным управлением заданиями и, таким образом &(если вы поместите его туда), засорите свой терминал сообщением типа [1]+ Done ….

Связанный контент