TravisCIでDockerに入ったWebアプリのSeleniumテスト

 WebアプリをDockerでビルドして、そこでできたイメージをデプロイしてコンテナを走らせるってケースは増えてきているはず。そのイメージを使ってTravisCI上でSeleniumを使ってテストしてみる。


 Dockerイメージは既にDocker Hubにアップしてあるものとする。今回使うのは、Webアプリで、リクエストを送ると世界へのあいさつを返すだけのごく初歩的なものだ。テストはSeleniumを使ってリクエストを送り、レスポンスのHTMLのタイトルをチェックするものとする。

 テストに使うSeleniumを用意する。Seleniumはホスト環境に用意するのもいいが、仮想ディスプレイを使ったりすると、自分の環境でTravisCIでやるコマンドをちょっと試したかったりする場合に用意が手間になる。SeleniumもDockerで公式のものが用意されているので、今回はDockerで用意されたものを使う。

 Seleniumを動かすテストコードは、今回はPythonを使う。
 DockerコンテナとしてWebアプリとSeleniumが動いており、ホストにSeleniumを動かすPythonがあるというのが今回の環境。コンテナが二つあるので、ここらはdocker-composeを使うことにする。

 まずWebアプリとSeleniumのある環境の準備のためにdocker-compose。docker-compose.ymlを用意する。
docker-compose.yml

version: '2'

services:
tornadoapp:
image: matoba/tornadoapp
ports:
- "80:80"
selenium:
image: selenium/standalone-chrome
ports:
- "4444:4444"
links:
- tornadoapp

 WebアプリはTornadoを使って作ったのでtornadoappという名前にしてある。ポートバインドで、ホストのポート80でこのWebアプリがlistenされることになっている。
 あとSeleniumは、Pythonドライバで接続するためにポート4444でlistenしているのと、Webアプリに名前でアクセスができるようにlinksで設定してある。
 環境はこれでOK。

 ホストで動く、Seleniumを動かすPythonを用意する。"python setup.py test"のコマンドで動かすので、setup.pyと実際のテストコードを用意する。./TestWithSelenium以下にsetup.pyと、tests/s_test.pyが必要。
setup.py

from setuptools import setup
import sys

sys.path.append('./tests')

setup(
name='browsertest',
version='1.0',
description='test a project on browser',
test_suite = 's_test.suite',
install_requires=[
'selenium',
],
)


s_test.py

import unittest

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

#HOST = "http://172.17.0.1"
HOST = "http://tornadoapp"

class BrowserTests(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Remote(
command_executor = 'http://127.0.0.1:4444/wd/hub',
desired_capabilities = DesiredCapabilities.CHROME
)
self.driver.implicitly_wait(10)

def test_top(self):
"""top page"""
self.driver.get(HOST + "/")
self.assertIn("foo", self.driver.title)

def suite():
"""run tests"""
suite = unittest.TestSuite()
suite.addTests([unittest.makeSuite(BrowserTests)])
return suite


if __name__ == '__main__':
unittest.main()

 SeleniumからWebアプリへのアクセスはdocker-composeで設定した名前で行っている。その他の手段としては、ポートがホストにバインドされていれば、IPでアクセスできる。Dockerネットワークのデフォルトは172.17.0.*なのでこれを使っていいはず。

 テストが用意できたら、あとはこれをTravisCIで動かすための設定ファイルを用意する。
.travis.yml

sudo: required

language: python

python:
- 3.6

services:
- docker

env:
- DOCKER_COMPOSE_VERSION=1.8.0

before_install:
- sudo rm /usr/local/bin/docker-compose
- curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
- chmod +x docker-compose
- sudo mv docker-compose /usr/local/bin
- docker-compose build
- docker-compose up -d
- pip install selenium

script:
- cd ./TestWithSelenium
- python setup.py test

matrix:
fast_finish: true

 やっていること↓
前準備
・Dockerを使えるように
・docker-composeコマンドを使えるように
・WebアプリコンテナとSeleniumコンテナを用意
・PythonのSeleniumドライバを用意
テスト
・Pythonコードを走らせる

 これでTravisCIでコンテナに入ったWebアプリを、Seleniumでテストする準備ができた。あとはアップするだけ。設定などを間違えていなければこれでテストは通る。




今回のTravisCI
https://travis-ci.org/hMatoba/PlayDockerTest
comment: 0

遠隔でMongoDBサーバのバックアップを取る2

 前回に構築したMongoDBサーバの自動遠隔バックアップシステムを削除し、新しくバックアップシステムを組んだ。今回はMongoDBサーバでバックアップコマンドを実行し、それをAzure Blob StorageにPythonでアップするという流れ。

backup.sh

#!/bin/sh
cd /home/h/backup
mongodump --host foo -u bar -p boo
tar zcvf dump.tar.gz dump
python3.5 /home/h/up.py
rm -rf *


up.py

from azure.storage.blob import BlockBlobService
import datetime

account = "xxx"
key = "yyy"
block_blob_service = BlockBlobService(account_name=account, account_key=key)
filename = datetime.datetime.now().strftime("%Y%m%d")
block_blob_service.create_blob_from_path("mongo", filename, "dump.tar.gz")



comment: 0

MongoDBのセキュリティを考える(続き)

MongoDBのセキュリティを考える
 前回にMongoDBのセキュリティを考えた。Pythonで、どういうことが起こるとまずいかを書いた。今回はPythonの軽量WebフレームワークFlaskでそのまずい状況が起こるかを調べた。コードを含めてまとめておく。


 起こるとまずいのは、JSONとなる辞書型のオブジェクト中のkey,valueのうちのvalueのほうに、文字列や数値での条件ではなく、式で条件が与えられてしまうこと。下記で示すような変数some_valueに文字列か数値が入れば、それは通常の一致検索に使われる。
db.members.find({"id":some_value})

some_valueに辞書型で{"$ne":""}という形で値を入れられると、SQLInjectionでいうところの" OR 't' = 't' "のごとく、多数のレコードをひっかけるような条件となってしまう。

 まずいことがFlaskで起こるかを調べるため、WebアプリケーションとしてのFlaskを使ったコードを用意した。
from flask import Flask

from flask import request

app = Flask(__name__)

form = """<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8">
</head>
<body>
<form action="/" method="POST">
ID: <input type="text" name="user" />
PW: <input type="password" name="pw" />
<input type="submit" value="send" />
</form>
</body>
</html>
"""

@app.route("/", methods=['GET', 'POST'])
def hello():
if request.method == 'GET':
return form
elif request.method == 'POST':
user = request.form.get("user")
print(request.form)
is_str = str(isinstance(user, str))
print(user)
return is_str

if __name__ == "__main__":
app.run()

上記を書いたファイルをsrv.pyとしてPythonで実行する。
python svr.py


 認証フォームを想定したもので、ルート(http://localhost:5000/)にアクセスするとID、パスワードを入れるinputが表示される。ここで入れたIDが、さきほどの辞書型の{"id":some_value}でいうところのsome_valueの位置に入る。前回でクライアントから送信された値が、サーバ側で辞書型として得られるとまずいということはわかっているので、今回はDBに問い合わせはしない。値が辞書型で得られてしまうことがあるかを確かめる。
 FlaskでPOSTされた値は、request.form.get("key_name")でえられる。key_nameに"user"と入れれば、フォームに入れた値が文字列型で得られる。これが辞書型になってしまうようなケースがあるだろうか。
 PHPやNodejsではここらでまずいことができる。だからPythonではどうだろうと試してみた。PHPやNodejsで問題が起こるのは、inputのnameが"user"から"user[$ne]"に書き換えられたとき。そのときにサーバサイドでkey_nameの値がまずい形で取り出されてしまう。ブラウザの開発者ツールで”user[$ne]"をセットしてPOSTし、サーバサイドでその値を取り出そうとしてみる。
request.form.get("user")

取り出せなーい。なぜ?print(request.form)してみればわかる。
ImmutableMultiDict([('user[$ne]', 'foo'), ('pw', 'var')])

"user"というキーのフォーム要素はなかったということ。これなら少なくとも{"id":some_value}のsome_valueの値として、条件式となる辞書型の値となることはない。

 PHPではまずいことが起きうる。それが@ITで開設されている。
「JSON文字列へのインジェクション」と「パラメータの追加」
 PHPは変数を、配列として宣言するという手順を経ずにいきなり配列として使える。かなりゆるい。それがMongoDBと組み合わせることで穴となりうる。Pythonでは起こらなかったことを考えると、MongoDBのみに非がある脆弱性とはいいがたい。

 Flaskでなら、問題はそうそう起きなさそうである。さらに一般化していえば、検索条件に使うJSONに条件として値を入れるとき、型を確認してあればInjectionのようなことはできない。SQLでいうところのプレースホルダがないのもここらが理由だろう。
comment: 0

Bash on Ubuntu on WindowsでpyenvでFlask

'17/2/16追記*********************************************
公式ドキュメント見たらpyenvがdeprecatedだったので、この記事は完全に参考にならない記事に。
https://docs.python.org/3/library/venv.html

*********************************************

 FlaskでうっすいWebアプリを突貫で作りたかった。デプロイはコンテナでするとして、適当な環境で動きを確認するまでコンテナに入れなくていい。まずてっとりばやくFlaskを動く環境を作りたかった、Pythonは最新の3.6で。なのにWindowsでFlaskがうごかない・・・Bash on Ubuntu on WIndowsがあるじゃない。でもUbuntuのPythonは3.6じゃない。そこでpyenvを使うことにした。ちなみにPythonを始める人はこういう仮想環境構築はせんでいいと思う(参考)。


 まずBashを開く。そして依存ツールのインストール。
$ sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \

libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils


 そしてpyenvのインストール。
$ git clone https://github.com/yyuu/pyenv.git ~/.pyenv

$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc


 シェル再起動。
$ exec $SHELL


 今回使いたいPython3.6.0を入れる。
$ pyenv install 3.6.0


 カレントディレクトリで実行されるPythonを3.6.0にする。
$ pyenv local 3.6.0


 あとはpipでflaskを入れて動作確認。

comment: 0

Python: Githubでライブラリを公開する - オープンソースを作るその1

 プログラミングである程度基礎を覚えたら、そこから上のレベルアップ方法として、実際にコードを書くためにオープンソースに関わることが推奨されていたりする。ここで有名なライブラリにコミットするのもありだが、自分で書いたものをオープンソースソフトウェアとして立ち上げてしまうのもいいだろう。有名なライブラリはコミットのルールなどがあることを考えると多少敷居が高い。自作ならルールは自分で決められる。というわけで、自作のライブラリをオープンソースとして公開して、いろんな人に使ってもらえるようにブラッシュアップしていく方法の一つを記事にしていこうと思う。

 初回はまず、Githubで公開することから。
 今は多くのオープンソースがGithubで公開されている。ほかのCIサービスなどとも連携できるので、Githubにしておけばとりあえずいいんじゃない?と思う。そういうわけでGithubで公開する方法を説明する。ちなみにぼくの開発環境がWindowsなので、説明もその環境で。あとライブラリはPythonのものとしてやっていく。もうライブラリのコードは用意してある仮定でやっていく。

 最初に、Githubのデスクトップアプリを落としてきてインストール。「Github デスクトップ」で検索すれば出てくる。あとGithubのWebサイトで会員登録を済ませて、アプリケーションにアカウントを設定しておく。



 インストールしたアプリを起動して、ウィンドウ内左上にある「+」マークをクリックしてプロジェクトのディレクトリを作成する。



 ディレクトリを作ったらそこに、あらかじめ作ってあったライブラリのコードを入れる。コードを入れたら、Githubアプリケーションのウィンドウを見てみる。


 Changesが有効になっていて、コミットができるようになっているはず。コミットメッセージを入れて、コミットボタンをポチッと。これでプロジェクトの状態(ファイル配置、ファイル内容など)がセーブされたことになる。

 公開のためにもう一つ。ライセンスを用意する。とりあえず、「このライブラリの制限を緩くしていろんなところで使ってほしい」というならMITライセンスで構わないだろう。RubyのRailsとかJavaScriptのjQueryがMITだ。
 MITのライセンス文を探してlicense.txtという名前で保存して、プロジェクトルートに置く。置いたらさっきと同じようにGithubアプリケーションを使ってコミット。
MITライセンス文

 これまでのコミットではまだライブラリが公開されていない。Githubアプリケーションのプロジェクト画面の右上にPublishボタンがあるので、これを押して公開する。



 これでライブラリがオープンソースとして公開され、いろんな人の手に渡る準備ができた。次回以降で、いろんな人に使ってもらうための仕組みを整えていく。
 
comment: 0