AzureFileStorageのエミュレータにlocalhostじゃないIPを使ってコンニチワ

'17/9/21追記
IPを使っても、結局エミュレータにアクセスするマシンを仮想化で外に置いたら、エミュレータが使えなくなった。だから外の環境からエミュレータを使うのは無理かも。
********


Azure Storage Emulatorにコンテナ内からつなごうとして、結局リバースプロキシを立てた話

 少し前に、開発に使うAzureのストレージエミュレータがローカルからしか使えん、けどアプリはDockerコンテナにしたいから、Fiddlerでリバースプロキシするってのを書いた。書いておいて、やっぱFiddlerを開発の度に立ち上げるのもアレだなーと思ったので、一般的なサーバソフトで、まずはNginXで同じことをやってみることにした。今回は.NET CoreでC#によるエミュレータへの書き込み、ブラウザで書き込んだファイルの読み出しまでやる。

 .NET CoreのC#で、Azureのストレージエミュレータにアクセスして、適当なテキストファイルを書き込むコードを用意する。
using Microsoft.WindowsAzure.Storage;

using Microsoft.WindowsAzure.Storage.Blob;

using System;
using System.IO;
using System.Threading.Tasks;

namespace PlayAzureStorage
{
class Program
{
private static async Task Func2Async()
{
var accountName = "devstoreaccount1";
var key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
var connectionString = $"AccountName={accountName};"
+ $"AccountKey={key};"
+ $"BlobEndpoint=http://192.168.11.17:20000/devstoreaccount1;";
//+ "BlobEndpoint=http://127.0.0.1:1000/devstoreaccount1;";

var account = CloudStorageAccount.Parse(connectionString);
Console.WriteLine("connect: " + account.BlobStorageUri);
var blobClient = account.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("test");
var isCreated = await container.CreateIfNotExistsAsync();
if (isCreated)
{
var permissions = await container.GetPermissionsAsync();
permissions.PublicAccess = BlobContainerPublicAccessType.Container;
await container.SetPermissionsAsync(permissions);
}

var name = "foo.txt";
using (var stream = File.OpenRead(name))
{
var blockBlob = container.GetBlockBlobReference(name);
blockBlob.Properties.CacheControl = "public, max-age=2678400";
blockBlob.Properties.ContentType = "text/plain";
await blockBlob.UploadFromStreamAsync(stream);
Console.WriteLine("Uploaded.");
}
}


static void Main(string[] args)
{
Func2Async().Wait();
}
}
}

 適当なテキストファイルfoo.txtをプロジェクトに用意しておく。あと接続文字列にあるIPとポート番号は自分の環境に合ったもので。今回はリバプロで、ポート20000に来たものをポート10000に飛ばす。

 NginXを落としてきて、解凍したディレクトリ内にあるnginx.confをリバプロとして動作するように書き換える。その辺に転がってるリバプロサンプルを書き換えて、外部からのC#コードによる書き込みを実行したところ二度エラーが出た。一つはヘッダをもとのままになるように設定することで解決した。もう一つはNginXがデフォではHTTPのバージョンを1.0に書き換えてしまうためにエラーになったので、バージョン1.1で動くように設定。
 最終的に設定ファイルは下記のとおり。
nginx.conf

worker_processes 1;

events {
worker_connections 1024;
}

http {
sendfile on;

upstream app_servers {
server 127.0.0.1:10000;
}

server {
listen 20000;

location / {
proxy_pass http://app_servers;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
}

 nginx.confを用意したら、"start nginx"で起動しておく。

 まずストレージエミュレータを起動する。次に、用意しておいたC#コードを実行して、ストレージエミュレータへの書き込みを実行してみる。エラーなく処理が終了するはず。エラーが出てくる場合、Azureのライブラリがエラーを出すので、本来エミュレータから送られてくるエラーメッセージはそのままでは見られない。ここらはFiddlerを使ってキャプチャしておけば、デバッグに役立つ。


 ストレージエミュレータへの書き込みが成功したら、ブラウザからそのファイルを参照してみる。
http://192.168.11.17:20000/devstoreaccount1/test/foo.txt

成功。

 Azureを使ったWebアプリの開発をしたいが、その開発環境で使うストレージエミュレータがローカルからのアクセスしか受け付けないので、NginXでリバプロを立てて外部(Dockerのコンテナ内を想定)からのアクセスを受け付けるようにしてみた。うまくいった。
 しかしこれ、けっこうメンドウだったのでエミュレータがコンテナで用意できたらなーと思っている。
comment: 0

ConoHaにMongoDBサーバを立てた

 MongoDBを使いたかった。ほんとはMongoDBをホスティングしてるサービスを使いたかったのだが、それだとお値段がお遊びにならない額になるのでconohaのVPSで立てた。適当なデータベースと適当なユーザーを追加して適当にホスティングするメモ(適当==適切)。
・MongoDBのポート変更
・MongoDBにデータベース追加
・データベースのユーザ追加
・遠隔でクライアントを用意して接続、ちょっといじってみる


 conohaのVPSで選べるイメージの中にMongoDBがあるのでそれを選んでVPSを立ち上げた。OSはCentOSの7系だった。
 まずはユーザー追加など基本的なことを済ませる。そのあとのはなし。

・MongoDBのポート変更
 MongoDBのデフォルトのポートは27017である。接続用クライアントはこのことを知っているので、サーバのアドレスをポート抜きで渡すと勝手に27017経由でつないでくれる。しかしこれ、なんと警視庁が「MongoDBを狙ったポートスキャンが頻発している」とリリースを出したことがあるので、ポート変更はしておいたほうがいいと考えている。なのでポート変更。
 MongoDBの設定ファイルである/etc/mongod.confをテキストエディタで開くと、ネットワーク設定があるので、そこのポート番号を編集する。
/etc/mongod.conf

#network interfaces
net:
port: 27017

 ついでにその真下にあるであろうデータベースをいじるユーザの権限を、次の作業のためにちょっといじる。一時的に、つなげられれば誰でもいじれる状態にしてしまう。
security:

#authorization: enabled
authorization: disabled

https://docs.mongodb.com/manual/reference/configuration-options/#security.authorization
 設定を書き換えたら、MongoDBのプロセスを立ち上げなおし。
systemctl restart mongod

 ポートが変わったのでファイヤーウォールを設定する。設定ファイルの書き換え。
/etc/firewalld/zones/public.xml

<port protocol="tcp" port="27017"/>

 設定ファイルを書き換えたらファイヤーウォールの立ち上げなおし。
systemctl restart firewalld



・データベースのユーザ追加
 MongoDBにVPS内からつなぐ。コマンドで"mongo"と打って、MongoDBに接続する。そしてユーザ追加。roleは"read"や、書き込み権限まで付与したければ"readWrite"など。
db.createUser({user:userName, pwd:password, roles:[{role:role, db:dbName}]});


 認証確認。
db.auth(userName, password);

 もろもろ確認。
use admin;

db.system.users.find();

 ユーザが追加できたらctrl+CでMongoDB接続から抜ける。MongoDBの設定ファイルでユーザ権限を元に戻して、MongoDBを立ち上げなおす。


・遠隔でクライアントを用意して接続、ちょっといじってみる
 Pythonでpymongoを使って接続してみる。
from pymongo import MongoClient

client = MongoClinet(ip, port)
db = client.get_database(dbName)
db.authenticate(user, password)

 適切にユーザが作成されていれば、コレクション作成やドキュメント挿入ができるはず。
db.create_collection("foobar")

db.foobar.insert({"foo":"bar"})
db.foobar.count()


 ととのった。
comment: 0

TDD

 一時期「TDDは死んだ」なんて記事が話題になった。
テスト駆動開発(TDD)はもう終わっているのか? Part 1
「TDD、いや、テスト自体がウチには合わない」なんて話を直接耳に入れたりもした。そういう環境ではそうなんだろうと思う。銀の弾丸はない。

 ぼくはTDDをかなり気に入っている。最初にテストを書き、それにパスするようにコードを書いていく。炎上中のプロジェクトにアサインされたときになぜだか個人的に使い始めたのだが、結果としてうっかりなミスやデグレードを防いでくれた。プロジェクトで見ればそのうっかりやデグレードが頻発していたが、ぼくの担当したところに対して差し戻しはなかったので、TDDによって品質をかなり高いところまで持っていけていたんだろう。
 はじめてのTDDを炎上中のプロジェクトへアサインされたときに実践したというのは、時間的な制約から一見無謀だと捉えられるかもしれない。けどぼくはこれが成功だったと考えている。一つの機能の実装が終わったときに自動テストが用意されていなければ、ふつうなら手動でテストを行うだろう。デグレードを考えるなら、行うべきテストは機能が増えるたびに累積されていき、後半ではテストを行うコストが無視できないほど肥大化してくる。ぼくはTDDによってテストを自動化していたために、累積されるテストコストをうまく回避できたと考えている。

 ぼくはTDDにそれほどの厳格さは適用していない。自分が自信を持てれば十分という位置づけで行っている。ユニットテストは自分が適切だと思うレベルでしか書かない。カバレッジ100%にする必要はないし、70%を切るようでも場合によっては全然かまわないと考えている。つか今趣味で開発しているものはカバレッジを参考にしていない。
 テストも機能実装中に刻々と変化する。自分が好きで作っているものにしろ、クライアントのためのものにしろ、仕様が変更されるってのは皮肉なことにしょっちゅうある。さらには、自分のものだとテストを書きながら仕様を決めることだってある。刻々と仕様が変わろうが、ぼくはあまり粒度の高いテストは書かないので、テストの変更は大したコストにならない。
 たとえば今、この自作のブログのエンジンを開発しなおしている。TDDで。最初にまずログイン機能を作ることを決め、書いたのはSeleniumを使ったテストである。テストは二つで合計20行から30行ぐらいだ。厳格にやるならログイン機能をに内包されている、トークン取得やトークン付与を単体テストで書くべきだろうか。さすがにそこまでやるのは手間だ。それらが協調して動いていることは、ログインの成功と失敗が確認できれば十分実証される。だからSeleniumでそれを行うテストを書いた。これから機能が追加されていくが、これ以降でログイン機能が正常に動くかを確認するのにかかるコストは、10秒程度の時間コストしかかからない。

 過度にカバレッジを上げたり、すべてのメソッドに対して単体テストを用意しろなんて命令が来たら賛成しかねる。でもTDDってそういうのを強制するものじゃないだろう。ただ自分の自信になる程度にテストを書き、のちのちのデグレードチェックの手間を省いたり、リファクタリングを安全に進めるための補助として役立てる。すごくいい仕組みだと考えている。銀の弾丸ではないにしろ、ぼくはこれを適用できるところには使っていくし、適用できるできないの判断をうまくできるようになっていこうと思うところ。
 
comment: 0

Dockerは本番運用でばかり使うものでもないという話

 今年のデヴサミではDockerを使って運用環境を便利にしたってな話をぼちぼち聞いた気がする。一方で、Dockerなんて本番環境の運用で使うんじゃないよ、っていう話題が出てきたりしている。デヴサミでDockerを導入したって発表をしてたところは、自分のところで、自分たちの目的に合うように、しっかり知見を貯めていた。その上で本番環境への導入をしていた。お上から「なんじゃ、Dockerを入れると工数削減できるらしいじゃないか」という鶴の一声で唐突に、走っているプロジェクトに入れることになれば災厄になりかねないとは思う。
 そんな話題もあるが、Dockerってすごく便利だと思っていて、本番運用だけで使うものでもないなーと考えている。なので「とりあえず」でDockerを使えるような自分(イチ開発者)なりの使い方を書いておく。先に一つ書いておくと、GUIがないとダメなようならちょっとつらいものではある。

 まず、OSはなんでもいいがDockerは入っているものとする。
 たとえばMySQLを使っているが、MongoDBをちょっと検討したくて、軽くさわってみたくなったとする。通常なら、開発マシンや仮想マシンにインストールして立ち上げたりする。DockerならMongoDBサーバの用意にコマンド一つ。
docker run -p 27017:27017 mongo

これで少し待てばローカルにMongoDBサーバが立ち上がる。あとは自分の使いたい言語でMongoDBとつなぐライブラリを用意してつなぐだけ。Pythonでならpymongoをpipなどでインストールして下記。
from pymongo import MongoClient

client = MongoClient()

 Dockerのコマンドでやっているのは、コンテナと呼ばれるものを走らせる指示と、そのコンテナとクライアントのポートの結び付けだ。ここではmongoというコンテナを走らせろと指示をしており、mongoというコンテナイメージを持ってなければパブリックリポジトリへイメージを探しに行く。
 パブリックリポジトリはOSベンダやらなんやら様々なソフトベンダがイメージを提供している。UbuntuやCentOS、Debianなどのイメージから、RailsやDjangoなどのWebフレームワークがすぐ使えるもの、MySQLやMongoDBなどDBがすぐに使えるものなど、さまざまなものがそろっている。

 コンテナは仮想化の一種なので、依存関係的な意味ではクライアント環境は汚れていない。ただコンテナイメージは使いたいソフト+それが実行される環境(OSなど)の容量を持つため、物理的な容量は多少持っていかれる。けど動作としてはたいして重くはならない。コンテナによる仮想化は、従来の仮想化を使うより軽くなる仕組みになっている。昨今のDockerの盛り上がりを受けて、コンテナとしてAlpineというセキュリティを考慮された数MB程度しかないOSを使うのが流行っている。

 Dockerを使うことで、容易にちょっと触ってみたいDBサーバを用意できた。これをこのまま開発用のDBにしてもいいだろう。仮想環境に用意されたので、依存関係でクライアント環境を汚す心配はない。



 Dockerの使い方をもう一つ。Webアプリケーションの実行環境を用意してみる。例としてここではNodejsで。
 ぼくは開発にWindowsを使っている。Macを開発に使っている人も多数いるだろう。けどそんな人たちの本番サーバはなにかしらのLinuxディストリビューションであることが大半だ。開発環境と本番環境のOSが違うってのは嫌だ。そろえたい。これまでメジャーであったVirtualBoxのようなもので、仮想マシンを立ち上げる?それは手間だし、仮想マシン内でコマンドをいじっているうちに、その環境が秘伝のタレになってきて、本番環境で再現できない可能性だってある。じゃあDockerを使う。

 Dockerを使う前にアプリを準備する。ここでは簡単なもので済ませる。Nodejsで走らせるとポート8000番で待ち受け、Hello World!を返す単純なやつ。
./scripts/app.js

const http = require('http');

const port = 8000;

const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});

server.listen(port);

console.log(`Server running at port:${port}/`);


 続いてDockerfileを用意する。ここにはどんな環境を用意して、アプリ実行前にやる事前準備をして、ではいざ実行というプロセスを書くことになる。
./Dockerfile

# Nodejsが入っているAlpine OSを使う
FROM node:7.10.0-alpine

# RUNに続くコマンドを実行する
RUN mkdir /app

# クライアントのカレントディレクトリにあるファイルをコンテナの/appへコピー
COPY . /app

# コンテナでコマンドが走るときのカレントディレクトリを設定
WORKDIR /app/scripts

# ポート待ち受け設定
EXPOSE 8000

# あとで走らせるコマンド(Nodejsの実行)
CMD node app.js

 以上二つで必要なファイルはそろった。./Dockerfileと./scripts/app.jsの二つのファイルがカレントディレクトリ以下にある。二つコマンドを打てば、アプリを動かすサーバが立ち上がる。
 まずコンテナイメージの作成。Dockerfileで最終行のCMDの前までを実行し、環境イメージが作成される。
docker build -t node_app

 環境イメージができれば、あとはそれを使って実際に走るコンテナを作る。
docker run -p 8000:8000 node_app

 アプリ作成後は、デプロイまで単純な二つのコマンドで済んでいる。便利。

 実際にDockerでNodejsのWebアプリやるなら、ユーザを用意して、実行はそっちに切り替えようねという話など参考になるのが下記。
http://postd.cc/lessons-building-node-app-docker/


 さいきん開発の遊びに使っていたPCを修理に出したので、手元に予備マシンしかなかった。これだとDBの準備がめんどうだなーと思っていたが、Dockerですんなり解決した。
 Dockerは仮想化の一種であり、クライアント環境を汚さないという恩恵がある。あとはHyper-Vみたいなので仮想マシン一台を丸々立ち上げるより、いろいろ軽い。だから本番のデプロイが便利になる以外にも使い道はたくさん出てきそう。


知らないおじさんの作ったDockerfileとかイメージを無邪気に使っちゃダメ。セキュリティ的に。
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