この記事は最終更新から1年以上経過しています。 気をつけてね。
実践Opscode Chefシリーズ、part.1の続きになります。
記事中のCookbookとChefSolo設定一式はこちらに公開しています。
https://github.com/OpsRockin/lwrp_http_userdb
リソースの名前重複を解決した所で切ったので、今のResource/Providerはこんな感じです。
resources/auth_basic.rb
actions :create
default_action :create
attribute :user, :kind_of => String, :required => true
attribute :password, :kind_of => String
attribute :path, :kind_of => String, :required => true
providers/auth_basic.rb
require 'webrick'
def whyrun_supported?
true
end
action :create do
converge_by("====== Create http_auth user #{@new_resource.user}") do
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(@new_resource.path)
htpasswd.set_passwd nil, @new_resource.user, @new_resource.password
htpasswd.flush
end
end
早速ですが、この:create
アクションは欠陥品です。何故でしょうか?
そう、対象のユーザが既にユーザDBに存在しようが気にしていませんね。:create
アクションが取るべき行動は、『なければ作成』『変更があれば適用』であり、最後の『変更がなければ何もしない』の場合はupdated_resoucesにカウントされるないことが望ましいのです。
参考資料:
このあと@current_resource , @new_resource を扱う話になります、この概念を知らないなら下記スライドを一読してから先に進むことをお奨めします。
current_resource == new_resource
では:create
アクションに@current_resouce
の取得と比較する処理を入れていきます。
必要な考え方はこのとおりです。
- ユーザDBをロードする
- レシピに書いたユーザが存在しなかったら追加
- レシピに書いたユーザが存在したら…
- パスワードが変更されていたら更新
- パスワードが変更されていなかったら何もしない(up to date)
やり方は色々考えられますが、一つずつシンプルに片付けて行きましょう。
resources/auth_basic.rb
actions :create
default_action :create
attribute :user, :kind_of => String, :required => true
attribute :password, :kind_of => String
attribute :path, :kind_of => String, :required => true
attr_accessor :crypted_passwd
attr_accessor が追加されました、これはレシピでは指定することは無いけれど、current_resouce
の状態を入れるなどに使う属性を用意する時に使います。
今回ユーザDBから取得できるものはハッシュ化されたパスワードになるため、レシピで定義する属性と正面切って比較ができません。なのでcurrent_resouce
のいち属性としてそれを格納します。
providers/auth_basic.rb
require 'webrick'
def whyrun_supported?
true
end
action :create do
if @current_resource.crypted_passwd
Chef::Log.warn "====== Found http_auth user #{@new_resource.user}"
salt = @current_resource.crypted_passwd[0..1]
if @current_resource.crypted_passwd == @new_resource.password.crypt(salt)
Chef::Log.warn "====== http_auth user #{@new_resource.user} was not modified. (up to date)"
else
converge_by("====== Update http_auth user #{@new_resource.user}") do
Chef::Log.warn "====== Update http_auth user #{@new_resource.user}"
flush_userdb!
end
end
else
converge_by("====== Create http_auth user #{@new_resource.user}") do
Chef::Log.warn "====== Create http_auth user #{@new_resource.user}"
flush_userdb!
end
end
end
def flush_userdb!
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(@new_resource.path)
htpasswd.set_passwd nil, @new_resource.user, @new_resource.password
htpasswd.flush
end
def load_current_resource
@current_resource = Chef::Resource::HttpsvAuthBasic.new(@new_resource.name)
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(@new_resource.path)
@current_resource.crypted_passwd = htpasswd.get_passwd nil, @new_resource.user, true
end
いきなり行数が増えてしまいましたが、それぞれ大したことはしていないので解説します。
def load_current_resource
def load_current_resource
はリソースを利用する際に自動で呼ばれる関数です。@current_resouce
属性の収集処理を書いておきましょう。
新規作成
今回は対象のユーザDBにユーザが存在しない場合、get_passwd
がnil
を返してくるため、初っ端の分岐条件に使用できました。新規作成はこれでクリアです。
更新するかしないか
既にユーザがいた場合、暗号化されたパスワードをもらえるのでそれをレシピの定義と比較することで更新するかどうかの判断をします。
htpasswdのパスワードフィールドはこういう仕様でした。
- 最初の2文字がsalt
- そのsaltを使ってcrypt処理
ということはこれで条件比較ができますね。
if @current_resource.crypted_passwd == @new_resource.password.crypt(salt)
あとはマッチすれば何もしない、そうでなければ更新処理を行うと。
Digest認証の時
ちなみにDigest認証の際はパスワードフィールドはMD5になります、生成規則はこうです。
Digest::MD5::hexdigest([user, realm, pass].join(":"))
Basicと同じようにレシピの情報で十分照合が可能ですね。
recipes/sample05.rb
base_dir = ENV['PWD']
httpsv_auth_basic 'var/www/site1' do
user 'hoge1'
path File.join(base_dir, self.name, '.htpasswd')
name [self.name, self.user].join(':')
password 'password1'
end
httpsv_auth_basic 'var/www/site1' do
user 'hoge2'
path File.join(base_dir, self.name, '.htpasswd')
name [self.name, self.user].join(':')
password rand.to_s
end
user 'hoge2'のパスワードを毎回ランダムで生成するようにして、Chefを何度か実行してみます。
chef-solo run
まず初回
$ chef-solo -c solo.rb -o 'httpsv::sample05'
Starting Chef Client, version 11.6.0
[2013-09-07T23:07:52+09:00] WARN: Run List override has been provided.
[2013-09-07T23:07:52+09:00] WARN: Original Run List: []
[2013-09-07T23:07:52+09:00] WARN: Overridden Run List: [recipe[httpsv::sample05]]
Compiling Cookbooks...
Converging 2 resources
Recipe: httpsv::sample05
* httpsv_auth_basic[var/www/site1:hoge1] action create
[2013-09-07T23:07:52+09:00] WARN: ====== Create http_auth user hoge1
- ====== Create http_auth user hoge1
* httpsv_auth_basic[var/www/site1:hoge2] action create
[2013-09-07T23:07:52+09:00] WARN: ====== Create http_auth user hoge2
- ====== Create http_auth user hoge2
Chef Client finished, 2 resources updated
chef-solo run
2回め、user1, user2ともに存在。
$ chef-solo -c solo.rb -o 'httpsv::sample05'
Starting Chef Client, version 11.6.0
[2013-09-07T23:08:08+09:00] WARN: Run List override has been provided.
[2013-09-07T23:08:08+09:00] WARN: Original Run List: []
[2013-09-07T23:08:08+09:00] WARN: Overridden Run List: [recipe[httpsv::sample05]]
Compiling Cookbooks...
Converging 2 resources
Recipe: httpsv::sample05
* httpsv_auth_basic[var/www/site1:hoge1] action create
[2013-09-07T23:08:08+09:00] WARN: ====== Found http_auth user hoge1
[2013-09-07T23:08:08+09:00] WARN: ====== http_auth user hoge1 was not modified. (up to date)
(up to date)
* httpsv_auth_basic[var/www/site1:hoge2] action create
[2013-09-07T23:08:08+09:00] WARN: ====== Found http_auth user hoge2
[2013-09-07T23:08:08+09:00] WARN: ====== Update http_auth user hoge2
- ====== Update http_auth user hoge2
Chef Client finished, 1 resources updated
chef-solo run
2回め以降のWhy-Run
$ chef-solo -c solo.rb -o 'httpsv::sample05' -W
Starting Chef Client, version 11.6.0
[2013-09-07T23:08:50+09:00] WARN: Run List override has been provided.
[2013-09-07T23:08:50+09:00] WARN: Original Run List: []
[2013-09-07T23:08:50+09:00] WARN: Overridden Run List: [recipe[httpsv::sample05]]
Compiling Cookbooks...
Converging 2 resources
Recipe: httpsv::sample05
* httpsv_auth_basic[var/www/site1:hoge1] action create
[2013-09-07T23:08:50+09:00] WARN: ====== Found http_auth user hoge1
[2013-09-07T23:08:50+09:00] WARN: ====== http_auth user hoge1 was not modified. (up to date)
(up to date)
* httpsv_auth_basic[var/www/site1:hoge2] action create
[2013-09-07T23:08:50+09:00] WARN: ====== Found http_auth user hoge2
- Would ====== Update http_auth user hoge2
Chef Client finished, 1 resources would have been updated
できました、意外と簡単ですよね。action :create
はこれで’ほぼ’完成したと言えるでしょう。
何が足りないかって? それは後ほど。
deleteアクションの中身を作る
そろそろ:delete
を作ってもいいでしょう。:create
が作れるならもう簡単ですよね。
できました。
providers/auth_basic.rb
require 'webrick'
def whyrun_supported?
true
end
action :create do
if @current_resource.crypted_passwd
Chef::Log.warn "====== Found http_auth user #{@new_resource.user}"
salt = @current_resource.crypted_passwd[0..1]
if @current_resource.crypted_passwd == @new_resource.password.crypt(salt)
Chef::Log.warn "====== http_auth user #{@new_resource.user} was not modified. (up to date)"
else
converge_by("====== Update http_auth user #{@new_resource.user}") do
Chef::Log.warn "====== Update http_auth user #{@new_resource.user}"
update_user!
end
end
else
converge_by("====== Create http_auth user #{@new_resource.user}") do
Chef::Log.warn "====== Create http_auth user #{@new_resource.user}"
update_user!
end
end
end
action :delete do
unless @current_resource.crypted_passwd
Chef::Log.warn "====== http_auth user #{@new_resource.user} was not found. Nothing to do. (up to date)"
else
converge_by("====== Delete http_auth user #{@new_resource.user}") do
Chef::Log.warn "====== Delete http_auth user #{@new_resource.user}"
delete_user!
end
end
end
def update_user!
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(@new_resource.path)
htpasswd.set_passwd nil, @new_resource.user, @new_resource.password
htpasswd.flush
end
def delete_user!
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(@new_resource.path)
htpasswd.delete_passwd nil, @new_resource.user
htpasswd.flush
end
def load_current_resource
@current_resource = Chef::Resource::HttpsvAuthBasic.new(@new_resource.name)
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(@new_resource.path)
@current_resource.crypted_passwd = htpasswd.get_passwd nil, @new_resource.user, true
end
いなければ何もしない、いれば消す。アクションにあわせてflush_userdb!
メソッドを2つに分けました。
chef-solo run
まずWhy-Runから。
$ chef-solo -c solo.rb -o 'httpsv::sample06' -W
Starting Chef Client, version 11.6.0
[2013-09-07T23:29:17+09:00] WARN: Run List override has been provided.
[2013-09-07T23:29:17+09:00] WARN: Original Run List: []
[2013-09-07T23:29:17+09:00] WARN: Overridden Run List: [recipe[httpsv::sample06]]
Compiling Cookbooks...
Converging 2 resources
Recipe: httpsv::sample06
* httpsv_auth_basic[var/www/site1:hoge1] action delete
- Would ====== Delete http_auth user hoge1
* httpsv_auth_basic[var/www/site1:hoge2] action delete
- Would ====== Delete http_auth user hoge2
Chef Client finished, 2 resources would have been updated
chef-solo run
実際に消してみます。
$ chef-solo -c solo.rb -o 'httpsv::sample06'
Starting Chef Client, version 11.6.0
[2013-09-07T23:46:12+09:00] WARN: Run List override has been provided.
[2013-09-07T23:46:12+09:00] WARN: Original Run List: []
[2013-09-07T23:46:12+09:00] WARN: Overridden Run List: [recipe[httpsv::sample06]]
Compiling Cookbooks...
Converging 2 resources
Recipe: httpsv::sample06
* httpsv_auth_basic[var/www/site1:hoge1] action delete[2013-09-07T23:46:12+09:00] WARN: ====== Delete http_auth user hoge1
- ====== Delete http_auth user hoge1
* httpsv_auth_basic[var/www/site1:hoge2] action delete[2013-09-07T23:46:12+09:00] WARN: ====== Delete http_auth user hoge2
- ====== Delete http_auth user hoge2
Chef Client finished, 2 resources updated
chef-solo run
ユーザ削除以降の実行では、特に何もしません。
$ chef-solo -c solo.rb -o 'httpsv::sample06'
Starting Chef Client, version 11.6.0
[2013-09-07T23:46:16+09:00] WARN: Run List override has been provided.
[2013-09-07T23:46:16+09:00] WARN: Original Run List: []
[2013-09-07T23:46:16+09:00] WARN: Overridden Run List: [recipe[httpsv::sample06]]
Compiling Cookbooks...
Converging 2 resources
Recipe: httpsv::sample06
* httpsv_auth_basic[var/www/site1:hoge1] action delete[2013-09-07T23:46:17+09:00] WARN: ====== http_auth user hoge1 was not found. Nothing to do. (up to date)
(up to date)
* httpsv_auth_basic[var/www/site1:hoge2] action delete[2013-09-07T23:46:17+09:00] WARN: ====== http_auth user hoge2 was not found. Nothing to do. (up to date)
(up to date)
Chef Client finished, 0 resources updated
これで:delete
アクションもできましたね。
ユーザDBのパーミッションを調整しよう
ライブラリの都合で、ユーザDBのパーミッションが毎回Chefの実行ユーザの'0600'になっています。調整できるようにしておきます。
resources/auth_basic.rb
actions :create, :delete
default_action :create
attribute :user, :kind_of => String, :required => true
attribute :password, :kind_of => String
attribute :path, :kind_of => String, :required => true
attribute :filemode, :default => 0600
attr_accessor :crypted_passwd
filemode属性を追加して、省略時は'0600' がセットされるようにしておきます。
providers/auth_basic.rb
一部抜粋にします。
# -- snip --
def update_user!
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(@new_resource.path)
htpasswd.set_passwd nil, @new_resource.user, @new_resource.password
htpasswd.flush
fix_permission!
end
def delete_user!
htpasswd = WEBrick::HTTPAuth::Htpasswd.new(@new_resource.path)
htpasswd.delete_passwd nil, @new_resource.user
htpasswd.flush
fix_permission!
end
def fix_permission!
FileUtils.chmod(@new_resource.filemode.to_i, @new_resource.path)
end
# -- snip --
flush
直後に#fix_permission!
を呼ぶように書き換えます。ownerやgroupを調整したい場合は#fix_permission!
に処理を追加していけば良いでしょう。
これでレシピ側でユーザDBファイルのパーミッションを変更できるようになります。
recipes/sample07.rb
base_dir = ENV['PWD']
httpsv_auth_basic 'var/www/site1' do
user 'hoge1'
path File.join(base_dir, self.name, '.htpasswd')
name [self.name, self.user].join(':')
password 'password1'
filemode 0640
end
httpsv_auth_basic 'var/www/site1' do
user 'hoge2'
path File.join(base_dir, self.name, '.htpasswd')
name [self.name, self.user].join(':')
password rand.to_s
filemode 0640
end
既存DBを変更したい際はconvergenceを発生させないと権限が変わらないので、リソース比較の処理を追加しても良いでしょうね。
さて、そろそろ:create
アクションがほぼ'完成'としたことに触れたいと思います。
またしても長くなってきたので記事を分けます、Part.3へどうぞ。
実践LWRP、HTTP認証用ファイル(htpasswd,htdigest)をChefのリソースとして管理する part.3 of 3
実践LWRP、HTTP認証用ファイル(htpasswd,htdigest)をChefのリソースとして管理する part.1 of 3
実践LWRP、HTTP認証用ファイル(htpasswd,htdigest)をChefのリソースとして管理する part.2 of 3