自分のキャリアをあれこれ考えながら、Pythonで様々なデータを分析していくブログです

sshpassやpexpectでサーバーログインのパスワード入力を自動化する方法

Python
PythonLinux

まだまだリモートサーバーにユーザー名とパスワード認証でログインしている方もいらっしゃると思います。(私もそうです)

パスワード認証だと下記のようにSSH接続した時にPasswordが何か聞かれます。

ユーザー名&パスワード認証
ssh hinomaruc@192.168.88.1
Out[0]
Password:

正しいPasswordを入力すればそのままサーバーに入れますが、対話式のプロンプトだと自動化できずに困ってしまいますね。

対処方法は公開鍵認証にするという方法もありますが、今回はMacからリモートサーバーへアクセスする時のパスワード認証を自動化する方法としてsshpassとpexpectを使ってみたいと思います。

Linuxでも使えると思います。

スポンサーリンク

(オプション) dockerでssh接続用のコンテナを準備

まずは、ローカル環境にdockerで仮想リモートサーバーを構築します。(すでに接続先のリモートサーバー環境がある方はスキップしてください。)

Dockerfileを準備
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でイメージを作成する (DockerfileをDockerfile_ubuntu-sshにリネームしておく)
docker build -f Dockerfile_ubuntu-ssh -t ubuntu-ssh:23.04 .
Out[0]
[+] 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
Out[0]
61ed53e419fe636d4aa78c92afedd10f998ba63fa12d5c0f578ce8ffbe11bb72

テストなので、--rmオプションをつけてコンテナをストップしたら自動で消えるように設定しました。
-p 2222:22オプションでホストの2222ポートをコンテナの22ポートと繋いでいます。

docker psでプロセスの確認
docker ps
Out[0]
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接続確認
ssh hinomaruc@localhost -p 2222
Out[0]
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
Out[0]
==> 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

Xcodeがインストールされているか確認
softwareupdate --history | grep "Xcode"
Out[0]
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でsshpassをダウンロード
curl -O -L  https://sourceforge.net/projects/sshpass/files/sshpass/1.09/sshpass-1.09.tar.gz
tarコマンドでファイルを解凍
tar xvzf sshpass-1.09.tar.gz 
Out[0]
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
configureコマンドで確認
cd sshpass-1.09
./configure
Out[0]
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コマンドでコンパイル
make
Out[0]
/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  
sshpassのインストール
sudo make install
Out[0]
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コマンドが使えるか確認
sshpass -V
Out[0]
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でリモートサーバーに接続
sshpass -p phinomaruc ssh hinomaruc@localhost -p 2222
Out[0]
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でPexpectを利用する場合の注意点

Windowsでは pexpet.spawn と pexpect.run() コマンドは利用出来ないようです。その代わりpexpect.popen_spawn.PopenSpawnやpexpect.fdpexpect.fdspawnを使う必要があるようです。引用: https://pexpect.readthedocs.io/en/stable/overview.html#windows

リモートサーバーに画像などローカルに自動で移動したいファイルを準備

画像をリモートサーバーからローカル側にscpしてくることを想定し、テストデータを準備してみました。

テスト用にaaa.jpg,bbb.jpg,ccc.jpgをリモートサーバーに準備
hinomaruc@61ed53e419fe:~$ pwd
/home/hinomaruc
hinomaruc@61ed53e419fe:~$ touch aaa.jpg
hinomaruc@61ed53e419fe:~$ touch bbb.jpg
hinomaruc@61ed53e419fe:~$ touch ccc.jpg
hinomaruc@61ed53e419fe:~$ ll
Out[0]
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のリストを作成しておきます。

file_list.csvの中身
cat file_list.csv
Out[0]
/home/hinomaruc/aaa.jpg
/home/hinomaruc/bbb.jpg
/home/hinomaruc/ccc.jpg
Pexpectでリモートサーバーからファイルをscpでコピーする
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('子プロセスは正常にクローズした')
Out[0]
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
子プロセスは正常にクローズした

正常終了したようです。実際にファイルがローカルのフォルダに存在するか見てみましょう

scpしたファイルが存在するか確認
ls /Users/hinomaruc/Docker
Out[0]
Dockerfile_ubuntu-ssh   bbb.jpg         file_list.csv
aaa.jpg         ccc.jpg

ありました!問題なく使えました。

スポンサーリンク

まとめ

分析用のデータ(ログや画像ファイル)がリモートサーバーに溜まっている場合に使えそうですね。

個人的には単発利用であれば、リモートサーバー側に必要なファイルをtar.gzやzipファイルに固めておいて、GUIのFTPツール(FileZilla)でローカルに持ってくるという方法も楽でいいかなと思います。

タイトルとURLをコピーしました