SSH には独立した stdin、stdout、stderr、tty があります。

SSH には独立した stdin、stdout、stderr、tty があります。

質問

次のコマンドを検討してください。

<binary_input ssh user@server 'sudo tool' >binary_output 2>error.log

ここでtoolランダムなのは、上記の操作を可能にするsshラッパーまたは一部のラッパーです。ssh-like-contraption一般的な単語はssh機能しません。

ここで試しましたが、sudoちょうどそれでした。はいtty コマンドが必要です。 sudo.


研究:なぜ

通常、ssh次の理由で機能しません。

  • sudoパスワードを要求するにはttyが必要です(それとも単に仕事に行きます。)だから私は必要ですssh -t;実際にこの場合は が必要ですssh -tt
  • 一方、パスワードを読むことがssh -ttできます。私はローカルttyを介してパスワードを提供したいと思います。パスワードなしで動作するように設定した場合、またはパスワードを挿入した場合でも、リモートttyから出力を読み込み、次に書き込みます。sudobinary_inputsudobinary_inputssh -ttsudotoolそしてリモートttyにエラーとプロンプトを送信します。ローカルで出力とエラー/プロンプトを区別することはできませんが、すべてのストリームはリモートttyによって処理され、データが破損します(次の例に示すように)私の答え、「一部の実践」を参照)。

研究:有効な命令との比較

  • このローカルコマンドは参照点です。いくつかのバイナリデータが正常に処理されたとします。

    <binary_input tool >binary_output
    
  • toolサーバーで実行する必要がある場合は、これを行うことができます。sshパスワードを聞いても、次のように動作します。

    <binary_input ssh user@server tool >binary_output
    

    この場合、sshバイナリデータに透過的です。

  • 同様に、ローカルsudoも透明にすることができます。次のコマンドは、sudoパスワードを要求してもデータを破壊しません。

    <binary_input sudo tool >binary_output
    
  • toolしかし、サーバーで実行するのはsudo面倒な作業です。

    <binary_input ssh user@server 'sudo tool' >binary_output
    

    この構成sshsudo 一緒に通常、透明性は不可能です。これを透明にする方法を見つけることがこの質問の中心です。


研究:同様の質問

いくつかの同様の質問を見つけました。


私の明確な質問

次のコマンドで:

<binary_input ssh user@server 'requires-tty' >binary_output 2>error.log

requires-ttyttyが必要ですが、stdinからstdoutまでのバイナリデータを処理するコードのプレースホルダです。必要なようですssh -tt。それ以外の場合はrequires-tty動作しません。同時に使用できません。ssh -ttそうしないと、バイナリデータが破損します。この問題をどのように便利に解決できますか?

requires-tty可能sudo …ですが、具体的に説明したくありませんsudo

理想的な(?)ソリューションは、ssh上記の呼び出しに代わってうまく機能するスクリプト/ツールになりそうです。リモートのstdin、stdout、およびstderrをそれぞれローカル対応エントリに関連付ける必要があります(?)。そしてリモートttyをローカルttyに。

可能であれば、サーバー側コンパニオンプログラムを必要としないクライアント側ソリューションを好みます。

ベストアンサー1

スクリプト

私はこの質問の作成者であり、これが問題を解決するスクリプトを作成しようとしています。このスクリプトはクライアント側で動作するように設計されており、ssh問題のあるコマンドを置き換えます。それ実験的。私はそれを呼ぶsshe。スクリプトは次のとおりです。

#!/bin/sh -

# the name of the script
me="${0##*/}"

# error handling functions
scream() { printf '%s\n' >&2 "$1"; }
die()    { scream "$2"; exit "$1"; }

# initialization of variables
redir0=''
redir1=''
redir2=''
tty="/dev/$(ps -p "$$" -o tty=)"

# edge cases
[ "$tty" = '/dev/?' ] && { scream "$me: no tty detected, falling back to regular ssh"
   exec ssh "$@"; }
[ "$#" -lt 2 ] && die 1 "usage: $me [options] [user@]hostname command"

# see what needs to be redirected
exec 7>&1
if [ "$(<&0 tty 2>/dev/null)" != "$tty" ]; then redir0=y; fi
if [ "$(<&7 tty 2>/dev/null)" != "$tty" ]; then redir1=y; fi
if [ "$(<&2 tty 2>/dev/null)" != "$tty" ]; then redir2=y; fi
exec 7>&-

# edge case
[ "$redir0$redir1$redir2" ] || { scream "$me: no redirection detected, falling back to ssh -t"
   exec ssh -t "$@"; }

# command line parsing, extract two last arguments: ... host command
z="$#"
n="$z"
for arg do
   if [ "$n" -eq "$z" ]; then
      set --
   fi
   case "$n" in
      1) command="$arg"
   ;;
      2) host="$arg"
   ;;
      *)
      set -- "$@" "$arg"
   esac
   n="$(($n - 1))"
done

# prepare to clean on exit
trap 'status="$?"; rm -r "$tmpd" 2>/dev/null; trap - EXIT; exit "$status"' EXIT HUP INT QUIT PIPE TERM

# temporary directory and socket
tmpd="$(mktemp -d)"
[ "$?" -eq 0 ] || exit 1
sock="$tmpd/sock"

# main pipe: ssh master connection -> background cat
(
[ "$redir0" ] || exec 0</dev/null
# ssh master connection, it will report the remote PID of the remote shell via its stdout
ssh -M -S "$sock" "$@" -T "$host" '</dev/null echo "$$"; exec sleep 2147483647'
) | {

# read the remote PID
IFS= read -r rpid || exit 1

# background process to pass data
exec 6<&0
cat <&6 2>/dev/null &

# move original descriptors out of the way
exec </dev/tty >/dev/tty 6>&-

# prepare remote redirections
if [ "$redir0" ]; then redir0="<&6";  fi
if [ "$redir1" ]; then redir1=">&7";  fi
if [ "$redir2" ]; then redir2="2>&8"; fi

# ssh to run the command, with remote tty
ssh -S "$sock" -t "$host" "
 trap 'status=\"\$?\"; kill $rpid 2>/dev/null; trap - EXIT; exit \"\$status\"' EXIT HUP INT QUIT PIPE TERM
 exec 6</proc/$rpid/fd/0 7>/proc/$rpid/fd/1 8>/proc/$rpid/fd/2 9>/dev/tty $redir0 $redir1 $redir2 || exit 3;
 $command"
}


一般免責事項

  • このスクリプトはさまざまな状況でうまく機能します。私いいえランダムに失敗するという意味です。ランダムに失敗しません。私いいえこれは、特定のデータを処理できないことを意味します。任意のデータを処理します。

    「動作しない」場合は、ローカルttyとの相互作用によって発生します。 tty(任意のバイナリデータを含む)が含まれていないチャンネルを流れるデータは常に大丈夫です。問題を理解して回避する必要があることを確認するには、この回答の残りの部分、特に「障害と警告」のセクションを読んでください。

  • 次のコマンド:

    <binary_input sshe user@server 'sudo tool' >binary_output 2>error.log
    

    この問題は回避されます。技術要件(以下の「要件」を参照)を満たしている場合は、正しく機能します。

  • スクリプトは実験的であり、trap終了状態を維持し、終了時に正気な(?)方法でクリーンアップするためにsを設定しようとしました。私が成功したかどうかはわかりません。

  • スクリプトは決して完璧ではありません。コンセプト証明だと思います。


使用法

sshe次のように使用してください。

sshe … [user@]hostname command

つまり、実行ファイルがある場合、ここにも(または)sshを配置する必要はありません。このスクリプトはリモート側でttyを使用すると仮定します(そうでない場合は単にttyを使用)。スクリプト-t-tt-Tssh予想するローカルのstdin、stdout、およびstderrの少なくとも1つは、ローカルのttyからリダイレクトする必要があります。ssh -tすべてがローカルttyに接続されている場合、スクリプトは置き換えられます。

重要もの:

  • commandサーバーで実行するシェルコードです。それ〜しなければならないは単一のパラメータであり、最後のパラメータですsshe。省略できません。
  • hostnameまたは、user@hostname最後の2番目のパラメータでなければなりません。省略できません。

内部的に、スクリプトはcommand前にいくつかのコードを追加する方法を知る必要があります。[user@]hostname2回使用されたので知っておく必要があります。スクリプトはそれぞれ最後の引数と最後の2番目の引数のみを選択するため、上記の制限が適用されます。

単にに置き換えるだけで、すべての有効な呼び出しを呼び出しsshに変換できるわけではありません。ssheしかし、私はコードを実行するすべての有効な呼び出し(対話型シェルを生成するのとは対照的)が有効なコマンドに再配置できると思います。例:sshsshesshsshe

ssh user@server -p 1234 echo foo

次のように並べ替える必要があります。

sshe -p 1234 user@server 'echo foo'

sshe(本当に必要ない限りこれケースこれは正しい構文の例にすぎません。 scriptを使用した場合、sshe user@server -p 1234 echo fooそのスクリプトは以前と同じ引数を解析しないため、echoサーバーとコマンドとして機能します。foossh

ここにいくつかの例があります。


要件、移植性の問題

ローカル要件(sshe作業場所):

  • /dev/$(ps -p "$$" -o tty=)制御端末の「実名」と推定される。比較するこの問題
  • mktemp -d
  • sshこのスクリプトはマスターとスレーブの接続をサポート-Mします。-S

リモート要件(サーバー上):

  • SSHサーバーはマスター - スレーブ接続を処理できます。
  • /proc擬似ファイルシステム
  • /proc/nnnn/fd/N同じユーザーに属する他のプロセスを使用できます。
  • POSIX互換エンクロージャ。
  • 自動起動スクリプト(比較echoSCPがアイテムに機能しない.bashrcsshe状況に似ています)。

テスト中に、Kubuntu(18.04.5 LTS)のさまざまなDebianまたはDebian派生サーバーに正常に接続しました。鉱山sshsshdOpenSSHから。


仕事

sshessh(またはで置き換えることを決定しない限りssh -tssh2回実行します。

  1. ssh -M … -T …これはデフォルトの接続であり、リモート側にttyを割り当てません。そこで実行されるシェルコードは、execstdoutとsを介して長期実行(約68年)にsleepPIDを報​​告します。プロセスの標準ファイル記述子は、他のプロセスで使用されます。

    マスターから報告されたPIDはsshによって取得されますread。その後、マスターの標準出力はそれを(ローカル)標準出力に渡す目的でのみsshバックグラウンドに移動します。catsshe

  2. 後でssh … -t …リモート側にttyを割り当てるスレーブ接続が提供されます。リモートPIDがマスター接続で既に認識された後、リモート側で別々のstdin、stdout、stderr(マスター接続を介して)、およびtty(スレーブ接続を介して)を使用できるssheように提供されるコードをリダイレクトします。スレーブは、生のstdinまたはstdoutを使用せずにローカルを使用します。commandsshsshsshsshe/dev/tty

アイデアは似ています。この回答(すでに質問につながっています)はい。リンクされた回答のコードは、追加の記述子を提供するためにssh(暗黙的に)2回実行されます。ssh -T私のスクリプトが実行さssh -Tれ、ssh -t標準記述子とttyを提供します。そしてマスタースレーブ機能を使用するので、ssh認証(パスワード要求など)を実行します。

ローカル stdin、stdout、stderr がローカル tty でない場合、データフローの方向は次のようになります。

  • ローカルのstdinはmasterに移動し、ssh他のローカルプロセスはスクリプトのstdinから読み込まれません。 (リモートで)読み取ることで、/proc/nnnn/fd/0リモートプロセスはローカル標準入力にアクセスできます。スレーブssh接続はリモート側のシェルに事前にリダイレクトされ、標準入力commandとして使用されます。/proc/nnnn/fd/0

  • 同様に、リモート側のシェルが/proc/nnnn/fd/1標準出力として使用されます。何が起こっても、地域の所有者が出てくるでしょうssh。これは、マスターがssh実行されている(リモート)シェルコードから正しいPIDを取得した後です。 PIDが消費され、その後のすべてのデータがreadバックグラウンドで元の標準出力に送信されます。sshecat

  • 同様に、リモート側のシェルは/proc/nnnn/fd/2stderrを使用します。このストリームはローカルマスターからsshstderrに直接送信されますsshe。スクリプトによって生成されたローカルプロセスによっては、スクリプトの標準エラーが標準エラーとして使用されるため、sshe … 2>error.logログにエラーメッセージも含まれます。特に期待されますShared connection to server closed.。これはssh -T … 2>error.log、ログがリモートコマンドとそれ自体からメッセージを収集する方法と似ていますsshsshe他のstdoutに関連付けられたチャネルを介してリモートコマンドからstderrを渡すバリアントを作成することが可能だと思います。sshこの場合、リモートstderrとローカルツールによって生成された診断メッセージを区別できます。しかし、スクリプトはそうしません。

  • ローカルttyはマスターssh(パスワードを要求する必要がある場合)に使用し、スレーブに使用できますssh。 (正直スクリプトで使用するローカルツールはlocalにアクセスできますが、/dev/tty使用しません。)スレーブデバイスは標準入力および標準出力ssh -tとして使用されます。/dev/ttyこれにより、/dev/tty他のリダイレクト(ssh -tリダイレクトなしで端末で実行)にもかかわらず、ローカルおよびリモートで接続できます。リモートプロセスから読み取った内容は、ローカルスレーブがローカルで読み取った内容を/dev/tty取得します。リモートプロセスで書き込み操作を実行すると、ローカルスレーブはローカルで書き込み操作を実行します。ssh/dev/tty/dev/ttyssh/dev/tty

ローカルstdin、stdout、またはstderrがローカルttyの場合、そのリモート側(スレーブcommandでリモートで実行中)はリダイレクトされず、リモートttyへの接続は維持されます。どちらにしても地元ターミナルに到着します。要点は、リモートttyを迂回してはならないということです。その理由はすぐに明らかになるでしょう。ssh/proc/nnnn/fd/N

sshe必ずしも機能する必要がないローカルとリモートのリダイレクトはほとんどありません。これは私の他の実験的なものによるものです。私はsshe私が覚えているよりも単独でより重要な場合に備えて追加のリダイレクトを維持することにしました。


障害物と考慮事項

全体の概念は思ったより簡単ではありません。 ttyはユーザーが入力した内容を処理できます(例:翻訳^Mする^J)と印刷される内容(たとえば、cat端末から* nix行の末尾にファイルを送信すると、各改行はキャリッジリターン+改行のように機能します)。移動するstty -a多くの設定を参照してください。

これがランダムなバイナリデータを処理するときにttyが必要ない理由です。そして対話するときに本当に必要です。

プロセスは必要に応じてttyを設定できます。バラより生のそして料理

ssh何とかサーバーにttyを割り当てると、そこのプロセスはそれを自分のttyとして扱います。 ttyを設定する必要がある場合は、サーバーに表示されるttyを設定します。ローカルttyを直接設定する方法はありませんssh。すべての(?)「クッキング」はリモートttyで行われ、sshローカルttyは邪魔にならないように設定されています。

ssheこれが最終的にローカルttyに接続する必要があるリモート記述子をリダイレクトするときにリモートttyをバイパスしないでください。リモートttyがバイパスされると、ストリームを「クッキング」するオブジェクトはありません。 stdin、stdout、または stderr をssheローカルの tty にリンクすると、そのエントリが「クッキング」されることを示します。これは、すべての標準ストリームがローカルttyによって「クッキング」される対話型シェルで実行されるのとsshe似ています(これは、ローカル端末を「raw」モードに設定しないことに注意してください)。ssh … commandsshssh -T

だからsshe明らかに「料理」したいことを「料理」してください。問題は、次のような場合に発生します。

sshe … command | whatever

データは(予想どおり)「クッキング」されず、リモートからローカルcommandに流れますが、出力はローカルで「クッキング」されません。ローカルttyは「料理」に再構成できますが、ローカルttyはそのttyで印刷されている場合は「料理」にしてはいけません(つまり、設定に応じて「料理」にすることも、そうでない場合もあるリモートttyに印刷する必要があります)。 。whateverssh … command | whateverwhateversshecommand

sshe問題を解決しようとしないでください。デフォルトでは、出力が端末以外の場所(通常のファイルやブロックデバイスなど)で終わる状況をサポートするように設計されています。この問題では、次のコードが優れています。

sshe … command | whatever >some_file

stderrはwhatever「料理」ではありません。予想される診断メッセージバラより奇妙な。ファイル(または「クッキング」する他のローカルtty)にリダイレクトできます。

入力側では状況がさらに悪化します。他のローカルプロセスがローカルttyからデータを読み取ろうとすると、元のデータを読み取るだけでなくsshe入力のために競合します。これは、同じ端末からデータを読み取る両方のプロセスの一般的な問題です。

sshe要約すると、「生」出力を許可できない限り、ローカルttyから印刷以外のツールがssheローカルttyに印刷することを許可しないようにローカルコマンド(パイプ)を作成します。

私はssheバイナリデータを転送または処理する能力を開発しました。私の場合、ローカルttyからデータを読み書きする必要はほとんどありません。ローカルツールの診断メッセージが十分に「洗練されていない」まま生きることができます。そのsshe代わりに、ローカルのようにリモコンを使用できます。sudo


はい

  • 読み書きするには、リモートブロックデバイスへのアクセスが必要ですsudo

    • 読む:

      sshe user@server 'sudo cat /dev/sdx1' >local_file
      # or
      sshe user@server 'sudo pv  /dev/sdx1' >local_file
      
    • 執筆:

      <local_file sshe user@server 'sudo tee /dev/sdx1 >/dev/null'
      

      私のテストでは、ローカルはローカルpvttyが「生」であるという事実を気にしないようです。あるいは、むしろそれが課す構成が課す構成とsshe矛盾することはなく、どのツールが最初にローカルttyを構成するかは重要ではありません。だからこれはうまくいくようです:

      pv local_file | sshe user@server 'sudo tee /dev/sdx1 >/dev/null'
      

      設定がpv中断されると、ssheリモコンにパスワードを入力できないことがありますsudo。設定がsshe妨げpvられると、pv端末に印刷された内容が破損しているように見えることがあります。これらの仮定の下でも、コンテンツはリモートlocal_fileでそのまま送信されます/dev/sdx1

  • ローカルsudoとリモートがsudo付属しています。

    ローカルsudoでパスワードを求める場合は、ローカルと同時にローカルttyで読むので良い考えではありませんsudo … | sshe … 'sudo …'。完全ローカルは、ロック機構が実装され、両方のローカルが同時に同じ端末と対話しないために機能します。ローカルとリモートが混在している場合は機能しません。sshe … 'sudo …' | sudo …sudosshesudo … | sudo …sudosudosudo

    あなたの地域を願ってsudo タイムアウトを許可。そのsudo -v場合は、事前に電話して(必要な場合)、中断することなくローカルパスワードを入力してから、次のものをパイプしてください。

    • リモートデバイスからローカルデバイスにコピー:

      sudo -v   # input local password if needed
      sshe user@server 'sudo cat /dev/sdx1' | sudo tee /dev/sdy1 >/dev/null
      
    • ローカルデバイスからリモートデバイスにコピー:

      sudo -v   # input local password if needed
      sudo cat /dev/sdy1 | sshe user@server 'sudo tee /dev/sdx1 >/dev/null'
      
  • 質問が求めたのがまさにそれです。

    • そしてsudo

      <binary_input sshe user@server 'sudo tool' >binary_output 2>error.log
      
    • またはより一般的に:

      <binary_input sshe user@server 'requires-tty' >binary_output 2>error.log
      

最終メモ

私はstdinからstdinへ、stdoutからstdoutへ、stderrからstderrへのトンネリングを考えました。そして /dev/ttyする/dev/ttyのは些細なことだ。私はなぜこれを行うためのsshオプション(と同様)がないのかと思いました。-t今、私はそれがそれほど単純ではないことを知っています。おそらくまだ何かが足りません。

おすすめ記事