Qiitaに書かなくなっているのは、技術的なネタが頓挫していることに他ならないので、ここは自分を追い込んでいかないといけないとSoftLayer Advent Calendar 2015に登録してみたわけです。こうでもしないと、技術ネタがないのかという感じですが、イロイロあって、全然書けなかったんです。来年からは増えると思います(さらに追い込む)。
さて。最近、社内のPython勉強会で、Bluemixの上でPythonを使って、SoftLayer の APIを叩いてみよう! という企画があり、そんなネタでPythonコードを書いて、恥ずかしながらgithubに公開しています。今回は、このコード自身のことではなく、このようなコードを書かなければならなくなった背景について、ちょっと触れておかねばならぬかなと考え、筆をとった次第です。自分で書いたネタを説明するのがめんどくさくなったわけではないです。断じて。
これ以降を読むための前提知識と対象
- Bluemixでなんかできる
- SoftLayer API または slcli(旧sl)コマンドは叩いてみたことがある
-
cf push
と聞いても不思議に思わない - それでいて、あまりweb系の開発はしたことがない、特にインフラエンジニア
ものすごく限定的ですが、限定されたような人にしか、たぶん役に立たないです。
Webアプリで SoftLayer APIなどを叩くときの考慮ポイント
タイトルが地味に変わってますが、実は BluemixとかSoftLayerとか限定的な話でもなく、一般的な概念です。今回、たまたま Bluemix上で SoftLayer の APIをたたいたコードを書いたがために、そのような限定的な書き方になっています。コードも載せようと思ったとき、試そうと思ったとき、そのコードが存在しているほうがいいですよね。良いにちがいない。
さて。SoftLayerのAPIやslcliを叩いてバッチを書いてみたことがあるインフラエンジニアの諸君(限定すんなよ)、USER IDとAPIキーはどうしましたか?
私の予想を裏切らず、ほとんどのみなさんが、shellの中に USER IDと APIキーを記述していると睨んでいます。ちがいますか? もし、shell の中に書いていないよ、と言う人がいたら、これ以降はあえて読む必要はないかも知れません。
APIキーを、そのままプログラム中に書くのはやめよう
唐突にタイトルで考慮ポイントを示しましたが、これです。APIキーに限った話ではありませんが、変更の可能性や、複数の選択肢を示して利用するようなデータは、プログラム中にハードコードするのではなく、外部に持たせることが一般的というか、「当たり前」です。
なぜか
データやパスワードには変更が発生する場合がありますが、その時プログラムを変更しなければならないということは、問題をはらんでいるわけです。ぱっと思いつくだけでも、二つあります。
セキュリティ上の問題
そのプログラム自身をバージョン管理している場合やプログラム自身にアクセスできる場合、そのデータの内容が見えてしまうことが分かっています。ですから、見えては困るようなキー
やパスワード
をプログラム中に書いておくと、見えてはならない人に見えてしまいます。SoftLayer の APIキーもパスワード同様、漏洩してはならない重要なデータ項目ですので、プログラム内に直接記述することはやめましょう。
プログラムとデータは分離して管理できるようにしておくことが、セキュリティ上は望ましいです。
管理上の問題
もう一つは、データの内容を変えてコミットする度に、プログラムロジックの変更は一切していないのに、リリースが上がってしまうことです。リリースが上がってしまうことで、そのロジック自身は元より、アプリケーションのリグレッションテストを実施しなければならないという掟があることでしょう。掟がなくても、CIが自動的にテストを走らせるかも知れません。リソースと時間のムダですね。また、プログラム自身に変更はないのにリリースだけが上がっていくことで、一体、変更箇所を記録していくことも、ムダ以外になにものでもありません。
その点、データをロジックとは別の場所に保管しておけば、変更が発生した場合でも、アプリケーションロジックの再テストは不要と言えるでしょう(やりたい人はたくさんいるでしょうが)。
従って、SoftLayaer の APIキーは、プログラム中にハードコードせずに分離して、どこか別のところに書きましょう。
では、どこに? それには、次の4パターンがありますので、各々について説明します。
- APIキーを入力させる
- APIキーを別ファイルに保管しておく
- APIキーをDBへ登録しておく
- APIキーを環境変数へ登録しておく
1. APIキーを入力させる
いちいち、入力フィールドを使って入力させる方法です。どこにもデータが保管されないので、セキュリティ的にも( ´∀`)bグッ!とか考える人がいるかも知れません。ですが、Bluemixは何もしないでフツーに使うと「http」です。また、毎度、あの長いものを入力させることはユーザビリティが下がるという事もありますが、どこか別の場所にAPIキーを保管しておいて、そこから漏洩させる危険性も孕んでいます。
そもそも、入力させるサイト自身がフィッシングサイトだったら...。など考えると切りがないので、この方法はあまりオススメではなさそうです。
2. APIキーを別ファイルに保管しておく
よくやる方法です。ただ気をつけるべきことが 3点あります。外部からはアクセスできない、必要なユーザ以外はアクセスできないことが必要不可欠です。ただし、このような場合は同一サーバ内にプログラムとデータファイルを配置することが多く、プログラム管理者が適当やると、データにアクセスできるような状況下になりかねません。すると、一開発者がデータファイルへ簡単にアクセスできてしまうので、その視点からも注意が必要です。
- 静的コンテンツとして、外部からアクセスできない場所に配置すること。Apacheでいう
DocumentRoot
よりも上位のディレクトリなど。 - 静的コンテンツとしてアクセスできない、利用すべき開発者だけがアクセス可能なユーザ権限にしておくこと。
- 可能ならば暗号化しておくこと。ただし、そのキーはどうするの、という問題は常にはらむ。
外部ファイルとして、背後のファイルサーバなりに配置して、データファイルの管理者を別にする方法もあるでしょうが、そのファイルへのアクセス方式が厄介な気がしますね。なんとなく。
3. APIキーをDBへ登録しておく
今のトレンドはコレであるだろうことがコレです。変更可能データは全て、他のデータ同様、DBへ配置します。コレの何が良いかというと、データの管理がプログラムの管理者とはまったく異なるように手配しやすいことです。とは言え、気をつけるべきことはあるわけです。
- プログラムからDBへアクセスできるユーザは、必要最低限のアクセス権のみとする。アプリケーションを利用するユーザによって、DBへアクセスできるデータ自体を制御できると、開発者も置いてけぼりにできます。
- アプリとDBは分けておこう。論理的に別のサーバにしておいて、DBサーバには、プログラムからでしかアクセスできないようにしておけば、DB内のデータを自由に見ようという方法は少なくなります。多少は安心でしょう。
- それでもやっぱり、暗号化しておこう。この場合も暗号化キーをどうするかという問題はついてまわりますが、それ以前に、データへのアクセス制御が細やかにできていれば、それほどでもないです。これは、データ管理者が、そのレコードへアクセスしても分からないようにするため、という側面で暗号化を実施します。
4. APIキーを環境変数へ登録しておく
shell から子プロセスとして起動されるアプリケーションサーバであれば、環境変数に指定しておく方法も可能です。
とは言え、常に実行者が環境変数に値を定義してから、アプリケーションを起動させる方式をとらねば、環境変数でも安全みたいな方式はとれません。とてもユーザビリティの悪い方法です。特にBluemixで実行させようと考えたとき、manifest.yml
にその実行方法と、実行するためのshell script などを指定して起動させることになります。この場合、結局、shell script内にデータを環境変数に割り当てる命令を入れることになりますので、管理方式は別ファイルに保管することと変わりはないでしょう。取りあえず、起動するshellを書く人と、ロジックを書く人とを分離することくらいはできるでしょうが、お互いのファイルは丸見えでしょうね。
他に...?
さっき、他に方法を思いついたのに忘れちゃいました。思い出したら書きますね(´・ω・`)
今回、実現した方法
この度私が実装した方法は「APIキーをDBへ登録しておく」という方法を選択しました。
Djangoでは標準でユーザ管理機能を持っていますので、そのユーザと、DB内に登録された「username」「API KEY」を紐付けて、ユーザ毎に対象の SoftLayerサイトを変更できるようにしました。なんか、そのほうが便利そうだったから。
今回、対応した SoftLayer のユーザが登録している、アカウントの一覧を表示する部分までを作りました。ソースコードでは accounts/views.py
にあります。
def account_list(request):
loginname = None
if request.user.is_authenticated():
loginname = request.user.username
username = CustomUser.objects.get(username=loginname)
user = softlayer_api.objects.get(pk=username.softlayer_id)
client = SoftLayer.create_client_from_env(username=user.username, api_key=user.api_key)
acct = client['Account'].getUsers()
return render_to_response('account_list.html', {'accounts': acct}, context_instance=RequestContext(request))
この def
宣言の前に @login_required
を入れているので、認証されたログインユーザでしか、ここにはアクセスできないようになっています。それでも、最初に loginname
に、ちゃんと認証されてアクセスしてきたユーザであることを確認しています。
そのloginname
をキーに、DBに登録された SoftLayerの情報を読んできています。username=
やuser=
の部分です。この時user
変数には、DBへ登録されているSoftLayerの username
api_key
情報が含まれています。
その次の client=
の部分で、SoftLayer API を、指定された SoftLayer Userで実際にコールしています。acct=
では、そのユーザ情報から .getUsers()
で登録されている SoftLayer のユーザ情報を全て取得してます。最後に、その情報を全て表示するようにrender_to_response
してるわけですね。
このように、プログラム上からは、SoftLayer の username
やapi_key
といった情報を知ることはできないようになっています。またログインしたユーザによって、SoftLayer のユーザを切り替えることができるようにもなっていて、結構いいんじゃねってちょっと自分で作って陶酔しました。
まとめ
Bluemix上のWebアプリから、SoftLayer APIを叩きたいとき、次のようにすると良いでしょう。
- API情報は DBへ保管する
- DBはアプリとは論理的・物理的に別の場所にしておく
- プログラムとデータの管理者は分ける
今回作成したサンプルプログラムの使い方は、ちゃんとgithubに載せてありますので、その通りやって試してみてください。動くはずです。
ただ、sqlite でデータの暗号化も何もしてないので、DBを別サーバにしてみたりしてみてください。