3
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pythonコードから多段SSHでホストに接続し、コマンドを実行してstdout出力を受け取る

Last updated at Posted at 2020-12-01

記事の内容

下図(等幅フォントで見てね)のように、Pythonコードから踏み台サーバーを経由してVPSに多段SSH接続します。
そしてVPSで何らかのコマンドを実行し、stdout/stderrへの出力をPython側で受け取ります。

┌───────┐
│       │configで設定するホスト名:vps
│   VPS   │OS:CentOS 8.2
│       │sshポート:2222
│       │Ed25519鍵
└───────┘
    ↑
┌───┴───┐
│       │configで設定するホスト名:jump_host
│踏み台サーバー│OS:Ubuntu 20.04
│       │sshポート:22
│       │ECDSA鍵
└───────┘
    ↑
┌───┴───┐
│       │OS:WSL(Ubuntu 20.04)
│ Pythonコード │Python 3.8.5
│       │
└───────┘

ssh環境

Python側のマシンで、ssh用に以下のファイルを用意します。

  • 踏み台サーバーの秘密鍵: ~/.ssh/id_ecdsa
  • VPSの秘密鍵: ~/.ssh/id_ed25519
  • sshのconfigファイル: 以下の通り
~/.ssh/config

Host jump_host
  Hostname XXX.XXX.XXX.XXX    # 書き換えてください
  User usrname    # 書き換えてください
  Port 22
  IdentityFile ~/.ssh/id_ecdsa

Host vps
  Hostname YYY.YYY.YYY.YYY    # 書き換えてください
  User usrname    # 書き換えてください
  Port 2222
  IdentityFile ~/.ssh/id_ed25519
  ProxyCommand sshpass -f ./.pass -P "Enter passphrase for key" ssh -o StrictHostKeyChecking=no -W %h:%p jump_host

vpsホストのProxyCommandは、「ProxyCommand ssh -W %h:%p jump_host」のように通常の書き方で書くと、Pythonコード実行時に踏み台サーバーのパスフレーズの手入力を求められます(パスフレーズが設定してあれば)。
今回はsshpass(=インストールしてください)を使用して、~/.ssh/.passファイル(=準備が必要です)からパスフレーズを読み込んで、踏み台サーバーのパスフレーズを自動入力させるように設定しています。

Pythonコード

以下にPythonコードを記します。
コードが読み込む設定ファイル(.secret.ini)は後述します。

paramikoというライブラリを使用してSSH接続します。

コードで logger という変数を使っていますが、logging周りのコードは割愛しましたのでご了承ください。

ssh_cmd.py

import configparser
from pathlib import Path
import datetime
import paramiko
from paramiko.config import SSH_PORT


current_directory = "."


def ssh_out(stderr, stdout, host, username, cmd):
    """
    ホストで実行したコマンドの出力結果をログに出力します
    """

    logger.info(f'''\
=============
[{username}@{host}]$ {cmd}
=============''')

    s = ""
    for e in stderr:
        s = s + e
    if 0 < len(s):
        logger.info(s)

    s = ""
    for o in stdout:
        s = s + o
    if 0 < len(s):
        logger.info(s)


def ssh_cmd(ssh_config, host, key_file, passphrase, sudo_password, list_cmd):
    """
    ホストにssh接続してコマンドを実行し、その出力結果をログに出力します
    """

    logger.info(f'''\
*************
host = {host}
*********''')

    # sshのconfigファイルから値を取り出す
    lookup = ssh_config.lookup(host)

    hostname = lookup["hostname"]
    username = lookup["user"]

    try:
        port = int(lookup["port"])
    except Exception:
        port = SSH_PORT

    key_filename = lookup['identityfile'],

        # 秘密鍵ファイルからキーを取得(パスフレーズを設定している場合)
    try:
        pkey = paramiko.Ed25519Key.from_private_key_file(key_file, passphrase)
    except Exception:
        try:
            pkey = paramiko.ECDSAKey.from_private_key_file(key_file, passphrase)
        except Exception:
            try:
                pkey = paramiko.RSAKey.from_private_key_file(key_file, passphrase)
            except Exception:
                pkey = None

        # 踏み台経由の場合
    try:
        sock = paramiko.ProxyJump(lookup["proxyjump"])
    except Exception:
        try:
            sock = paramiko.ProxyCommand(lookup["proxycommand"])
        except Exception:
            sock = None

    # ssh接続
    ssh_client = paramiko.SSHClient()
    try:
        ssh_client.load_system_host_keys()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(
            hostname=hostname,
            port=port,
            username=username,
            key_filename=key_filename,
            pkey=pkey,
            sock=sock,
            )

        # ホスト側でコマンド実行
        for cmd in list_cmd:
            stdin, stdout, stderr = ssh_client.exec_command(cmd)
            if "sudo" in cmd:
                stdin.write(sudo_password + '\n')
                stdin.flush()
            ssh_out(stderr, stdout, host, username, cmd)
    finally:
        ssh_client.close()


def main():
    """
    メイン
    """

    # iniファイルの読み込み
    ini = configparser.ConfigParser()
    ini.read(f"{current_directory}/.secret.ini", encoding='utf-8')

        # ~/.ssh/config
    SECTION = "common"
    ssh_config_file = ini[SECTION]["ssh_config_file"]

        # 踏み台
    SECTION = "jump_host"
    jmp_key_file = ini[SECTION]["key_file"]
    jmp_passphrase = ini[SECTION]["passphrase"]
    jmp_sudo_password = ini[SECTION]["sudo_password"]

        # 踏み台経由で入るVPS
    SECTION = "vps"
    vps_key_file = ini[SECTION]["key_file"]
    vps_passphrase = ini[SECTION]["passphrase"]
    vps_sudo_password = ini[SECTION]["sudo_password"]


    # ~/.ssh/configの読み込み
    ssh_config = paramiko.SSHConfig()
    ssh_config.parse(open(ssh_config_file, 'r'))

    # まず、踏み台に入ってみる
    host = "jump_host"
        # ホストで実行させるコマンド
    list_cmd = [
        "cat /etc/issue",   # Ubuntuのバージョン表示
        "sudo -S ls -a",
    ]
    ssh_cmd(ssh_config, host, jmp_key_file, jmp_passphrase, jmp_sudo_password, list_cmd)

    # 踏み台経由でVPSに入ってみる
    host = "vps"
        # ホストで実行させるコマンド
    list_cmd = [
        "cat /etc/centos-release",   # CentOSのバージョン表示
        "sudo -S ls -a",
    ]
    ssh_cmd(ssh_config, host, vps_key_file, vps_passphrase, vps_sudo_password, list_cmd)


if __name__ == '__main__':
    main()

このPythonコードが読み込む設定ファイルを、以下の内容で準備する必要があります。
本記事では、ここに各ホストのパスフレーズとパスワードを書いてしまっていますので、注意が必要です。
運用時には、それぞれの状況に合わせて安全な方法をご検討ください。

.secret.ini

[common]
ssh_config_file = /home/usrname/.ssh/config

[jump_host]
key_file = /home/usrname/.ssh/id_ecdsa
passphrase = secret
sudo_password = secret

[vps]
key_file = /home/usrname/.ssh/id_ed25519
passphrase = secret
sudo_password = secret

実行結果

Pythonコードを実行すると、jump_hostとvpsでそれぞれコマンドが動き、以下のように結果がログに出力されました(記事用に加工しています)。


*************
host = jump_host
*********

=============
[usrname@jump_host]$ cat /etc/issue
=============

Ubuntu 20.04.1 LTS \n \l



=============
[usrname@jump_host]$ sudo -S ls -a
=============

[sudo] usrname のパスワード: 

.
..
.bash_history
.bash_logout
.bashrc
.ssh
ダウンロード
テンプレート
デスクトップ
ドキュメント
ビデオ
ピクチャ
ミュージック
公開


*************
host = vps
*********

=============
[usrname@vps]$ cat /etc/centos-release
=============

CentOS Linux release 8.2.2004 (Core) 


=============
[usrname@vps]$ sudo -S ls -a
=============

[sudo] usrname のパスワード:

.
..
.bash_history
.bash_logout
.bash_profile
.bashrc
.ssh
.zshrc


Process finished with exit code 0

以上です。

3
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?