ラベル paramiko の投稿を表示しています。 すべての投稿を表示
ラベル paramiko の投稿を表示しています。 すべての投稿を表示

2024年11月21日木曜日

CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from this module in 48.0.0. 対策

CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from this module in 48.0.0. 対策

概要

paramiko を import する際に warning が出るのでてっとり早く対応する方法を紹介します

環境

  • Ubuntu 24.04.1
  • Python 3.10.2
  • paramiko 2.12.0

対応前

python
Python 3.10.2 (main, Feb 16 2024, 16:09:49) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import paramiko
/add_disk1/.local/share/virtualenvs/api-UwQw2i9A/lib/python3.10/site-packages/paramiko/pkey.py:82: CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from this module in 48.0.0.
  "cipher": algorithms.TripleDES,
/add_disk1/.local/share/virtualenvs/api-UwQw2i9A/lib/python3.10/site-packages/paramiko/transport.py:253: CryptographyDeprecationWarning: TripleDES has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and will be removed from this module in 48.0.0.
  "class": algorithms.TripleDES,

対応後

python
Python 3.10.2 (main, Feb 16 2024, 16:09:49) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import warnings
>>> from cryptography.utils import CryptographyDeprecationWarning
>>> warnings.simplefilter('ignore', CryptographyDeprecationWarning)
>>> import paramiko

最後に

warnings の使い方は 3.10 版になります
3.11 系からは少し違うので注意してください

参考サイト

2023年10月31日火曜日

paramiko で古いサーバに ssh するときは disabled_algorithms を設定しなければならない

paramiko で古いサーバに ssh するときは disabled_algorithms を設定しなければならない

概要

古いサーバに接続するときに最新の openssh などが適用されていないので paramiko で使用されているデフォルトのアルゴリズムが使えません

環境

  • Python 3.11.3
  • paramiko 2.12.0

サンプルコード

ssh_client.connect(ip_address, port, user, pkey=rsa_key, disabled_algorithms=dict(pubkeys=['rsa-sha2-256', 'rsa-sha2-512']))

最後に

エラーは普通に Authentication failed. なのでかなりわかりづらいです

参考サイト

2022年11月9日水曜日

paramikoでfile descriptorが開放されない場合に確認すること

paramikoでfile descriptorが開放されない場合に確認すること

環境

  • Python 3.102
  • paramiko 2.7.2

必ずクライアントはクローズすること

オブジェクトのスコープが終了すると自動でクローズされるようなのですがどうもクローズされないことがあるようなので必ず close をコールしましょう

with 句を使えば間違いないです

2022年8月5日金曜日

paramikoで30分以上かかるコマンドを投げる場合にはchannelを使ったほうがいいかもしれない

paramikoで30分以上かかるコマンドを投げる場合にはchannelを使ったほうがいいかもしれない

概要

過去に paramiko を使ってコマンドを実行する方法を紹介しました
今回は channel という機能を使って接続状態を保つ方法を紹介します

環境

  • macOS 11.6.8
  • Python 3.10.2
  • paramiko

サンプルコード

import paramiko

with paramiko.SSHClient() as client:
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    rsa_key = paramiko.RSAKey.from_private_key_file("/path/to/sshkey.pem", "xxx")
    client.connect(hostname="192.168.100.1", port=22, username="user01", pkey=rsa_key)
    # channelの作成
    channel = client.get_transport().open_session(timeout=3600)
    try:
        command = "hostname"
        channel.exec_command(command)
        RECV_SIZE = 1024 * 32
        stdout_data = b''
        stderr_data = b''
        while not channel.closed or channel.recv_ready() or channel.recv_stderr_ready():
            stdout_data += channel.recv(RECV_SIZE)
            stderr_data += channel.recv_stderr(RECV_SIZE)
        code = channel.recv_exit_status()
        print(stdout_data)
        print(stderr_data)
    finally:
        channel.close()

注意事項

while で無限ループにしているので最悪抜け出せないことになるかもしれないのでもう少し考慮が必要かもしれない (外部タイマとか別のフラグのチェックとか)

2021年8月23日月曜日

paramiko で docker exec しようとすると The input devic e is not a TTY になる

paramiko で docker exec しようとすると The input devic e is not a TTY になる

概要

Python の paramiko で docker exec しようとすると「The input devic e is not a TTY」が発生しました
対処方法を紹介します

環境

  • macOS 11.5
  • Python 3.8.3
  • paramiko 2.7.2

対応策

「-T」オプションを付与して exec します

  • docker-compose exec -T app hostname

-T は pseudo-tty を無効にするオプションです

参考サイト

2021年7月21日水曜日

Paramiko でコマンドをバックグランド実行する方法

Paramiko でコマンドをバックグランド実行する方法

概要

過去 に paramiko を試してみました 今回はバックグランドで実行する方法を紹介します

環境

  • macOS 11.4
  • Python 3.8.3
  • paramiko 2.7.2

サンプルコード

import paramiko

def execute_cmd():
    with paramiko.SSHClient() as ssh_client:
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(hostname='192.168.1.2',
                    port=22,
                    username='username',
                    password='xxxxxxxxx')
        transport = ssh_client.get_transport()
        channel = transport.open_session()
        channel.exec_command('for i in `seq 1 10`; do echo $i; sleep 1; done', timeout=5)
        # try: 
        #     RECV_SIZE = 1024 * 32
        #     stdout_data = b''
        #     stderr_data = b''
        #     while not channel.closed or channel.recv_ready() or channel.recv_stderr_ready():
        #         stdout_data += channel.recv(RECV_SIZE)
        #         stderr_data += channel.recv_stderr(RECV_SIZE)
        #     code = channel.recv_exit_status()
        # finally:
        #     channel.close()
        return "ok"

print(execute_cmd())

ポイント

transport.open_session() でセッションを作成してからチャネルに対して exec_command することでバックグランド実行になります

ただこの場合は結果が戻ってこないので別ロジックでチャネルを使って結果を待つ必要があります コメントしている部分を外せば stdout_data に結果が入りますが Python 的には結局結果を待つ必要があるので結果が取得できるまでプロンプトは戻ってきません

バックグランドでコマンドを実行させる場合はコマンド側に結果をどこかに格納してそれを参照するような仕組みにしたほうが良いかなと思います (例えばコマンドの結果をファイルに出力してそのファイルの中身を見るなど)

参考サイト

2021年6月30日水曜日

ERROR: Could not build wheels for cryptography which use PEP 517 and cannot be installed directly

ERROR: Could not build wheels for cryptography which use PEP 517 and cannot be installed directly

概要

macOS 上に paramiko をインストールしようとしたらタイトルのエラーに遭遇しました 対応方法を紹介します

環境

  • macOS 11.4
  • Python 3.8.3
  • paramiko 2.7.2

原因

どうやら全然関係ないところでエラーになっているようです paramiko は cryptography というライブラリに依存しているのですがそのライブラリがデフォルトで Rust のコンパイラを使っています

Rust のコンパイラがすでにインストールされていれば問題なくインストールできるかもしれないのですがインストールされていない場合は今回のエラーに遭遇するようです

エラー文の途中を見ると「error: can’t find Rust compiler」という文が表示されているのが確認できると思います

対策1: cryptography だけインストールしてみる

Rust コンパイラを使わないでビルドする方法もあるようです 以下の環境変数をセットして cryptography だけインストールできるか確認してみましょう

  • export CRYPTOGRAPHY_DONT_BUILD_RUST=1
  • pipenv install cryptography

対策2: openssl のビルドオプションを追加する

openssl も使っているので以下のように openssl のヘッダファイルなどのパスも指定してあげます 環境によってことなるので brew info openssl で確認しましょう

  • export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  • export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
  • export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"

再度 paramiko をインストールする

上記が成功したら再度 paramiko をインストールしてみましょう

  • pipenv install paramiko

参考サイト

2021年6月29日火曜日

paramiko を monkeypatch する方法

paramiko を monkeypatch する方法

概要

Python の SSH クライアントライブラリ paramiko を pytest の monkeypatch でモックする方法を紹介します with - as 句を使うので __enter____exit__ を実装する必要があります

環境

  • macOS 11.4
  • Python 3.8.3
  • paramiko 2.7.2

実行コード

import paramiko

def execute_cmd():
    with paramiko.SSHClient() as ssh:
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname='192.168.100.99',
                    port=22,
                    username='user1',
                    password='xxxxxxxxxxx')
        stdin, stdout, stderr = ssh.exec_command('hostname')
        stdin.close()
        return stdout.read()

テストコード

import paramiko

from test import execute_cmd

class DummySSHClient():
    def __init__(self):
        self

    def set_missing_host_key_policy(self, policy):
        self.policy = policy

    def connect(self, hostname=None, port=None, username=None, password=None):
        return None

    def exec_command(self, cmd):
        return DummyStdin(), DummyStdout(), ""

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def close(self):
        return None

class DummyStdout():
    def read(self):
        return "testhost1"

class DummyStdin():
    def close(self):
        return None

class DummyAutoAddPolicy():
    def __init__(self):
        pass

def test_execute_cmd(monkeypatch):
    monkeypatch.setattr(paramiko, "SSHClient", DummySSHClient)
    monkeypatch.setattr(paramiko, "AutoAddPolicy", DummyAutoAddPolicy)
    result = execute_cmd()
    assert (result == "testhost1")

解説

paramiko.SSHClient をダミーの DummySSHClient でモックします paramiko.AutoAddPolicy もモックが必要なので DummyAutoAddPolicy を作成します

stdout と stdin のオブジェクトも使う場合にはそれぞれモックします 使用しない場合はモック不要です

あとは monkeypatch で setatter で SSHClient と AutoAddPolicy を持っくすれば OK です

scp の場合

paramiko + scp でファイルをアップロードする場合も紹介します

import paramiko
import scp

def upload_file():
    with paramiko.SSHClient() as ssh:
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname='192.168.100.99',
                    port=22,
                    username='user1',
                    password='xxxxxxxxxxx')
        with scp.SCPClient(ssh.get_transport()) as scp:
            scp.put('hoge.txt', '/tmp/hoge.txt')

scp のテストコード

import paramiko
import scp

from test import execute_cmd, upload_file

class DummySSHClient():
    def __init__(self):
        self

    def set_missing_host_key_policy(self, policy):
        self.policy = policy

    def get_transport(self):
        return None

    def connect(self, hostname=None, port=None, username=None, password=None):
        return None

    def exec_command(self, cmd):
        return DummyStdin(), DummyStdout(), ""

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

class DummySCPClient():
    def __init__(self, transport):
        self

    def put(self, src, dict):
        return None

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

class DummyStdout():
    def read(self):
        return "testhost1"

class DummyStdin():
    def close(self):
        return None

class DummyAutoAddPolicy():
    def __init__(self):
        pass

def test_upload_file(monkeypatch):
    monkeypatch.setattr(paramiko, "SSHClient", DummySSHClient)
    monkeypatch.setattr(paramiko, "AutoAddPolicy", DummyAutoAddPolicy)
    monkeypatch.setattr(scp, "SCPClient", DummySCPClient)
    result = upload_file()
    assert (result == None)

解説

scp の場合もほぼ同じです DummySSHClient 側に get_transport を追加で実装してあげます

おまけ: ProxyCommand を使う場合は

import paramiko

def execute_cmd_with_proxy():
    with paramiko.SSHClient() as ssh:
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname='192.168.100.99',
                    port=22,
                    username='user1',
                    password='xxxxxxxxxxx',
                    sock=paramiko.ProxyCommand('nc -X connect -x 192.168.100.100:3128 192.168.100.99 22'))
        stdin, stdout, stderr = ssh.exec_command('hostname')
        stdin.close()
        print(stdout.read())

になるので connect に 1 つ変数を追加してあげれば OK です (以下途中省略)

class DummySSHClient():
    def connect(self, hostname=None, port=None, username=None, password=None, sock=None):
        return None

おまけ2: 鍵認証の場合は

class DummyRSAKey():
    @classmethod
    def from_private_key_file(cls, key_path, passphrase):
        return "key"
class DummySSHClient():
    def connect(self, hostname=None, port=None, username=None, password=None, sock=None, pkey=None):
        return None
monkeypatch.setattr(paramiko, "RSAKey", DummyRSAKey)

こんな感じです

2021年6月25日金曜日

macOS で paramiko を使ってみた

macOS で paramiko を使ってみた

概要

Python の paramiko を使っていろいろな ssh 接続をしてみました 便利だけどケースによってはハマる点も多いと思います

環境

  • macOS 11.4
  • Python 3.8.3
  • paramiko

インストール

  • pipenv install paramiko scp

普通に ssh する

connect メソッドを使って各種 ssh の基本パラメータを指定するだけです

コネクションを貼るので with を使うのをオススメします

import paramiko

with paramiko.SSHClient() as ssh:
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname='192.168.100.10', port=22, username='user1', password='xxxxxxxxxx')
    stdin, stdout, stderr = ssh.exec_command('hostname')
    stdin.close()
    print(stdout.read())

トラブルシューティング

paramiko.ssh_exception.SSHException: Server ‘192.168.100.10’ not found in known_hosts

以下で回避できます

ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

AttributeError: ‘NoneType’ object has no attribute ‘time’

以下で回避できます

stdin.close()

もしくはコマンド実行後に 5 秒ほど wait を入れましょう 参考: https://github.com/paramiko/paramiko/issues/1617

鍵を使ってログインする

paramiko.RSAKey を使います 鍵へのパスはフルパスを指定しましょう 相対パスやカレントパスを使うとうまく鍵ファイルを読み込めない場合があります パスフレーズがある場合は一緒に指定します

import paramiko

rsa_key = paramiko.RSAKey.from_private_key_file("/path/to/key/privkey.pem", "xxxxxxxxx")

with paramiko.SSHClient() as ssh:
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname='192.168.100.10', port=22, username='root', pkey=rsa_key)
    stdin, stdout, stderr = ssh.exec_command('hostname')
    stdin.close()
    print(stdout.read())

scp する

scp モジュールと組み合わせることで使えます paramiko オンリーでは scp は使えません https://github.com/paramiko/paramiko/issues/150

以下はファイルをローカルからサーバにアップロードする方法になります 逆にダウンロードする場合は scp.get を使います

import paramiko
import scp

with paramiko.SSHClient() as ssh:
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname='192.168.100.10', port=22, username='user1', password='xxxxxxxxxx')
    with scp.SCPClient(ssh.get_transport()) as scp:
       scp.put('hoge.txt', '/tmp/hoge.txt')

鍵を使って scp する

鍵認証+scp を組み合わせるだけです

import paramiko
import scp

rsa_key = paramiko.RSAKey.from_private_key_file("/path/to/key/privkey.pem", "xxxxxxxxx")

with paramiko.SSHClient() as ssh:
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname='192.168.100.10', port=22, username='root', pkey=rsa_key)
    with scp.SCPClient(ssh.get_transport()) as scp:
       scp.put('hoge.txt', '/tmp/hoge.txt')

プロキシを使う

これがかなり曲者です ProxyCommand と組み合わせて使うのですが %h や %p などのテンプレート変数を paramiko は展開してくれません なので ssh コマンドで使っている ProxyCommand をそのまま使うと

paramiko.ssh_exception.ProxyCommandFailure: ProxyCommand(“nc -X connect -x 192.168.100.20:3128 %h %p”) returned nonzero exit status: Broken pipe」

と言ったエラーが発生します なお以下のサンプルは squid で ssh をプロキシした場合の ProxyCommand の設定になります

192.168.100.20 が squid プロキシで 192.168.100.10 が接続したい ssh サーバになります

import paramiko
with paramiko.SSHClient() as ssh:
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname='192.168.100.10',
                port=22,
                username='user1',
                password='xxxxxxxxx',
                sock=paramiko.ProxyCommand("nc -X connect -x 192.168.100.20:3128 192.168.100.10 22"))
    stdin, stdout, stderr = ssh.exec_command('hostname')
    stdin.close()
    print(stdout.read())

なお謎が解けなかったのは ProxyCommand で多段 ssh プロキシを使っている場合でプロキシも ssh でかつ鍵認証の場合に ProxyCommand で指定している ssh サーバの鍵のパスフレーズを指定する方法がわかりませんでした

もしかするとその場合はプロキシサーバにパスフレーズなしの鍵を登録しろってことなのかもしれません

最後に

多段プロキシで paramiko を使う場合はハマりどころがかなり多いと印象です 可能であれば多段プロキシを使わないケースで paramiko は使ったほうが良いかもしれません

参考サイト