mocker.patchではまってわかったimportの仕組み

mocker.patchで関数の返り値を指定したかったんだけど、うまくいかずはまった。調べてたらimportが正しく理解できていないことがわかったので、メモ。

コード

utils.py(モック化したいもの)

def utilA():
    return "returnA"

sample.py(テストしたいもの)

from utils import *

def methodA():
    str = utilA()

    return str

test_sample.py(テストコード)

from sample import *

def test_A(mocker):
    mocker.patch('utils.utilA', return_value="mock_returnA")
    print(methodA())

うまくいかなかったこと

関数methodAのテストのため、utilAの戻り値をreturnAではなくmock_returnAにしたかった。 mocker.pathの第1引数はモジュール+オブジェクト名を書けば良い。今回モックにしたいのはutilsモジュールのutilA関数なので utils.utilAであっているはず。

だけどテストを実行すると、

# pytest tests/testsample.py -s
…

tests/testsample.py returnA

と、returnAと出てきてしまった。これは通常通りutilAが実行されてしまっているのでモックが動いていない。なぜなのか。

うまくいった

いろいろ試した結果、mocker.pathの第1引数を sample.utilA と書けば、うまくutilAがモック化されることがわかった。

from sample import *

def test_A(mocker):
    mocker.patch('sample.utilA', return_value="mock_returnA")
    print(methodA())
# pytest tests/testsample.py -s
…

tests/testsample.py mock_returnA

なぜなのか

ポイントはimportだった。自分めんどくさがりなので、自分のモジュールを読み込みたいときはfrom utils import *のようにアスタリスクを使ってインポートすることが多い。 これがどういうことなのかあまり深く考えたことなかったけど、sampleモジュールにutilsモジュールのオブジェクトを展開しているということのようだ。 なのでutilsモジュールのutilAではなくsampleモジュールのutilAを使っているイメージなようだ。 なのでmocker.patchにもmocker.patch('sample.utilA'と書かないと使っているutilAがモック化されない。

なお、PEP8ではこのアスタリスクでのインポートは推奨されていないらしい。

https://pep8-ja.readthedocs.io/ja/latest/#import

ワイルドカードを使った import (from import *) は避けるべきです。なぜなら、どの名前が名前空間に存在しているかをわかりにくくし、コードの読み手や多くのツールを混乱させるからです。

sample.pyのimport文をimport utilsとし、utils.utilAを呼び出すコードにするなら、mocker.patch('sample.utilA'でうまくいく。

sample.py

from utils import 

def methodA():
    str = utils.utilA()

    return str
from sample import *

def test_A(mocker):
    mocker.patch('utils.utilA', return_value="mock_returnA")
    print(methodA())
# pytest tests/testsample.py -s
…

tests/testsample.py mock_returnA

IKEv1のpreshared-keyはどのように使われているのか

ふと、IKEv1のPreshared-key(事前共有鍵)認証で設定したキーは、ISAKMP SA作成時のパケットのどこに載っているのか気になった。(載っているもんだと思っていた。)

別件でstrongswanを使いそうな予感がしていたので、たいして書籍等調べもせず「GNS3のルータとstrongswan間でIPsec通信させて、ISAKMPのパケットを見ればわかるだろう」という安直な考えから、とりあえず両者の設定を行った。

構成

f:id:pesnia:20160924165821p:plain

strongswanはvirtualboxであげたCentos7(SrvForGNS)上にいれてある。

各種バージョン

GNS3: 1.5.2
ゲストOS: Centos7.2
strongswan: 5.5.0

設定(Cisco)

show runの抜粋。

crypto isakmp policy 10
 encr aes
 authentication pre-share
 group 5
crypto isakmp key cisco address 172.16.10.2
crypto isakmp keepalive 30 periodic

crypto ipsec transform-set TS esp-aes esp-sha-hmac
 mode tunnel

crypto map cmap 10 ipsec-isakmp
 set peer 172.16.10.2
 set transform-set TS
 match address cryptoacl

interface GigabitEthernet2/0
 ip address 192.168.1.1 255.255.255.0
 negotiation auto

interface GigabitEthernet3/0
 ip address 172.16.10.1 255.255.255.0
 negotiation auto
 crypto map cmap

ip route 192.168.2.0 255.255.255.0 172.16.10.2

ip access-list extended cryptoacl
permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255

設定(strongswan)

ipsec.conf
config setup
 

conn %default
    ikelifetime=1440m
    keylife=60m
    rekeymargin=3m

    keyingtries=1
    keyexchange=ikev1
    authby=secret

conn ciscoios
    ikelifetime=1440m
    left=172.16.10.2
    leftsubnet=192.168.2.0/24
    leftid=172.16.10.2
    leftfirewall=yes
    right=172.16.10.1
    rightsubnet=192.168.1.0/24
    rightid=172.16.10.1
    auto=add
    ike=aes128-sha1-modp1536
    esp=aes128-sha1
ipsec.secrets
172.16.10.1 172.16.10.2 : PSK "cisco"

コマンド

strongswan
ipsec start
cisco

ping契機でISAKMP SAの生成が始まる。

ping 192.168.2.1 source 192.168.1.1

結果

Wiresharkでパケットを眺めていても全然わからなかった。ものの本によるとSKEYIDという鍵がpreshared-keyから生成されているらしいけど、それがどこにあるのかもよくわからない。
まだまだ勉強が足りないようなので、まずは調べてみる。

2016.09.24 追記

ISAKMP SA生成の3番目のメッセージからパケットが一部(ID、ハッシュ値)暗号化されているようで、その暗号化にVPN機器が自身のpreshared-keyから生成した秘密鍵(SKEYID_eかな?)を使っているみたい。
で、暗号化して相手におくって、相手も同様に自身に設定してあるpreshared-keyから秘密鍵を生成し、復号化する。復号化できなければ認証失敗、ということだと予想しています。