まだまだリモートサーバーにユーザー名とパスワード認証でログインしている方もいらっしゃると思います。(私もそうです)
パスワード認証だと下記のようにSSH接続した時にPasswordが何か聞かれます。
ssh hinomaruc@192.168.88.1
Password:
正しいPasswordを入力すればそのままサーバーに入れますが、対話式のプロンプトだと自動化できずに困ってしまいますね。
対処方法は公開鍵認証にするという方法もありますが、今回はMacからリモートサーバーへアクセスする時のパスワード認証を自動化する方法としてsshpassとpexpectを使ってみたいと思います。
Linuxでも使えると思います。
(オプション) dockerでssh接続用のコンテナを準備
まずは、ローカル環境にdockerで仮想リモートサーバーを構築します。(すでに接続先のリモートサーバー環境がある方はスキップしてください。)
FROM ubuntu:23.04
# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y --no-install-recommends openssh-server
# Fix: Missing privilege separation directory: /run/sshd
RUN mkdir /run/sshd
# 今回はsystemd使わないからいらない
#RUN apt-get update && apt-get install -y --no-install-recommends init systemd
# build中だけ有効な変数を定義 (build後も有効な変数を定義するにはENVを使う)
ARG USERNAME=hinomaruc
ARG PASSWORD=phinomaruc
# sshでログインするユーザーを作成
RUN useradd -s /bin/bash --uid 2525 -m ${USERNAME} && echo "${USERNAME}:${PASSWORD}" | chpasswd
# sshd_configのPAM認証をONにする (ユーザー認証)
RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config
# お掃除
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# コンテナ実行時にsshdを起動(デーモン化しないで起動)
CMD ["/usr/sbin/sshd", "-De"]
docker build -f Dockerfile_ubuntu-ssh -t ubuntu-ssh:23.04 .
[+] Building 23.8s (10/10) FINISHED => [internal] load build definition from Dockerfile_ubuntu-ssh 0.0s => => transferring dockerfile: 1.01kB 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/ubuntu:23.04 0.0s => [1/6] FROM docker.io/library/ubuntu:23.04 0.3s => [2/6] RUN apt-get update && apt-get install -y --no-install-recommends openssh-server 19.9s => [3/6] RUN mkdir /run/sshd 0.4s => [4/6] RUN useradd -s /bin/bash --uid 2525 -m hinomaruc && echo "hinomaruc:phinomaruc" | chpasswd 1.6s => [5/6] RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config 0.4s => [6/6] RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 0.4s => exporting to image 0.6s => => exporting layers 0.6s => => writing image sha256:700e33bb180fb7448fb572184f7b3a2696f960f18c5890a89c96a4a17446a3d5 0.0s => => naming to docker.io/library/ubuntu-ssh:23.04 0.0s Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
23.8秒でビルドが完了しました。
ファイル名がDockerfileだと-fのオプションは必要ありません。私はいつも分かりにくくなるのでファイル名でDockerfileを区別できるようにリネームしています。
ちなみに私の環境ではユーザーフォルダ(/Users/hinomaruc)以下にDockerfileを配置してビルドしようとするとエラーになりました。
[+] Building 0.1s (1/2) => ERROR [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 40B 0.0s ------ > [internal] load build definition from Dockerfile: ------ failed to solve with frontend dockerfile.v0: failed to read dockerfile: error from sender: open .Trash: operation not permitted
.Trashフォルダへの権限がないとか。。
ユーザーディレクトリ以下に適当なフォルダを作成して(Dockerフォルダなど)ビルドすることによって解決しましたので、お困りの方はお試しください。
docker run --rm -it -d -p 2222:22 ubuntu-ssh:23.04
61ed53e419fe636d4aa78c92afedd10f998ba63fa12d5c0f578ce8ffbe11bb72
テストなので、--rmオプションをつけてコンテナをストップしたら自動で消えるように設定しました。
-p 2222:22オプションでホストの2222ポートをコンテナの22ポートと繋いでいます。
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 61ed53e419fe ubuntu-ssh:23.04 "/usr/sbin/sshd -De" 6 seconds ago Up 5 seconds 0.0.0.0:2222->22/tcp boring_shaw
ssh hinomaruc@localhost -p 2222
The authenticity of host '[localhost]:2222 ([::1]:2222)' can't be established. ECDSA key fingerprint is SHA256:MeLhCHt1yPWYuf5wuP90qmL3y77J0Yo52YOoeoCt6Cc. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '[localhost]:2222' (ECDSA) to the list of known hosts. hinomaruc@localhost's password: hinomaruc@61ed53e419fe:~$
ホストの2222番ポートにhinomarucユーザーで繋いでみます。パスワードはphinomarucに設定したので入力するとログイン出来ました。
これでリモートサーバーの準備はOKです。
Macにsshpassをインストールする
sshのパスワード入力を自動化するため、sshpassというライブラリを使おうと思います。
macではbrew install sshpassでインストール出来ると思ったのですが、セキュリティ保護のためbrewには初期状態だと追加されていないようです。
brew search sshpass
==> Formulae sshs If you meant "sshpass" specifically: We won't add sshpass because it makes it too easy for novice SSH users to ruin SSH's security.
How to install sshpass on Mac?を参考にするとbrewでsshpassをインストールできるスクリプトを公開しているgithubのリンクがたくさん紹介されていますが、スクリプトの中身を見るとsourceforgeからsshpassのファイルをダウンロードしてビルドしてあげているようです。
上記のGithubからbrew install esolitos/ipa/sshpassなどでインストールしてもいいのですが、今回はsshpassのソースをビルドしてインストールしてみようと思います。
Xcodeがインストールされていること (Xcode command line toolだけでもOK)
MacでビルドするためにXcodeが必要になります。
brewをインストールしたことがあればrequirementsにXcodeがあるので問題ないと思います。
Command Line Tools (CLT) for Xcode (from xcode-select --install or https://developer.apple.com/download/all/) or Xcode 3
引用: https://docs.brew.sh/Installation#macos-requirements
softwareupdate --history | grep "Xcode"
Command Line Tools for Xcode 12.4 2022/06/01 19:57:51
sshpassのダウンロードとビルド&インストール
まずは、https://sourceforge.net/projects/sshpass/files/sshpass/ からインストールしたいsshpassのバージョンのファイルをダウンロードします。(curlコマンドでもブラウザからでもOKです)
今回は23年2月2日時点で最新版の1.09をダウンロードしました。
curl -O -L https://sourceforge.net/projects/sshpass/files/sshpass/1.09/sshpass-1.09.tar.gz
tar xvzf sshpass-1.09.tar.gz
x sshpass-1.09/ x sshpass-1.09/ChangeLog x sshpass-1.09/missing x sshpass-1.09/configure x sshpass-1.09/README x sshpass-1.09/NEWS x sshpass-1.09/depcomp x sshpass-1.09/compile x sshpass-1.09/main.c x sshpass-1.09/INSTALL x sshpass-1.09/aclocal.m4 x sshpass-1.09/Makefile.in x sshpass-1.09/configure.ac x sshpass-1.09/AUTHORS x sshpass-1.09/COPYING x sshpass-1.09/sshpass.1 x sshpass-1.09/config.h.in x sshpass-1.09/Makefile.am x sshpass-1.09/install-sh
cd sshpass-1.09
./configure
checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for a thread-safe mkdir -p... ./install-sh -c -d checking for gawk... no checking for mawk... no checking for nawk... no checking for awk... awk ・・・省略・・・ checking that generated files are newer than configure... done configure: creating ./config.status config.status: creating Makefile config.status: creating config.h config.status: executing depfiles commands
make
/Library/Developer/CommandLineTools/usr/bin/make all-am gcc -DHAVE_CONFIG_H -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c mv -f .deps/main.Tpo .deps/main.Po gcc -g -O2 -o sshpass main.o
sudo make install
Password: ./install-sh -c -d '/usr/local/bin' /usr/bin/install -c sshpass '/usr/local/bin' ./install-sh -c -d '/usr/local/share/man/man1' /usr/bin/install -c -m 644 sshpass.1 '/usr/local/share/man/man1'
sshpass -V
sshpass 1.09 (C) 2006-2011 Lingnu Open Source Consulting Ltd. (C) 2015-2016, 2021 Shachar Shemesh This program is free software, and can be distributed under the terms of the GPL See the COPYING file for more information. Using "assword" as the default password prompt indicator.
無事インストール出来たようです。
sshpassコマンドを使ってリモートサーバーに接続してみる
sshpass -p phinomaruc ssh hinomaruc@localhost -p 2222
Last login: Wed Feb 1 23:53:22 2023 from 172.17.0.1 hinomaruc@61ed53e419fe:~$
出来ました!簡単ですね。
接続した後にscpコマンドなどを実行するなどシェルスクリプトを書けば自動化出来るかと思います。
Pexpectを使ってリモートサーバーにログインして特定の処理を動かしてみる
次にPexpectを試してみます。Linuxでよく使っていたexpectコマンドがありますが、そのPythonバージョンのようです。
brew install expectでMacでexpectコマンドを使うことも出来るようですが、PythonでやりたいのでPexpectを使ってみようと思います 笑
Pexpectは子プロセス/アプリを作り出して自動処理するためのモジュールで、sshやftpなどの対話式プログラムを自動化する為に使われます。
同じアプリケーションを複数のサーバーでインストールするときの自動化するといった時にも使えるようです。
Pexpect is a Python module for spawning child applications and controlling them automatically. Pexpect can be used for automating interactive applications such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup scripts for duplicating software package installations on different servers.
引用: https://pexpect.readthedocs.io/en/stable/api/pexpect.html
Windowsでは pexpet.spawn と pexpect.run() コマンドは利用出来ないようです。その代わりpexpect.popen_spawn.PopenSpawnやpexpect.fdpexpect.fdspawnを使う必要があるようです。引用: https://pexpect.readthedocs.io/en/stable/overview.html#windows
リモートサーバーに画像などローカルに自動で移動したいファイルを準備
画像をリモートサーバーからローカル側にscpしてくることを想定し、テストデータを準備してみました。
hinomaruc@61ed53e419fe:~$ pwd
/home/hinomaruc
hinomaruc@61ed53e419fe:~$ touch aaa.jpg
hinomaruc@61ed53e419fe:~$ touch bbb.jpg
hinomaruc@61ed53e419fe:~$ touch ccc.jpg
hinomaruc@61ed53e419fe:~$ ll
total 28 drwxr-x--- 1 hinomaruc hinomaruc 4096 Feb 2 04:33 ./ drwxr-xr-x 1 root root 4096 Feb 1 23:48 ../ -rw------- 1 hinomaruc hinomaruc 5 Feb 2 03:55 .bash_history -rw-r--r-- 1 hinomaruc hinomaruc 220 Jan 7 07:34 .bash_logout -rw-r--r-- 1 hinomaruc hinomaruc 3771 Jan 7 07:34 .bashrc -rw-r--r-- 1 hinomaruc hinomaruc 807 Jan 7 07:34 .profile -rw-r--r-- 1 hinomaruc hinomaruc 0 Feb 2 04:33 aaa.jpg -rw-r--r-- 1 hinomaruc hinomaruc 0 Feb 2 04:33 bbb.jpg -rw-r--r-- 1 hinomaruc hinomaruc 0 Feb 2 04:33 ccc.jpg
Pexpectでリモートとローカル間でファイルをやり取り
事前にリモートサーバーからローカルにコピーしたいファイルのパスを羅列したcsvのリストを作成しておきます。
cat file_list.csv
/home/hinomaruc/aaa.jpg /home/hinomaruc/bbb.jpg /home/hinomaruc/ccc.jpg
import sys
import os
import pexpect
# file_list.csvの置き場所
LOCAL_DIR="/Users/hinomaruc/Docker"
# リモートサーバーにログインするパスワード
var_password = "phinomaruc"
# コピーする先のパスを取得
import pandas as pd
df = pd.read_csv(os.path.join(LOCAL_DIR,"file_list.csv"),sep=",",header=None,names=["file_path"])
for FILE in df["file_path"]:
print("transfer...",FILE)
try:
#SCPコマンドでリモートサーバからローカルにファイルをコピーする
child = pexpect.spawn('scp -P 2222 -p "hinomaruc@localhost:%s" %s' % (FILE,LOCAL_DIR))
i = child.expect(["password:",pexpect.EOF])
# DEBUG用
print(FILE,"...",child.before,child.after)
if i==0:
print("send password")
# パスワードを送り込む
child.sendline(var_password)
child.expect(pexpect.EOF)
elif i==1:
print("timeout")
except Exception as e:
print("エラー発生")
print(e) #エラーメッセージの表示
# 子プロセスがまだ存在する場合、closeする
if child.isalive():
print('子プロセスは生きている')
child.close()
# 通常あり得ないが、まだ子プロセスが存在している場合はメッセージを出す。
if child.isalive():
print('子プロセスはまだクローズしていない')
else:
print('子プロセスは正常にクローズした')
transfer... /home/hinomaruc/aaa.jpg /home/hinomaruc/aaa.jpg ... b"\rhinomaruc@localhost's " b'password:' send password 子プロセスは正常にクローズした transfer... /home/hinomaruc/bbb.jpg /home/hinomaruc/bbb.jpg ... b"\rhinomaruc@localhost's " b'password:' send password 子プロセスは正常にクローズした transfer... /home/hinomaruc/ccc.jpg /home/hinomaruc/ccc.jpg ... b"\rhinomaruc@localhost's " b'password:' send password 子プロセスは正常にクローズした
正常終了したようです。実際にファイルがローカルのフォルダに存在するか見てみましょう
ls /Users/hinomaruc/Docker
Dockerfile_ubuntu-ssh bbb.jpg file_list.csv aaa.jpg ccc.jpg
ありました!問題なく使えました。
まとめ
分析用のデータ(ログや画像ファイル)がリモートサーバーに溜まっている場合に使えそうですね。
個人的には単発利用であれば、リモートサーバー側に必要なファイルをtar.gzやzipファイルに固めておいて、GUIのFTPツール(FileZilla)でローカルに持ってくるという方法も楽でいいかなと思います。