kikumotoのメモ帳

インフラ・ミドル周りを中心に、興味をもったことを適当な感じで。twitter : @takakiku

h2oのsystemd unitファイル

h2oのsystemd unitファイルってみなさんどんな感じなんでしょう?

https://github.com/h2o/h2o/issues/84 をみつつ、& でバックグラウンドってのもどうなの?って気がしたので、自分は以下のように書いてます。

[Unit]
Description=h2o optimized HTTP server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=simple
WorkingDirectory=/etc/h2o
PIDFile=/var/run/h2o.pid
ExecStart=/usr/local/bin/h2o -m master -c /etc/h2o/conf/h2o.conf
ExecReload=/bin/kill -HUP $MAINPID
PrivateDevices=yes
PrivateTmp=true
LimitNOFILE=infinity

[Install]
WantedBy=multi-user.target

-m masterはforegroundで動作するから、Type=simpleでOKですよね。 Type=simpleだからPIDFile別にいらないかぁと思いつつ書いてます。

HashiCorp Vault + LDAP で MySQL のアカウント管理

ようやくはてなブログに移りました。 ということでその最初の記事を書く。

LinuxのアカウントはLDAP(やらIPA Server)とか使えば統合管理できるのだけど、MySQLのアカウント管理を一箇所で統合的に管理しようと思うと、その権限なども含めるとなんかよいソリューションがないように思える。 イヤ、こういうのあるよってあれば教えてください。

そこで、HashiCorpのVaultとLDAPを組み合わせると、ちょっとそれらしくMySQLのアカウント管理できるのではないかと思ってそれを試してみた。

Vault 起動

今回は、おおよそこういうことができる、ということを確認することが目的なので、Vault自体の可用性とかまで考えない。

dev モードで起動する。

$ vault server -dev

表示される root Token を使って、root として Vault にログインする。以下のような感じ。

$ vault auth 4a1eea82-facd-c7d3-e9c7-c0bb4b04e81f

MySQL Secret Backend の設定

Vaultには機密情報(Secret)を保持したり生成したりするコンポーネントである Secret Backends というのがあり、そのうちの1つに MySQL のアカウントを生成しその権限も設定してくれる MySQL Secret Backend というのがある。

今回の目的のためにこの MySQL Secret Backend を利用する。

複数のデータベースを想定して(ここでは1つしか設定しないが)、db1 というパスにマウントする。

$ vault mount -path=db1 mysql
Successfully mounted 'mysql' at 'db1'!

db1に対応するデータベースへの接続情報を登録する。GRANT OPTION権限をもつユーザを登録する必要がある。

$ vault write db1/config/connection value="admin:adminpass@tcp(db1.example.com:3306)/" 
Success! Data written to: db1/config/connection

このMySQL Secret Backedが払い出してくれるアカウントとパスワードの有効期限を設定する。

$ vault write db1/config/lease lease=10m lease_max=1h
Success! Data written to: db1/config/lease

Roleを登録。以下では、"readonly" という Role を登録している。権限は任意のDBに対するSELECT だけ。

$ vault write db1/roles/readonly sql="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT SELECT ON *.* TO '{{name}}'@'%';" 
Success! Data written to: db1/roles/readonly

強い権限をもつアカウントが必要であれば、さらに別の Role を作ればよい。

LDAP Auth Backendの設定

認証し、ユーザにVaultのポリシーを割り当てる Auth Backends というのがある。そのうちの1つにLDAPを使って認証し、LDAPのグループとポリシーを対応づけてくれる LDAP Auth Backend というのがある。

自分の環境では IPA Server が稼働しており、こことつなぐためにこの LDAP Auth Backends を使う。

下記のコマンドで有効にする。

$ vault auth-enable ldap
Successfully enabled 'ldap' at 'ldap'!

IPA Server(LDAP)への接続情報を登録。

$ vault write auth/ldap/config url="ldap://ipa.example.com" \
         userattr=uid \
         userdn="cn=users,cn=accounts,dc=example,dc=com" \
         groupdn="dc=example,dc=com" \
         upndomain="EXAPMPLE.COM" \
         insecure_tls=true \
         starttls=true
Success! Data written to: auth/ldap/config

ポリシー

db1 に Role "readonly" としてアクセスする情報を読むことができる Policy を作成。 readonly.hcl として下記内容のファイルを用意。

path "db1/creds/readonly" {
    policy = "read" 
}

このポリシーをポリシー名 "readonly" として登録。

$ vault policy-write readonly readonly.hcl
Policy 'readonly' written.

IPA Server(LDAP)のグループ: "readers" に "readonly" ポリシーを割り当てる。

$ vault write auth/ldap/groups/readers policies=readonly
Success! Data written to: auth/ldap/groups/readers

動作確認

以上の設定により、IPA Serverのグループ:"readers"に所属するユーザで、Vault にログインすると、db1/creds/readonly を読むことができ、その結果 SELECT のみが許可されるユーザ・パスワードが払い出される。

Vault にログインする。

$ vault auth -method=ldap username=kikumoto
Password (will be hidden): <IPA Serverのユーザ kikumoto に対するパスワード>
Successfully authenticated! The policies that are associated
with this token are listed below:

readonly, readonly

このように、readonly というポリシーが関連づけれらたことがわかる。

引き続き、DBアクセス情報の取得。

$ vault read db1/creds/readonly
Key                Value
lease_id           db1/creds/readonly/3bc89d58-fe04-8e2e-0ab4-419cacc618eb
lease_duration     600
lease_renewable    true
password           8c0f7f32-1739-1911-41e5-c9565af7289a
username           ldap-kikum-c8129

この username / password を用いて db1.example.comMySQL にアクセスできる。

$ mysql -u ldap-kikum-c8129 -h db1.example.com -p
Enter password:

> create database mydb;
ERROR 1044 (42000): Access denied for user 'ldap-kikum-c8129'@'%' to database 'mydb'

という感じで、CREATE はできない。

そして10分ほど経過すると、Vault のログに下記のような表示されて、アクセス情報が廃止されたことがわかる。

2015/09/06 21:22:53 [INFO] expire: revoked 'db1/creds/readonly/3bc89d58-fe04-8e2e-0ab4-419cacc618eb'

そして、MySQL にはアクセスできなくなる。

$ mysql -u ldap-kikum-c8129 -h db1.example.com -p
Enter password:
ERROR 1045 (28000): Access denied for user 'ldap-kikum-c8129'@'localhost' (using password: YES)

課題

ポリシーに正規表現が使えないので、対象DBノード数が増えると、その分だけポリシーファイル内のエントリーが増える。 これは Consul Template で解決できればまだ助かるかなぁ。でも、正規表現欲しい。

Vaultがダウンしていたりとかすると DB にゴミユーザが残る気もするので、このあたりどこまで面倒見てくれるのかもう少し確認が必要そう。それでもゴミが残るケースはあると思うので、適宜ゴミ掃除をする仕組みがいるかも。

まとめ

Vault + LDAP を使うと MySQL のアカウント・権限を一元管理的なことができそうなことを試した。

そこそこいけそうな感じではあるが、まだまだ確認しなければいけないことはある。

あとは、Vaultについて詳しいかたと話してみたい。

また、進展があればエントリーを書こうと思う。

いじょう -

[YAPC] YAPC::Asia 2014 感想エントリ

最近、全く、Qiitaにも何も書いてなくて、ほんとに久しぶりの記事作成です。誰にというわけでなく、ごめんなさい。。。

ということで、YAPC::Asia 2014 に参加してきました。今年も個人スポンサーで参加させてもらいました。
パーカー、とても良いですね!8月だというのに、気温が高くないせいで、パーカーが早速役立つとは思いませんでした。それに、これがあればデータセンター作業も安心ですね(嘘、今は行くことはない。。。)。

主に、インフラ関係のトークを聞いてきましたが、今年は本当にどのトークも人が一杯で、自分のようについにアラフィフとなった体には椅子にすわれないのは苦行になってしまいます。
もっと広いところ、どなたか貸してくれませんかねー。

懇親会とか休憩時間などに、前にしばらくだけ在籍した会社の方々と話すことができたので非常によかったです。@takhito さん、 @kenjiskywalker さんに感謝です。個人的には @songmu さんが覚えていてくれたのはとても嬉しかった。
単にトークを聞くだけでなく、こういう機会も得られる YAPC はやはりサイコーですね。

ところで、なぜ今更、というツッコミがありそうなのですが、Perl入学式 in YAPC::Asia に参加しました。
知識の整理と、人間関係の拡大のために、参加させていただきましたが、大変良かったです。@xtetsuji さん、@hoto17296 さんお世話になりました。
次回も参加予定です(体が元気なら、、、)。

以上、まとまりのないエントリーを終わりにします。
ただ、来年も是非参加しようと思います。それまでまた日々精進します。

最後に、スピードの皆様、スタッフの皆様、大変ご苦労様でした。ありがとうございました。

[nginx][mpeg-dash] 第1回 html5j TV部 勉強会LT枠で参加して玉砕してきた

ブログに書くまで勉強会、という古き良きルールのもと、
第1回 html5j TV部 勉強会 に参加してきた、記事です。

今回は、一般枠が埋まってしまったのでLT枠で参加してきました。
通常セッションの方は、Azureやるなー、普及率なめんな、という感じで非常に勉強となりました。
LTの方も他の人のセッションは、勉強会らしくその場で問題解決もあるなどすばらしかったです。

詳しくは以下あたりを見るのがよいんじゃないかと思います。

で、自分の資料は、以下です。


動画にしっかり記録されてしまっていますが、デモが動かず玉砕してきました。
LT前の動作確認では動いていて、(自宅遠いので先に帰ったのですが)帰宅後再確認すると問題なく動くという、まさにデモでよくありがちなパターンを踏んでしまったという流れでした。

機会があるなら是非リベンジLTをしたいですね。

ということで、今後ともお邪魔させていただきたい勉強会の1つになりました。
よろしくお願いします。

      • -

Qiitaに書いた記事(2013/10〜2014/01)

ここに書くのは久しぶりです。
最近は、ノウハウ的なのは Qiita に書くようになっているので。

なので、あいた期間に書いた記事を貼っておきます。そんなに書いてないけど。

な感じですね。

YAPC::Asia Tokyo 2013に参加してきました

ブログに書くまでがYPACということで、遅まきながらそのエントリ。
今回個人スポンサーとして参加させていただきました。提灯をいただいてきました。ありがとうございます!

Perlに限らずエンジニアの熱い思いを聴けたすばらしいカンファレンスでした。
ホントいろんな刺激をうけてきた感じがするので、今後のエンジニア/マネージャ(管理じゃなくて)としての日々の活動に活かせていければと思う。

今回はただトークを聞きにいっただけとなりましたが、@barimiさんのLTであったように、確かに何か話すことがこういうカンファレンスを一番楽しむ方法であると思ったので、自分も今後なにか話したいという刺激も受けました。

もっとも直近したこといえば

という感じで、どれも先人の後追いであるので、目新しさがないですね。
でも、興味があるという方がいるならどこかで話してみたいので、@takakiku までお声がけくださいませ。

というにかく、YAPC::Asia Tokyo 2013のスタッフ、スピーカーをはじめ、皆様大変ありがとうございました。
大変すばらしいカンファレンスだったと思います!

来年がどういう開催の形になるのか大変楽しみですし、Perlあまり書かないけれど、なんらかの形で関わればと思います。

YAPCサイコー!

WebPay3分後のあと〜顧客の詳細をまねる〜

WebPay: 開発者向けクレジットカード決済サービスを使う流れになりそうなので、ちょっと実際に試してみることにしたので、そのメモ。

アカウント作って、3分で分かる使い方に従って、課金したり顧客を作ったりした後にダッシュボードを見ると、課金情報とかが表示されるようになっている。
で、顧客の情報をクリックすると顧客の詳細情報が表示されるようになりますが、これをどうプログラムで取得するかについて勉強がてら調べてみた。

今回書いたサンプル的なコードの全体はGistにあげておいた。
Rubyを使いました。

顧客情報の詳細

これは、Customerオブジェクトを取得して、それから直接的に取得できる。
コードは以下のような感じ。

customer = Stripe::Customer.retrieve("cus_c4F1nGgK7eEwegF")

puts "顧客情報の詳細"
puts " ID: #{customer.id}"
puts " 作成日: " + Time.at(customer.created).strftime("%Y/%m/%d %H:%M %Z")
puts " メールアドレス: " + (customer.email ? customer.email : "未登録")
puts " メモ: #{customer.description}"

あまり説明もいらないと思う。

クレジットカード情報

Customerオブジェクトのactive_card属性から情報を取得できる。

card = customer.active_card
puts "クレジットカード情報"
puts " 名前: #{card.name}"
puts " カード番号(下4桁): #{card.last4}"
puts " 有効期限: #{card.exp_month} / #{card.exp_year}"
puts " タイプ: #{card.type}"

カード番号全体は取得できないようになっている。下4桁のみ取得可能。

課金履歴

最初に悩んだのがこの課金履歴。
Customerオブジェクト自体から取得するものではなく、Chargesの方から検索するような感じとなる。

puts "課金履歴"
charges = Stripe::Charge.all(:count => 100, :customer => "cus_c4F1nGgK7eEwegF")
charges.each do |charge|
  puts " 金額 => #{charge.amount} (#{charge.currency}), メモ => #{charge.description ? charge.description : charge.id}, 課金日時 => #{Time.at(charge.created).strftime("%Y/%m/%d %H:%M %Z")}"
end

注:実際は課金履歴が100件以上の場合も考慮して記述する必要がある。

加入中の定期課金プラン

CustomerオブジェクトのSubscriptionオブジェクトから情報を取得する。
またSubscriptionオブジェクト内のPlanオブジェクトからも情報を取得する。

subscription = customer.subscription
puts "加入中の定期課金プラン"
puts " ステータス: " + (subscription ? "加入中" : "プランに加入していません。")
puts " プラン: #{subscription.plan.name}#{subscription.plan.amount} / #{subscription.plan.interval})"
puts " 次回の課金日: " + Time.at(subscription.current_period_end + 1).strftime("%Y/%m/%d %H:%M %Z")

少なくとも次回に課金があるかのチェックは必要なはず。
その上で、次回の課金日がこの方法でよいのか疑問。だけど、この次回の課金日という明示的な情報がないので、これが一番妥当そう。

定期課金の請求情報

これはChargesと同様にInvoicesから検索するような感じとなる。

puts "定期課金の請求情報"
invoices = Stripe::Invoice.all(:count => 100, :customer => "cus_c4F1nGgK7eEwegF")
invoices.each do |invoice|
  puts " 金額 => #{invoice.total} (#{invoice.currency}), ID => #{invoice.id}, 請求日時 => #{Time.at(invoice.date).strftime("%Y/%m/%d %H:%M %Z")}"
end

とりあえずざっとこんな感じで、ダッシュボードで表示される情報と同等なデータを得ることができた。
つづく(?)