アルパカchef日記3日目 data bagについて / またはユーザ管理クックブックなど

2日目の続き。
コンセプトはこちらをご参照下さい。

3日目の目標

  • ユーザ管理(data bag)
    • ユーザ作成
    • bash_profile管理
    • sudo
      • ここの段階で ec2-userのsudo権限を剥奪し、新ユーザにsudo権限を付与
    • security_limit

3日目を始める前に:data bag

ユーザ情報など、クックブックを跨るグローバルな値を
cookbookにいちいち書くのは得策ではありません。
さらに生で置いておくのも気が引けますね。
そんなご要望にお答えするために「data bag」という仕組みがあります。
databagを作成しておくと、複数のクックブックにまたがっている共通の変数などを保存しておくことができます。

シークレットキーを作成する

まずはdata bagを暗号/複合するためのシークレットキーを作成しましょう。
以下のコマンドでdata bag用の鍵ファイルを作っておきます。

openssl rand -base64 512 > data_bag_key
# 「unable to write 'random state'」というエラーが出る場合はsudo実行しましょう
鍵を共通ディレクトリに置く

chef-repo毎に鍵を作ってもいいのですが、毎回作るのも面倒なので
今回は共通で参照できるHOMEディレクトリ以下に置いてしまいましょう。

mv data_bag_key ~/.chef/data_bag_key

# knife.rbに「encrypted_data_bag_secret」を追加
vi <chef-repo>/.chef/knife.rb
...
encrypted_data_bag_secret "#{ENV['HOME']}/.chef/data_bag_key"
...
data bagを作成/編集する

knife soloコマンドでdata bagを新規作成します。
この時「EDITOR環境変数」を予め設定しておく必要があるので
未設定の場合は .bashrc などに追記しておきましょう。

vi .bashrc
...
" 以下を追加
export EDITOR=vim
...

# databagの作成
# 「databags/users/alpaca3.json」が作られる
knife solo data bag create users alpaca3

# 編集する場合はedit
knife solo data bag edit users alpaca3

この時の注意点として
databags/users/alpaca3.jsonを直接編集することはできません。*1
データを編集したい場合は knife solo data bag edit コマンドから行う必要があります。


ユーザ作成

今回は完全オリジナルの「user-origin」クックブックを作ります。
が、その前にユーザのdata bagを作っておきましょう。
「alpaca3」というユーザを作ります。

create databag
knife solo data bag create users alpaca3
/databags/users/alpaca3.json の中身

以下のように編集しましょう。

{
  "id": "alpaca3",
  "name": "alpaca3",
  "password": "$1$xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "ssh_key": "ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxx"
}
passwordについて

chefに記述するパスワード文字列はshadow passwordにする必要があるようです。
以下のコマンドで標準出力された文字列をpasswordの欄に設定しましょう。

openssl passwd -1
> input
> verify input
$1$xxxxxxxxxxxxxxxxxxxxxxxxxxxxx   <- これ!

※参考ドキュメント
http://docs.opscode.com/resource_user.html#password-shadow-hash

ssh_keyについて

今回はec2-userの鍵を拝借しましょう。
一度 ec2-userでログインし、以下のコマンドで表示された文字列を「ssh_key」にします。*2

cat ~/.ssh/authorized_keys
/site_cookbooks/user-origin/recipes/default.rb を書く

では、レシピを書いていきます。
少し長くなりそうなので小出ししながら説明します。

# 暗号化されたデータバッグの情報を取得
user = Chef::EncryptedDataBagItem.load("users", 'alpaca3')
user_name = user['name']
password  = user['password']
ssh_key   = user['ssh_key']
home      = "/home/#{user_name}"

# 「alpaca3」ユーザの作成
user user_name do
  password password
  home  home
  shell "/bin/bash"
  supports :manage_home => true # ホームディレクトリも管理する
end

# 「alpaca3」をwheelグループに追加する
group "wheel" do
  action [:modify]
  members [user_name]
  append true
end

まずはユーザの作成です。
1行めの Chef::EncryptedDataBagItem.load() を用いて databagのデータをロードしています。
その情報を用いて「alpaca3」ユーザを作成します。
その後に「alpaca3」ユーザをwheelグループに追加しています。


次にssh用のディレクトリやkeyを設定します。
「directory」リソースを使ってディレクトリを作成した後、
「file」リソースを使ってファイルをアップロードしています。

# .sshディレクトリを作ります
directory "#{home}/.ssh" do
  owner user_name
  group user_name
end

# authorized_keysファイルを作ります
authorized_keys_file ="#{home}/.ssh/authorized_keys"
file authorized_keys_file do
  owner user_name
  mode  0600
  content "#{ssh_key} #{user_name}" # ファイルの中身を直接指定
  not_if { ::File.exists?("#{authorized_keys_file}")} # 既にファイルが存在していたらリソースを実行しない
end


次にbash_profileの管理です。
まずはbash_profileをどのように管理しようとしているか説明したいと思います。

<home>
├── .bash_profile         # .bash_profile。.bash_profile_inc以下のファイルを読む設定を入れておく     
├── .bash_profile_inc     # profile include用ディレクトリ
     ├── base_profile.sh  # 共通profile
     └── ...              # ミドルウェア毎にbash_profileを用意する

他の様々なリソースが .bash_profile_inc 以下に
各々のprofileを置くような運用です。
こうすることによって、.bash_profile自体をいじらなくても
リソース毎の様々なprofileを管理しようとしています。


続きのレシピは以下のようになります。

# 1. bash_profile include用のディレクトリを作ります
directory "#{home}/.bash_profile_inc" do
  owner user_name
  group user_name
end

# 2.「base_profile.sh(静的ファイル)」をbash_profile include用のディレクトリに配置します
cookbook_file "#{home}/.bash_profile_inc/base_profile.sh" do
  source "base_profile.sh"
  mode 0644
  owner user_name
  group user_name
end

# 3. bash_profile本体に「.bash_profile_incディレクトリ以下のファイルを読み込む」ような処理を追加しています
script "include bash_profile" do
  environment 'HOME' => home
  user   user_name
  group  user_name
  interpreter "bash"
  not_if "grep -q 'bash_profile_include' #{home}/.bash_profile"
  flags  "-e"
  code <<-"EOH"
cat << EOF >> #{home}/.bash_profile 2>&1
# bash_profile_include
for file in \\`find ~/.bash_profile_inc -type f\\`; do
  source \\$file
done
EOF
  EOH
end

また新しいリソースが出てきたので簡単に説明を。


2. の「cookbook_file」はfilesディレクトリ以下に置かれた静的ファイルをそのままサーバに転送します。
ちなみに 2. で使用している静的ファイルの中身は
現段階で以下のような内容です。

vi files/default/base_profile.sh

# 今後育っていくはず
export LANG="ja_JP.UTF-8"
alias sl='ls'
alias ll='ls -l'


3. の「script」は「code」に書かれた内容をサーバ上で実行します。
3. で行なっている処理は既存の.bash_profileに追記を行なっているので
重複して実行したくありません。
そのため not_if で 「bash_profile_include」という文字列が存在しない時だけ実行するようにしています*3。

cooking

ではnodes jsonに追加してcookしましょう。

vi nodes/ec2-chef-repo.json
{
    "run_list":[
        "recipe[selinux::disabled]",
        "recipe[openssh]",
        "recipe[ntp]",
        "recipe[sysctl]",
        "recipe[user-origin]"
    ]
}

# cook
knife solo cook ec2-chef-test

うまくいったでしょうか?


今回は利用しませんでしたが、DataBagの内容をそのままユーザ登録に使うようなレシピもあるようです。
気になる方は試してみてはいかがでしょうか。
https://github.com/opscode-cookbooks/users



sudo

sudo管理をします。
デフォルトユーザである ec2-user に管理者権限を持たせるのはちょっと嫌なので
ec2-userのsudo権限を剥奪してalpaca3ユーザにsudo権限を付けたいと思います。

sudoクックブックはopscodeのをダウンロードします。

# Berksfileに以下を追加。berks install も忘れずに。
cookbook 'sudo'
/site_cookbooks/sudo/attributes/default.rb を書く

usersはalpaca3のみにしておきます。

default['authorization']['sudo']['groups']            = []
default['authorization']['sudo']['users']             = ['alpaca3']
# 心配であれば最初だけec2-userを残したままにしておいても良いです
#default['authorization']['sudo']['users']             = ['ec2-user', 'alpaca3']
default['authorization']['sudo']['passwordless']      = true
default['authorization']['sudo']['include_sudoers_d'] = false
default['authorization']['sudo']['agent_forwarding']  = false
default['authorization']['sudo']['sudoers_defaults']  = ['!lecture,tty_tickets,!fqdn']

cooking

ではnodes jsonにレシピを追加してcookしましょう。

vi nodes/ec2-chef-repo.json
# sudo追記
...

# cook
knife solo cook ec2-chef-test

このレシピが成功したあとは ec2-userでのsudoができなくなります。
すなわちknife soloの実行もできなくなります。
ssh/configでユーザの指定「alpaca3」に変えておきましょう。

# ssh config設定
vi .ssh/config
Host ec2-chef-test
   HostName     ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
   IdentityFile /path/to/private.pem
   User         alpaca3
   Port         22

Caution!!!!

sudoクックブックはデフォルトのまま(site-cookbooksを用意しないまま)実行してしまうと
誰もsudoできなくなってしまいます!*4
ご注意下さい。



security_limits

security_limitsもオリジナルで作成してみます。
出来上がるファイルはこんな感じ。

security_limits
├── attributes
│   └── default.rb
├── recipes
│   └── default.rb
└── templates
    └── default
        └── limits.conf.erb
/site_cookbooks/security_limits/attributes/default.rb
# 設定したいユーザ配列
default['security_limits']['users'] = ['alpaca3', 'nobody']
# soft / hard limitの設定
default['security_limits']['soft'] = 10240
default['security_limits']['hard'] = 10240
/site_cookbooks/security_limits/recipes/default.rb
# テンプレート読み込む
template '/etc/security/limits.conf' do
  source "limits.conf.erb"
  mode "0644"
  variables(
    :users      => node['security_limits']['users'],
    :hard_limit => node['security_limits']['hard'],
    :soft_limit => node['security_limits']['soft']
  )
end
/site_cookbooks/security_limits/templates/default/limits.conf.erb
# Dynamically generated file dropped off by Chef!
<% @users.each do |user| %>
<%= user %> hard nofile <%= @hard_limit %>
<%= user %> soft nofile <%= @soft_limit %>
<% end %>

cooking

ではnodes jsonにレシピを追加してcookしましょう。

vi nodes/ec2-chef-repo.json
# security_limits追記
...

# cook
knife solo cook ec2-chef-test


うまくいくと、サーバ上に以下のようなファイルが作成されているはずです。

$ cat /etc/security/limits.conf
# Dynamically generated file dropped off by Chef!
alpaca3 hard nofile 10240
alpaca3 soft nofile 10240
nobody hard nofile 10240
nobody soft nofile 10240

補足

今回、knife soloを実行するユーザもchefで設定/作成していますが、
これに関してはchef管理だと微妙かな〜と思ったりしています。*5
今回はあえてchef管理で行いましたが、
このあたりはケースバイケースで運用を変えても良いかもしれませんね。


次回アジェンダ

共通で使うようなパッケージやミドルウェアをインストールしてみたいと思います。

*1:直接表示してもencryptされたデータが表示されます

*2:あえてdatabagを使用していますが、ログインさせたいだけであれば /home/ec2-user/.ssh/authorized_keys を /home/alpaca3/.ssh/authorized_keys にコピーするだけでも良いですね

*3:んー、微妙

*4:デフォルトは ['sudo']['users']=[] のため

*5:予め作っておいたほうがいいのでは、というお話