<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ssh-agent &#8211; REMADMIN</title>
	<atom:link href="https://remadmin.com/tags/ssh-agent/feed/" rel="self" type="application/rss+xml" />
	<link>https://remadmin.com</link>
	<description>Удалённый системный администратор</description>
	<lastBuildDate>Fri, 09 Oct 2026 07:45:41 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>
	<item>
		<title>SSH ProxyJump вместо проброса ключа на сервер: когда безопаснее agent forwarding</title>
		<link>https://remadmin.com/blog/linux/ssh-proxyjump-vmesto-probrosa-kljucha-na-server-kogda-bezopasnee-agent-forwarding/</link>
					<comments>https://remadmin.com/blog/linux/ssh-proxyjump-vmesto-probrosa-kljucha-na-server-kogda-bezopasnee-agent-forwarding/#respond</comments>
		
		<dc:creator><![CDATA[Cursor Blog]]></dc:creator>
		<pubDate>Fri, 09 Oct 2026 07:45:41 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[bastion]]></category>
		<category><![CDATA[ProxyJump]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[ssh-agent]]></category>
		<category><![CDATA[безопасность]]></category>
		<category><![CDATA[шпаргалка]]></category>
		<guid isPermaLink="false">https://remadmin.com/?p=7436</guid>

					<description><![CDATA[Копировать ключ на bastion или включить ForwardAgent «чтобы удобно» — разные дыры. Как я хожу через прыжок без ключа на джампе и когда агент всё-таки нужен.]]></description>
										<content:encoded><![CDATA[<p>Типичная заявка: «на прод с ноутбука не пускает, только через bastion, я скопировал свой <code>id_ed25519</code> на прыжок, теперь захожу в два ssh». Вариант «культурнее»: «включили <code>ForwardAgent</code>, чтобы не вводить пароль от ключа дважды, и Ansible сам прыгает». Через месяц на джампе кто-то с root снимает сокет агента и ходит на прод вашим ключом. Ключ при этом «не крали» — его и не надо красть, агент сам подписывает.</p>
<p>Я не таскаю закрытый ключ на промежуточную машину. Прыжок для меня — дырка в TCP, не второй дом для <code>ssh-agent</code>. Ниже — чем <code>ProxyJump</code> отличается от проброса агента, когда агент всё-таки оставляю, и чем проверяю, что ключ с ноутбука никуда не уехал.</p>
<p>Хосты в примерах вымышленные: <code>jump.example.net</code> (документационный <code>203.0.113.40</code>), внутренний <code>prod-web-01.internal</code>, пользователь <code>ops</code>. Свои имена и ключи подставляйте, чужие из чатов не копируйте.</p>
<h5>Две привычки, которые путают с «удобным прыжком»</h5>
<p>Первая: положить тот же закрытый ключ на bastion. «Ну я же сам туда захожу, чего его беречь». Bastion — это машина, на которую смотрит интернет. На ней чаще стоят общие учётки, старый sudo, чужой tmux. Если ключ от прода лежит файлом в <code>~/.ssh</code> на такой коробке, компрометация прыжка равна компрометации всего, куда этот ключ пускают. Пароль на ключ тут слабое утешение: его снимут вместе с файлом, из памяти агента или из вашей же сессии.</p>
<p>Вторая: <code>ForwardAgent yes</code> глобально в <code>~/.ssh/config</code> или в системном <code>ssh_config</code>. Так делают, чтобы со прыжка открыть следующий ssh «как с ноутбука». Удобно. И ровно поэтому опасно. На время сессии на джампе появляется unix-сокет агента. Любой, кто может читать этот сокет — root, ваша скомпрометированная учётка, чужой процесс под тем же uid — может пользоваться ключами, пока вы онлайн. Ключ с диска не копировали. Подпись от вашего агента уже достаточно.</p>
<p>Есть ещё гибрид: ключ на ноутбуке, а на прыжок его один раз залили «на всякий случай» и забыли. Потом ротировали ключ дома, на джампе старый остался. Я такие находки встречаю чаще, чем хотелось бы: <code>ls -l ~/.ssh</code> на bastion и два <code>id_*</code>, один из которых уже «не наш».</p>
<h5>Что именно делает агент, когда его пробросили</h5>
<p><code>ssh-agent</code> на ноутбуке — нормальная вещь. Он держит расшифрованные ключи в памяти, чтобы не тыкать passphrase на каждый <code>git fetch</code>. Плохо не агент. Плохо отдавать его сокет чужой машине.</p>
<p>При <code>ForwardAgent yes</code> клиент поднимает на удалённой стороне канал и выставляет <code>SSH_AUTH_SOCK</code>. Дальше <code>ssh prod-web-01</code>, запущенный уже <em>на прыжке</em>, ходит в этот сокет. Подпись делает ваш ноутбук. Выглядит безопасно: «ключ же дома». Но решение «подписать вот этот challenge» принимает процесс на bastion. Если bastion врет, вы подпишете не то соединение, о котором думаете.</p>
<p>Практическая картинка, которую я показываю заказчику:</p>
<ul>
<li>Вы в сессии на <code>jump.example.net</code>.</li>
<li><code>echo $SSH_AUTH_SOCK</code> даёт путь вроде <code>/tmp/ssh-XXXX/agent.1234</code>.</li>
<li>С правами на этот сокет можно сделать <code>ssh-add -L</code> и увидеть ваши ключи.</li>
<li>Дальше — обычный <code>ssh ops@prod-web-01.internal</code>. Прод видит ваш ключ. В authorized_keys это тот же самый ключ, которым вы заходите «сами».</li>
</ul>
<p>Root на джампе для этого не всегда нужен. Достаточно вашей же утёкшей сессии, crон-скрипта под тем же uid или «временного» бинарника в <code>$PATH</code>. Поэтому фраза «ну у нас же на bastion только свои» меня не греет: свои тоже ошибаются, а агент не спрашивает, кто именно попросил подпись.</p>
<p>Отдельно: <code>ssh-add -c</code> на ноутбуке чуть лучше, чем голый агент. Перед каждой подписью всплывает confirm. Это не броня, но если сокет увели тихо, вы хотя бы увидите лишний запрос в момент, когда сами ничего не открывали.</p>
<h5>ProxyJump: прыжок без вашего ключа на той стороне</h5>
<p><code>ProxyJump</code> (ключ <code>-J</code>) делает другое. Клиент на ноутбуке открывает ssh на bastion и просит его прокинуть TCP до цели. Аутентификация на <code>prod-web-01</code> идёт с ноутбука, напрямую в этом тоннеле. На прыжке нет вашего закрытого ключа, нет сокета агента, нет второго <code>ssh</code> от вашего имени.</p>
<p>Грубо: bastion видит, что с вашего IP пришёл ssh и что вы попросили соединиться с <code>prod-web-01:22</code>. Bastion не видит, каким ключом вы представились проду, и не может этот ключ переиспользовать через минуту после того, как вы отключились.</p>
<p>Минимальный <code>~/.ssh/config</code>, с которого я начинаю:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">Host jump
    HostName jump.example.net
    User ops
    IdentityFile ~/.ssh/id_ed25519_jump
    IdentitiesOnly yes
    ForwardAgent no

Host prod-web-01
    HostName prod-web-01.internal
    User ops
    ProxyJump jump
    IdentityFile ~/.ssh/id_ed25519_prod
    IdentitiesOnly yes
    ForwardAgent no</pre>
<p>Два ключа — не паранойя. Ключ на интернет-джамп я считаю расходным: его проще отозвать. Ключ на прод на прыжок не кладу и в агент для сессии на jump не пихаю, если можно не пихать. <code>IdentitiesOnly yes</code> обязателен: иначе клиент переберёт все ключи из агента, и вы сами не вспомните, чем открыли хост. Плюс лишние попытки ключей иногда заканчиваются <code>Too many authentication failures</code>.</p>
<p>Одноразово без конфига:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh -J ops@jump.example.net ops@prod-web-01.internal</pre>
<p>Два и больше прыжка — список через запятую:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh -J jump.example.net,dmz-gw.example.net ops@prod-web-01.internal</pre>
<p>В конфиге то же самое: <code>ProxyJump jump,dmz-gw</code>. Старый <code>ProxyCommand ssh -W %h:%p jump</code> я оставляю только там, где клиент древний и <code>ProxyJump</code> ещё нет. Смысл тот же: проброс TCP, не проброс агента.</p>
<h5>Как я проверяю, что агент не уехал</h5>
<p>После захода на цель смотрю три вещи, не «ну вроде работает».</p>
<p>На ноутбуке, до входа:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh -G prod-web-01 | egrep -i 'proxyjump|proxycommand|forwardagent|identityfile|identitiesonly'</pre>
<p>Хочу увидеть свой <code>ProxyJump</code>, <code>forwardagent no</code>, нужные <code>IdentityFile</code> и <code>identitiesonly yes</code>. Если <code>forwardagent yes</code> приехало из <code>/etc/ssh/ssh_config</code> или из <code>Host *</code> в конце файла — чиню там, а не добавляю ещё один <code>Host</code> выше, который потом перебьют.</p>
<p>Вход с отладкой:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh -v prod-web-01 2&gt;&amp;1 | egrep -i 'proxy|jump|forwarding|agent|Offering|Authentication succeeded'</pre>
<p>Ищу, что клиент идёт через jump и что нет строк про agent forwarding. «Offering public key» должно относиться к ключу прода, не к ключу джампа. Если клиент предлагает пять ключей подряд — <code>IdentitiesOnly</code> не сработал.</p>
<p>На самой цели и на прыжке, уже внутри сессии:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">echo "SSH_AUTH_SOCK=${SSH_AUTH_SOCK:-}"
ssh-add -l</pre>
<p>На <code>prod-web-01</code> при чистом ProxyJump сокет агента обычно пустой, <code>ssh-add -l</code> отвечает, что агента нет. На прыжке при обычном заходе на сам jump — то же самое, если я не просил проброс. Если сокет вдруг есть, а я его не включал, ищу кто: <code>Host *</code>, Include-info в конфиге, обёртка вроде <code>assh</code>/<code>sshpass</code>, корпоративный «улучшатель» ssh.</p>
<p>Ещё одна проверка, которую люди пропускают: ключи на диске прыжка.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ls -l ~/.ssh
find ~/.ssh -type f \( -name 'id_*' -o -name '*.pem' \) ! -name '*.pub'</pre>
<p>Закрытых ключей там быть не должно. Если есть — снимаю с машины, смотрю <code>authorized_keys</code> на проде, не торчит ли тот же pubkey, и ротирую. Pubkey на прыжке (чтобы вы сами на него заходили) — нормально. Приватник от прода — нет.</p>
<h5>Когда агент всё-таки нужен — и как его не размазать</h5>
<p>Иногда без проброса не обойтись. Типичные случаи, которые я оставляю точечно:</p>
<ul>
<li>С удалённой машины нужно сходить в git по SSH вашим ключом, а ключ на эту машину класть нельзя. Тогда агент — меньшее зло, чем файл в <code>~/.ssh</code>.</li>
<li>Старый сценарий, который сам делает <code>ssh</code> со прыжка на пачку хостов и не умеет <code>ProxyJump</code>. Это чинят, но не в пять минут посреди инцидента.</li>
<li>Промежуточный хост должен подписать что-то ещё вашим ключом (редко, и я сначала пытаюсь перенести это действие на ноутбук).</li>
</ul>
<p>Правило одно: агент включаю на конкретный <code>Host</code>, не на <code>*</code>. И только на время задачи.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">Host git-build.internal
    HostName git-build.internal
    User ops
    ProxyJump jump
    IdentityFile ~/.ssh/id_ed25519_prod
    IdentitiesOnly yes
    ForwardAgent yes</pre>
<p>На ноутбуке ключ в агент кладу с подтверждением:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ssh-add -c ~/.ssh/id_ed25519_prod</pre>
<p>Сессию короче держу. После работы <code>ssh-add -d</code> этот ключ из агента. На прыжке в <code>sshd_config</code> я по возможности ставлю <code>AllowAgentForwarding no</code> — тогда даже ошибочный <code>ForwardAgent yes</code> у клиента не поднимет сокет. Если агент нужен на одну машину за прыжком, форвардинг агента разрешаю только там, не на bastion.</p>
<p>Ansible отдельным абзацем. Плейбук, который заходит на прод через bastion, не обязан пробрасывать агент. В инвентаре достаточно:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">ansible_ssh_common_args: '-o ProxyJump=ops@jump.example.net'</pre>
<p>или <code>ansible_ssh_extra_args</code> с тем же. Ключ остаётся на контрольной машине. Если вам «без <code>ForwardAgent</code> плейбук не едет», чаще виноват не Ansible, а то, что роли сами делают <code>ssh</code>/<code>rsync</code>/<code>git</code> уже на целевом хосте вашим ключом. Это другая задача: либо ключ деплоя на целевой машине, либо отдельный deploy key в git, либо вынести шаг на ноутбук.</p>
<h5>Что должно быть на самом прыжке</h5>
<p>Bastion я собираю скучно. Чем меньше на нём живёт, тем меньше соблазна «раз уж зашли, давайте тут ключи».</p>
<ul>
<li>Отдельный ключ только для входа на jump. Этот ключ не пускает на прод.</li>
<li><code>PermitRootLogin no</code>, вход по паролю выключен, по возможности отдельный порт или хотя бы geo/fail2ban — как принято у заказчика, не догма.</li>
<li><code>AllowTcpForwarding yes</code> (иначе ProxyJump не взлетит), <code>AllowAgentForwarding no</code>, <code>X11Forwarding no</code>.</li>
<li>Если sshd умеет <code>PermitOpen</code> — ограничиваю, куда вообще можно прыгать: только рабочие подсети, не «любой порт мира через нас».</li>
<li>Нет общих <code>ops</code> с одним ключом на пятерых. Если ключ общий, вы не отзовёте одного человека.</li>
<li>На диске нет <code>id_*</code> без <code>.pub</code>. Нет <code>authorized_keys</code> с ключами прода «для удобства».</li>
</ul>
<p>Кусок <code>sshd_config</code> на прыжке, от которого я отталкиваюсь:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">AllowAgentForwarding no
AllowTcpForwarding yes
X11Forwarding no
PermitTunnel no
GatewayPorts no
# если политика позволяет — режем направления:
# PermitOpen prod-web-01.internal:22 prod-db-01.internal:22</pre>
<p><code>PermitOpen</code> легко забыть и потом час ловить <code>administratively prohibited</code>. Сначала проверяю, что ProxyJump жив без него, потом сужаю. Логи sshd на прыжке смотрю не «красиво ли», а кто и куда просил forward: если из сессии <code>ops</code> вдруг полезли на адрес, которого в заявке не было, это уже не удобство, а инцидент.</p>
<h5>Мелочи, из-за которых «прыжок не работает»</h5>
<p>Частая жалоба: «с <code>-J</code> меня не пускает, а два ручных ssh работают». Почти всегда одно из трёх.</p>
<p>Первое — разные пользователи. На jump вы <code>ops</code>, на проде <code>deploy</code>, а в команде указали одного. В конфиге это два <code>User</code>. В <code>-J</code> — явно <code>ops@jump</code> и <code>deploy@prod</code>.</p>
<p>Второе — DNS. Ноутбук не резолвит <code>prod-web-01.internal</code>, а bastion резолвит. <code>ProxyJump</code> соединяет туда, куда сказал <em>клиент</em>. Если клиент не знает имя, пишу в <code>HostName</code> адрес, который виден с прыжка, либо завожу имя в локальный <code>/etc/hosts</code> только как метку. Обратная ловушка: в <code>HostName</code> поставили внешний IP прода, которого с прыжка нет — ручной второй ssh с jump шёл на внутреннее имя, а ProxyJump пытается в белый адрес.</p>
<p>Третье — ключи и агент. Клиент предлагает ключ джампа на прод, прод отвечает «не тот» несколько раз и отваливается. Лечится <code>IdentitiesOnly</code> и отдельным <code>IdentityFile</code>. Если ключ в агенте один, а файлов много, агент всё равно может предложить не то: поэтому для прод-хостов я указываю файл явно.</p>
<p>Четвёртое, реже: <code>ControlMaster</code>. Мультиплекс на jump удобен, но старый сокет иногда держит чужие опции. Если после правки конфига поведение «как раньше», сношу сокеты <code>~/.ssh/cm-*</code> и проверяю ещё раз.</p>
<h5>Что я отказываюсь считать решением</h5>
<p>Не считаю решением скопировать закрытый ключ на bastion «только на время работ». Файл переживает работы. Его найдёт следующий, кто зайдёт под той же учёткой.</p>
<p>Не считаю решением глобальный <code>ForwardAgent yes</code> «потому что так в wiki». Wiki часто писали, когда <code>ProxyJump</code> ещё не было в клиенте по умолчанию. Сейчас клиент это умеет давно.</p>
<p>Не считаю решением один ключ на всё: ноутбук, jump, прод, git, CI. Отзыв после утечки тогда равен «перевыпустить жизнь». Два-три ключа с понятной ролью дешевле нервов.</p>
<p>Не считаю решением «у нас на джампе свои люди, нам не надо». Свои люди оставляют tmux, ставят тестировать бинарник, дают sudo на час. Агент в этот час работает на всех.</p>
<p>Считаю решением короткую схему, которую можно проговорить вслух: ключ прода живёт на ноутбуке, прыжок только прокидывает TCP, агент выключен, кроме одной явно названной машины, и на диске bastion нет ни одного <code>id_*</code> без суффикса <code>.pub</code>. Это скучно. Зато когда bastion однажды вскроют — а интернет-машину вскрывают — прод не откроется тем же ключом из чужой сессии.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://remadmin.com/blog/linux/ssh-proxyjump-vmesto-probrosa-kljucha-na-server-kogda-bezopasnee-agent-forwarding/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>