ChefでのMySQLパスワードの扱い

opscodeのリポジトリにあるMySQLのcookbookでは、rootユーザやレプリケーション用のユーザのパスワードをランダムに生成して設定している。

opscode の recipe の特徴

このランダムという点をカバーするべく、うまい仕組みが組み込まれている。

パスワードを設定するところは

node.set_unless['mysql']['server_root_password'] = secure_password

といった形で、attributeに設定されていない場合はランダムに生成するという事をして、2度目以降も同じパスワードとなるようになっている。

2回目以降も同じパスワードを保証するために、もうひとつの技が

unless Chef::Config[:solo]
  ruby_block "save node data" do
    block do
      node.save
    end
    action :create
  end
end

ここの node.save というやつ。
普通であれば、recipe(run_list)の実行がすべて完了するまではattributeがサーバに保存されないんだけど、このメソッドを使うことで、即時に保存される。これで、万が一rootのパスワードが設定されたあとのどこかでコケても、同じパスワードで設定を続けることができる(上のコードにあるようにchef-soloのときは実行されないけど)。また、masterは途中でコケたけど、スレーブはそこで設定された(であろう)であろうパスワードを使ってセットアップだけは続けることができる。

これはよく考えられた仕組みですよね。

Encrypted Data Bag

ただ、ランダムであれば推測されづらくていいかもしれないけど、好きなパスワードを設定したいし、アプリからのアクセスを考えるとすべてのサーバでアプリ用ユーザは共通にしておきたいといった要望もある。また、提供されているrecipeのままだったりattributeにそのまま設定してしまうと、平文のパスワードがattributeの一覧から確認できてしまう。

そんな時に便利なのが、Encrypted Data Bagという平文で保存したくないデータをChefに保持しておくための機構。Encrypted Data Bag を使うために必要なのはencrepted_data_bag_secretと呼ばれる共通鍵だけ。
※やってることはOpscodeのサイトに書かれていることと全く同じです。

共通鍵の作成

# openssl rand -base64 512 | tr -d '\r\n' > /etc/chef/encrypted_data_bag_secret

これをEncrypted Data Bags利用する各クライアントと共有する。共有方法は初回セットアップであれば、knifeコマンドのbootstrapで指定してあげれば良い。その方法は後ほど。

knifeコマンドで暗号化されたデータをdata bagsに保存

passwordというdata bagにmysqlという項目を作成します。ここで指定するmysqlというのは、data bagのIDとなり、これは暗号化されません。

# knife data bag create --secret-file /etc/chef/encrypted_data_bag_secret passwords mysql
[editorが開くので以下のように入力して保存]
{
  "id": "mysql",
  "user": "root",
  "pass": "your_password"
}

※EDITOR環境変数を設定していないとErrorになります。

作成したdata bagを確認してみましょう。

# knife data bag show passwords mysql
{
  "id": "mysql",
  "pass": "trywgFA6R70NO28PNhMpGhEvKBZuxouemnbnAUQsUyo=\n",
  "user": "e/p+8WJYVHY9fHcEgAAReg==\n"
}

userとpassが暗号化されて保存されています。
では、復号化して表示してみます。

# knife data bag show --secret-file /etc/chef/encrypted_data_bag_secret passwords mysql
{
  "id": "mysql",
  "user": "root",
  "pass": "your_password"
}

先程入力したものが表示されました。

Recipeから Encrypted Data Bagのデータを呼び出す

実際に recipe からdata bagの値を呼ぶには、以下のような感じにしてあげます。

共通鍵を直接指定する場合

mysql_data = Chef::EncryptedDataBagItem.load("passwords", "mysql", secret)
user = mysql_data["user"]
password =mysql_data["pass"]

共通鍵ファイルがデフォルトの /etc/chef/encrypted_data_bag_secret に配置されている場合はsecretを指定しなくてもOK

mysql_data = Chef::EncryptedDataBagItem.load("passwords", "mysql")
user = mysql_data["user"]
password =mysql_data["pass"]

ここで、ユーザ名やパスワードを node['mysql']['user'] とやってしまうと、せっかく暗号化して保存されているユーザ名とパスワードが平文でattributeに登録されてしまいますので、やめたほうがいいと思います。data bagに保存されているので、次回以降に変わるという事も無いですしね。

recipe内で定義したローカル変数のままだとテンプレートに渡せないので、templateリソースの中で以下のように定義すればテンプレート内で呼び出せる。

template "hoge" do
  source "hoge.erb"
  ...
  variables(
    :user => user,
    :pass => pass
  )
end

これでめでたくattributeに平文のパスワードが登録されることなく、自分の好みのパスワードが設定出来ました。

bootstrap で encrypted_data_bag_secret を渡す

初回セットアップのサーバの場合には、validation.pemやchef_server_urlを新規クライアントに設定するために bootstrap の仕組みを利用する。この中で、encrypted data bagの共通鍵をセットアップするサーバに配置することができる。

以下のようにbootstrapファイルに書く。

(
cat <<'EOP'
<%= encrypted_data_bag_secret %>
EOP
) > /etc/chef/encrypted_data_bag_secret

encrypted_data_bag_secret など、bootstrap で使用される値などは、knifeを実行するノードのknife.rbに設定されているものが利用される。そのため、encrypted_data_bag_secretファイルの場所もknife.rbに指定しておく必要がある。

encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret"

これで、knifeコマンドからbootstrapファイルをして初期実行をすれば、recipeを実行するクライアントでも問題なくdata bagを復号化できる。


(追記:2012-02-12 16:25) recipe内のローカル変数をテンプレートで使うための記述を追記。