Catalyst 9300のapp hostingでsshサーバーを構築してみる
MMA Advent Calendar 2024 9日目の記事です
はじめに
Cisco Catalyst 9300では,Dockerアプリケーションホスティングが利用できます.
これを利用することで,L3スイッチなのにLinuxライクに様々なアプリケーションを動かすことができます.
というわけで,今回はsshサーバーを構築してみました.
今回の環境
インターフェース | サブネット | sshサーバーのIPアドレス | ゲートウェイ | |
---|---|---|---|---|
グローバル | Vlan10 | 10.0.0.0/24 | 10.0.0.2 | 10.0.0.254 |
ローカル | Vlan20 | 192.168.0.0/24 | 192.168.0.2 | 192.168.0.254 |
グローバルとローカルの,どちらからもアクセスできるようにします.
スイッチの準備
ioxの有効化
まず,ioxが有効になっていなければ,有効化します.
コンフィグレーションに下記を追加してください.
iox
AppGigabitEthernetの設定
続いて,app hosting用のインターフェースを設定します.
複数のvlanを扱いたいので,trunkにします.
interface AppGigabitEthernet1/0/1
switchport trunk allowed vlan 10,20
switchport mode trunk
app hostingの設定
app hostingでは,動かすコンテナごとにapp idを付与する必要があります.
今回はssh_minimal
にしました.
app-hosting appid ssh_minimal
まず,仮想NICの設定をして,IPアドレスを付与します.
app-hosting appid ssh_minimal
app-vnic AppGigabitEthernet trunk
vlan 10 guest-interface 1
guest-ipaddress 10.0.0.2 netmask 255.255.255.0
vlan 20 guest-interface 0
guest-ipaddress 192.168.0.2 netmask 255.255.255.0
app-default-gateway 10.0.0.254 guest-interface 1
name-server0 8.8.4.4
name-server1 8.8.8.8
dockerのコマンドオプションを指定します.
app-hosting appid ssh_minimal
app-resource docker
run-opts 1 --restart=unless-stopped
最後に,使用するリソース単位を設定します.
app-hosting appid ssh_minimal
app-resource profile custom
cpu-percent 20
memory 512
persist-disk 512
end
memory
とpersist-disk
の単位はMBです.
以上のコンフィグを適用して保存したら,準備完了です.
Docker コンテナのビルド
docker composeは使えません.Dockerのtarボールを直接スイッチに移す形になります.
そのため,やりたいことはすべてDockerfile
に書きます.
Dockerfile
app hostingではchrootができません.
そのため,sshd_config
のUsePrivilegeSeparation
はno
にしておく必要があります.
ただし,no
にした場合,sshdがrootで動いてしまい,セキュリティ的に大変脆弱です.
そのため,sshdを非特権ユーザーで動かすようにすることで,リスクの軽減を図ります.
したがって,今回は単一ユーザーだけがログインできるsshサーバーとなります.
非特権ユーザーで動かすためのsshd_config
は以下のようになります.
mma
を非特権ユーザーにした場合です.
Port 2222
HostKey /home/mma/.sshd/ssh_host_rsa_key
HostKey /home/mma/.sshd/ssh_host_ecdsa_key
HostKey /home/mma/.sshd/ssh_host_ed25519_key
PidFile /home/mma/.sshd/sshd.pid
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
AllowTcpForwarding yes
X11Forwarding no
PrintMotd no
AcceptEnv LANG LC_*
UsePrivilegeSeparation no
特権ポートが使えなくなるため,ポート番号は2222
に変えました.
また,パスワード認証も無効化しています.
Dockerfileは,ベースをUbuntu 24.04にした場合は以下の通りとなります.
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
openssh-server openssl sudo iputils-ping curl vim nano busybox
RUN busybox --install -s
COPY sshd_config /etc/ssh/sshd_config
RUN mkdir -p /run/sshd
ARG SSH_USER
RUN useradd -m ${SSH_USER} -s /bin/bash -G sudo
RUN \
echo "${SSH_USER}:$(openssl passwd -6 $(cat /run/secrets/ssh_password))" | chpasswd -e
USER ${SSH_USER}
RUN mkdir -p /home/${SSH_USER}/.sshd
RUN ssh-keygen -f /home/${SSH_USER}/.sshd/ssh_host_rsa_key -N '' -t rsa
RUN ssh-keygen -f /home/${SSH_USER}/.sshd/ssh_host_ecdsa_key -N '' -t ecdsa
RUN ssh-keygen -f /home/${SSH_USER}/.sshd/ssh_host_ed25519_key -N '' -t ed25519
RUN mkdir -p /home/${SSH_USER}/.ssh
COPY (ssh公開鍵のパス) /home/${SSH_USER}/.ssh/authorized_keys
RUN chmod -R 600 /home/${SSH_USER}/.ssh
ENTRYPOINT ["/usr/sbin/sshd", "-D"]
apt-getで使いたいものをあらかじめインストールしています.unminimizeをしてもいいとは思いますが,今回はしませんでした.
また,sudoコマンドを使うときのために,パスワードも書き込んでいます.
さらに,公開鍵認証をする必要があるため,そのクライアント証明書もコピーしています.
パスワードの設定の仕方は後述します.
ビルドコマンド
Dockerfileとsshd_config
,パスワードが書かれたテキストファイルが用意できたら,
以下のコマンドでビルドできます.
$ docker build --secret id=ssh_password,src=${SSH_PASSWORD_FILE} --build-arg SSH_USER=${SSH_USER} -t ssh-minimal .
tagはssh-minimal
としました.
変数SSH_USER
は,非特権ユーザーのユーザー名です.例えば,sshd_config
が先述と同一の場合,mma
となります.
変数SSH_PASSWORD_FILE
は,非特権ユーザーのパスワードが書かれたファイルのパスです.
tarボールの保存
ビルドしても,カレントディレクトリにイメージが保存されるわけではありません.
イメージを保存するために,下記のコマンドを実行します.
$ docker save -o ssh-minimal.tar ssh-minimal
$ ls -l ssh-minimal.tar
-rw------- 1 shiragi shiragi 317933568 Nov 19 09:19 ssh-minimal.tar
Makefile
ビルドとtarボールの保存をMakefileにすると以下のようになります.
include .env
ssh-minimal.tar: build
docker save -o ssh-minimal.tar ssh-minimal
.PHONY: build clean
build: Dockerfile sshd_config ${SSH_PASSWORD_FILE}
docker build --secret id=ssh_password,src=${SSH_PASSWORD_FILE} --build-arg SSH_USER=${SSH_USER} -t ssh-minimal .
clean:
docker rmi ssh-minimal
rm -f ssh-minimal.tar
Dockerアプリケーションの起動
tarボールをコピー
tarボールをスイッチのフラッシュメモリにコピーします.
scpやUSBメモリで移したりといろいろ方法はありますが,
例えばスイッチ上のコマンドラインから,sftpでコピーする場合は以下の感じになります.
Switch# copy sftp://(クライアントのユーザー)@(クライアントのIP)/(tarファイルのフルパス) flash:/ssh-minimal.tar
クライアントからscpで送信する場合は以下のコマンドです.
$ scp ssh-minimal.tar (スイッチの特権ユーザー)@(スイッチのIP):flash:/ssh-minimal.tar
しかし,私の環境のうち,2台中1台では,scpを有効にしているにもかかわらずConnection closed
と返されてコピーできない筐体がありました.ただし,sftpではできました.
tarボールからDockerイメージをインストール
スイッチ上で下記のコマンドを実行することで,コピーしたtarボールをインストールできます.
appidには,先述のコンフィグで指定したappidと一致させます.
Switch# app-hosting install appid ssh_minimal package flash:/ssh-minimal.tar
Dockerアプリの有効化
下記のコマンドでインストールしたDockerアプリを有効化できます.
このとき,コンフィグに問題があるとエラーが出ます.
Switch# app-hosting activate appid ssh_minimal
Dockerアプリの起動
無事,有効化できたら,下記のコマンドでスタートさせます.
Switch# app-hosting start appid ssh_minimal
sshの接続
起動できたら,ssh接続してみましょう.
$ ssh -p 2222 192.168.0.2
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
mma@26df14e31b73:~$
無事,接続できました.
試しにip a
コマンドを実行してみます.先述のDockerfileでbusyboxをインストールしているため,Ubuntu minimizedですがip
コマンドが使えます.
mma@26df14e31b73:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
3: ip_vti0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
61: sss@if62: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 52:54:99:99:00:00 brd ff:ff:ff:ff:ff:ff
inet 192.168.10.66/27 brd 192.168.10.95 scope global sss
valid_lft forever preferred_lft forever
inet6 fe80::5054:99ff:fe99:0/64 scope link
valid_lft forever preferred_lft forever
63: eth0@if64: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000
link/ether 52:54:dd:8a:0c:bb brd ff:ff:ff:ff:ff:ff
inet 192.168.0.2/24 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ddff:fe8a:cbb/64 scope link
valid_lft forever preferred_lft forever
65: eth1@if66: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 10.0.0.2/24 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::5054:ddff:fe83:992f/64 scope link
valid_lft forever preferred_lft forever
mma@26df14e31b73:~$
コンフィグ通りにIPアドレスが付与されていることが確認できました.
Dockerアプリのアンインストール
スイッチからDockerアプリをアンインストールしたい場合は,
上記とは反対のコマンドを実行します.
Switch# app-hosting stop appid ssh_minimal
Switch# app-hosting deactivate appid ssh_minimal
Switch# app-hosting uninstall appid ssh_minimal
おわりに
特権ポートが使えなかったり,単一ユーザーしかログインできなかったりと,いろいろ制約はありましたが,何とか構築できました.
スイッチにsshサーバーがあることで,ポートフォワーディングを利用してローカル内にあるサーバーのIPMIをアクセスして,遠隔でサーバーの電源を点けたりと様々なことに利用できそうです.
また,sshサーバーだけでなく,今アツイtailscaleを動かすこともできそうです.
参考
Discussion