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内のローカル変数をテンプレートで使うための記述を追記。

knife ec2でインスタンスの初期設定(RVM+Ruby1.9.2編)

前回はnodeのchef-clientを動かすためにrbelリポジトリRubyを使っていた。特にこだわりがなかったり、動かすシステムでRubyを使うわけじゃなければ、RPMだと楽でいいですよね。
ただ、セットアップするnodeでRubyを使いたい、RVMで複数バージョンのRubyを使いたいなんてことがある場合もあると思う。
であれば、bootstrapでインストールするRubyもRVMでインストールしちゃえばいいじゃない。

という訳で、基本的には前回と同じで、bootstrapの中身を変えればOK。セットアップ対象はCentOS6で、Ruby1.9.2をインストールしてデフォルトRubyにします。

$CHEF_REPO/.chef/bootstrap/centos6-rvm.erb を以下のように作成。

bash -c '
<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>

yum install -y gcc bison autoconf patch git curl readline-devel libxml2-devel libxslt-devel wget man

if [ ! -f /usr/local/rvm/bin/rvm ]; then
  bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer )
  source /etc/profile.d/rvm.sh
  rvm install 1.9.2
  rvm use 1.9.2 --default
fi

gem install ohai --no-ri --no-rdoc
gem install chef --no-ri --no-rdoc
ln -nfs $(which chef-client) /usr/bin/chef-client

mkdir -p /etc/chef

(
cat <<'EOP'
<%= validation_key %>
EOP
) > /tmp/validation.pem
awk NF /tmp/validation.pem > /etc/chef/validation.pem
rm /tmp/validation.pem

(
cat <<'EOP'
<%= config_content %>
EOP
) > /etc/chef/client.rb

(
cat <<'EOP'
<%= { "run_list" => @run_list }.to_json %>
EOP
) > /etc/chef/first-boot.json

<%= start_chef %>'

上記bootstrapテンプレートが出来上がったら、前回使用したknife ec2コマンドの -d オプションでの指定を今回作ったものに変えてあげる。

$ knife ec2 server create -G security_roup -I ami-12345678 -f t1.micro -S key-pair-name -i ~/.ssh/private-key.pem -x ec2-login-user -d centos6-rvm -r 'rolw[rails_server]' --region ap-northeast-1 -Z ap-northeast-1a


しばらく待てば、はい完成。(microだとちょっと時間がかかります。)