ssh -Xした先でsudoを使う

2022年11月14日

GUIなLinuxからsshを使って別のGUIなLinuxに入って作業するとき、たまにXの画面が欲しいときがあります。そういうときssh -Xで繋ぐと手元の画面にリモートのGUIアプリを表示して操作することができます。便利な小技ですよね。

$ ssh -X user-name@remote-host
$ gedit

しかし、こうして繋いだ場合も、管理者権限が必要とかでsudoでGUIアプリを起動すると使えないというケースがよくあります。Xの認証の仕組みから使えないのですが、今回はsudoした先でGUIアプリを起動する方法を説明します。

ただし、本番運用の公開サーバーでX関係のライブラリなど必要のないパッケージを入れるのは個人的にはやめた方がいいと思います。あくまで複数のGUIを持つLinux開発マシンを渡り歩くときの小技です。また使い終わったらゴミを残さないようにしましょう。

目的

ssh -Xで接続したホストでsudoから起動したGUIアプリケーションを使用する。

LinuxのGUIについてのウンチク

まずは前提知識についてのおさらいです。知ってる人は読み飛ばして構いません。

Xサーバー

Linuxの画面は2022年11月現在主にXで表示されているのですが、これはXサーバーというサーバーが表示しているという意味です。アプリケーションはXクライアントとしてXサーバーに接続し、サーバーに画面を表示してもらい、UI操作を情報として受け取ります。ただし、この通信はアプリケーションとリンクされるToolKitライブラリが隠蔽しているので、アプリケーション開発ではあまり意識されません。しかしXはサーバーであり、ネットワーク越しに表示させることが可能な設計になっています。

※Ubuntu だといくつかのバージョンで(Xより新参の代替である)Waylandが使用されています。Xと互換性がないため、XWaylandというWaylandクライアントがXの機能を提供することで、その繋ぎとしての役割を果たしています。まだバタバタしてるようですが。。。

DISPLAY環境変数

例えば、GUIでログインすると、DISPLAY環境変数というのが設定されていることに気づいた人も多いと思います。

$ env | grep DISPLAY
DISPLAY=:0
$

DISPLAY環境変数は、「ホスト名:ディスプレイ番号(0から).スクリーン番号(0から)」を指定しています。

ホスト名は省略するとlocalhostであり、スクリーン番号も0になります。DISPLAY環境変数はつまるところ、起動するアプリケーションがGUIアプリで、Xクライアントだった場合に「どこに表示するか」を指定していたわけです。もし、localhostと違うホストだった場合は、実際にそのホストで動作しているXサーバーに向かって通信しに行くでしょう。TCP/IPのポートは6000+ディスプレイ番号が普通です。

なお、firewallが一般的でなかった時代からXサーバーは存在しているので、こういう仕様(実質サーバー側からクライアント側に繋ぎに行く仕様)ですが、現代ではこれらのポートが開いていることは稀です。例えばUbuntuではデフォルトではlistenすらしていません。

$ ss -ltn | grep 6000
$

またlocalhostではTCP/IPではなくunixドメインソケット(名前付きパイプのようなもの)を使用しています。

$ ss -lxn | grep X11
u_str  LISTEN 0 4096 @/tmp/.X11-unix/X0 31367 * 0                                    
u_str  LISTEN 0 4096 /tmp/.X11-unix/X0  31368 * 0                                    
$ 

※pathが@で始まるソケットは抽象ソケットというもので、普通は開くこともできません。というわけで、ここでは気にしません。

xhost/xauth

DISPLAY環境変数に指定されたXサーバーがあるからといって、誰でも使えてしまうと、ある人が使っているディスプレイに突然別の人が何か表示させてしまえます。それでは流石にまずいので、ここには認証の仕組みが入っています。それがxhostとxauthです。

xhostはIPとuserの組み合わせで許可/不許可を判断する仕組みで、xauthは自動生成された値を共有することで認証する仕組みです。xhostはもうほぼ使われないので、ここでは扱いません。

もう一方のxauthで管理している自動生成値は、Xサーバーに繫いだ後に表示される端末から、簡単に確認できます。

$ xauth list
ホスト名/unix:ディスプレイ番号  MIT-MAGIC-COOKIE-1  335f3fa9d78a1e9f6424bdbf44c4fb36
$

今のユーザーが保管している認証情報がリストで表示されており、左側がDISPLAY環境変数相当です。/unixはローカルホストのunixドメインソケットであることを示しています。真ん中のMIT-MAGIC-COOKIE-1が認証情報のタイプ(プロトコル)で、その右側の16進数列が認証情報です。リストというだけあり、複数のサーバーに対する認証情報を保管できます。$HOME/.Xauthorityがこのリストの実体です。

XクライアントがDISPLAY環境変数の指定するXサーバーに繋ぎに行く場合、この認証情報をXサーバー側の保管データと照合することになります。localhostのXサーバーならXセッション開始時、自動的にローカルのXサーバーの認証情報が追加されるため、照合は全て成功します。

実際にリモートのXサーバーに表示する

では実際にリモート接続してXの画面を出してみます。

ポートのlistenと開放

前述したように、最近のLinuxディストリビューションではセキュリティ考慮からXサーバーはTCP/IPのポートをlistenしていないので、リモート接続には準備が必要になります(ローカルのXサーバーはunixドメインソケットのみです)。実際にlistenポートを確認してみると、以下のように何も表示されません。

$ ss -ltn | grep 6000
$

Ubuntuでlistenさせるためには/etc/gdm3/custom.confを以下のように修正します。

...
[security]
DisallowTCP=false
...

そして直接/usr/bin/Xorgを以下のように書き換えます(-listen tcpの追加)。

exec /usr/bin/X -listen tcp "$@"

※なお、Ubuntu 20.04のXwaylandには-listen tcpが効かないバグがあるので(*)、Waylandを使用せず、Xorgを使う場合のみ参考にしてください。

(*) https://gitlab.freedesktop.org/xorg/xserver/-/issues/817

書き換えたあとは、以下でgdm(ログイン画面を表示してるやつ)を再起動することでXサーバーを再起動します。

$ sudo systemctl restart gdm3.service

再度listenポートを確認すると

$ ss -ltn | grep 6000
LISTEN 0      4096         0.0.0.0:6000      0.0.0.0:*
...
$

ポート6000でlistenしていることがわかります。これでリモートのクライアントから接続することが可能になりました。

(firewallを構築している場合は、6000を開けてください)

DISPLAY環境変数と認証情報の取得

ではまず表示したい側のXサーバーがある方のLinux(仮にhostnameをubuntu1とします)でDISPLAY環境変数と認証情報を表示してコピっておきます。

ubuntu1:$ env | grep DISPLAY
DISPLAY=:0
ubuntu1:$ xauth list
ubuntu1/unix:  MIT-MAGIC-COOKIE-1  e7fd4d5e065959c841a7a55182072204
ubuntu1:$

sshでリモートホストに接続

それではsshでubuntu1からリモートホストubuntu2に接続します。

ubuntu1:$ ssh ubuntu2
...
ubuntu2:$ 

コピっておいた情報を元に接続すべきXサーバーとその認証情報を設定します。

$ export DISPLAY=ubuntu1:0
$ xauth add ubuntu1:0 MIT-MAGIC-COOKIE-1  e7fd4d5e065959c841a7a55182072204
$

接続先とその認証情報も設定したので準備完了です。

リモートホストでGUIアプリを起動する

では、ubuntu2上でGUIのブラウザを立ち上げてみます。

$ gnome-www-browser
...

ubuntu1上のディスプレイ0にubuntu2上で動作しているブラウザが表示されます。これがXサーバーでのリモート画面表示です。

暗号化されないTCP/IPのポート6000通信ではありますが、ubuntu1のXサーバーと通信してubuntu1上の画面にubuntu2で動作中のアプリの画面を表示することができました。

後片付け

まずはブラウザを閉じてから、認証情報を削除します。

$ xauth remove ubuntu1:0
$

sshを抜けてから、ubuntu1の/etc/gdm3/custom.confと/usr/bin/Xorgを元に戻し、再度gdmを再起動してXサーバーを再起動します。これでTCP/IPのポート6000でのlistenはなくなりました。

sshのX11転送

ようやくssh -Xの説明ができます。

sshのポート転送

ご存知の方も多いと思いますが、sshはリモートホスト間の任意のポートを暗号化した通信で繋ぐことができます。

例えば、リモートホストubuntu2上のポートBでlistenしているとき(通常はローカルのNICのみでlisten)、それをubuntu1上のポートAとして利用することが出来るという仕組みです。これはsshがubuntu2上でクライアントとしてポート2に繋ぎ、それをsshでubuntu1に転送し、ubuntu1上のサーバーとしてポートAでlistenする仕組みです。転送データはsshと同じ仕組みで暗号化されるため、安全に繋ぐことが出来ます。

例えば、、、まずはubuntu2上でhttpサーバーを実行します(下の例だとポートは8000番になります)。

ubuntu2:$ python3 -m http.server

次に、ubuntu1上の別の端末からsshでポート転送します。

ubuntu1:$ ssh -L A:ubuntu1:B ubuntu2
...
ubuntu2:$ 

画面上は普通にsshで繋がっているだけのように見えますが、同時にポート転送もしています。

例えばubuntu1上のさらに別の端末から以下のようにすると、localhost=ubuntu1にhttpアクセスしてるはずなのに

ubuntu1:$ wget 'http://localhost:A/'

sshでポート転送されてるubuntu2上のhttpサーバーにアクセス出来てしまいます。しかもubuntu1<->ubuntu2間の通信は強力なsshで暗号化された状態です。httpsなどが気軽に使えないとき、このようにsshで転送すると、インターネット間を安全に繫ぐことが出来ます。

なお、sshで繫いだシェルを終了すると、ポート転送も自動的に終了します。接続方法やバックグラウンド実行など、いろいろなオプションもあるので、興味のある方は自分で調べてみてください。

sshのunixドメインソケット転送とX11転送

ここまででsshを使ってTCP/IP通信のポート転送が可能なことが分かりました。ただsshはunixドメインソケットも転送可能です。つまり技術的にはunixドメインソケットでlistenしているローカルXサーバーも同様にリモート転送可能というわけです。sshではX11転送という専用の機能まで用意しており、至ってシンプルに使えます。

ubuntu1:$ ssh -X ubuntu2
...
ubuntu2:$ gnome-www-browser
...

これだけです。unixドメインソケットの転送だけでなく、DISPLAY環境変数やxauthの処理も自動でやってくれるのでとても便利に使用できます。もちろん暗号化もされています。後始末も自動なので楽です。

なお、サーバー版などではデフォルトでsshdのX転送が許可されていないので、使用したい場合は自分で許可する必要があります。セキュリティリスクは主に実際のサーバーがXクライアントになり、あまり信用できないクライアント側のXサーバーを信用しないといけないことによるリスクです。

ssh -X でsudoを使用する

ようやく本題に辿り着きました。

便利なssh -Xも残念ながらsudoを使用するとうまく機能しません。

ubuntu1:$ ssh -X ubuntu2
...
ubuntu2:$ sudo gedit
X11 connection rejected because of wrong authentication.
X11 connection rejected because of wrong authentication.
Unable to init server: 接続できませんでした: 接続を拒否されました

(gedit:3220): Gtk-WARNING **: 20:24:00.255: cannot open display: localhost:10.0
ubuntu2:$ 

ここまで読んできた方はうすうすお気づきかもしれませんが、DISPLAYや認証情報などが設定されてないからかもしれません。

それでは確認してみましょう。

ubuntu2:$ env | grep DISPLAY
DISPLAY=localhost:10.0
ubuntu2:$ xauth list localhost:10.0
ubuntu2/unix:10  MIT-MAGIC-COOKIE-1  481f03e6c0c16ed38e49db7fd94cbcfc
ubuntu2:$ sudo -i
ubuntu2:# env | grep DISPLAY
DISPLAY=localhost:10.0
ubuntu2:# xauth list localhost:10.0
xauth:  file /root/.Xauthority does not exist
ubuntu2:# 

どうやら認証情報の設定だけがないようです。

ubuntu2:# touch ~/.Xauthority
ubuntu2:# xauth add localhost:10.0 MIT-MAGIC-COOKIE-1  481f03e6c0c16ed38e49db7fd94cbcfc

これでGUIもOKです。

ubuntu2:# gedit

終わったら忘れずに削除しておきましょう。

ubuntu2:# xauth remove localhost:10.0

長々と説明しましたが、詰まるところ、sudoを使う場合はxauthも設定してねってだけです。ただ、仕組みをある程度知らないと危険度が分からないと思い、ある程度書かせて頂きました。

まとめ

ssh -X でsudoを使う場合はxauthも設定する

そろそろ時代はWaylandかというときにXの昔話もどうかと思ったのですが、急にwiresharkでパケットキャプチャしたくなって調べたのでついでにまとめただけでした(最近はwiresharkグループ権限での使用は逆に推奨されてないらしい)。

未分類linux,ssh,ubuntu,X

Posted by first_user