Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to save each key in different data bag item #246

Merged
merged 4 commits into from
Dec 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 96 additions & 5 deletions lib/chef-vault/item_keys.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,33 @@ def initialize(vault, name)
@raw_data["admins"] = []
@raw_data["clients"] = []
@raw_data["search_query"] = []
@raw_data["mode"] = "default"
@cache = {} # write-back cache for keys
end

def [](key)
# return options immediately
return @raw_data[key] if %w{id admins clients search_query mode}.include?(key)
# check if the key is in the write-back cache
ckey = @cache[key]
return ckey unless ckey.nil?
# check if the key is saved in sparse mode
skey = sparse_key(sparse_id(key))
if skey
skey[key]
else
# fallback to raw data
@raw_data[key]
end
end

def include?(key)
# check if the key is in the write-back cache
ckey = @cache[key]
return (ckey ? true : false) unless ckey.nil?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OPT:
(if you really want to return true value)

return !!ckey if ckey

or if you just want to return true-ish value

return ckey if ckey

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to write !!ckey unless ckey.nil? ?
Rubocop doesn't like that :(

# check if the key is saved in sparse mode
return true unless sparse_key(sparse_id(key)).nil?
# fallback to non-sparse mode if sparse key is not found
@raw_data.keys.include?(key)
end

Expand All @@ -40,16 +64,24 @@ def add(chef_key, data_bag_shared_secret)
raise ChefVault::Exceptions::V1Format,
"cannot manage a v1 vault. See UPGRADE.md for help"
end
self[chef_key.name] = ChefVault::ItemKeys.encode_key(chef_key.key, data_bag_shared_secret)
@cache[chef_key.name] = ChefVault::ItemKeys.encode_key(chef_key.key, data_bag_shared_secret)
@raw_data[type] << chef_key.name unless @raw_data[type].include?(chef_key.name)
@raw_data[type]
end

def delete(chef_key)
raw_data.delete(chef_key.name)
@cache[chef_key.name] = false
raw_data[chef_key.type].delete(chef_key.name)
end

def mode(mode = nil)
if mode
@raw_data["mode"] = mode
else
@raw_data["mode"]
end
end

def search_query(search_query = nil)
if search_query
@raw_data["search_query"] = search_query
Expand All @@ -67,9 +99,8 @@ def admins
end

def save(item_id = @raw_data["id"])
if Chef::Config[:solo_legacy_mode]
save_solo(item_id)
else
# create data bag if not running in solo mode
unless Chef::Config[:solo_legacy_mode]
begin
Chef::DataBag.load(data_bag)
rescue Net::HTTPServerException => http_error
Expand All @@ -79,9 +110,53 @@ def save(item_id = @raw_data["id"])
chef_data_bag.create
end
end
end

# write cached keys to data
@cache.each do |key, val|
# delete across all modes on key deletion
if val == false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless val

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nil and false do not have the same meaning in the write-back cache

# sparse mode key deletion
if Chef::Config[:solo_legacy_mode]
delete_solo(sparse_id(key))
else
begin
Chef::DataBagItem.from_hash("data_bag" => data_bag,
"id" => sparse_id(key))
.destroy(data_bag, sparse_id(key))
rescue Net::HTTPServerException => http_error
raise http_error unless http_error.response.code == "404"
end
end
# default mode key deletion
@raw_data.delete(key)
else
if @raw_data["mode"] == "sparse"
# sparse mode key creation
skey = Chef::DataBagItem.from_hash(
"data_bag" => data_bag,
"id" => sparse_id(key),
key => val
)
if Chef::Config[:solo_legacy_mode]
save_solo(skey.id, skey.raw_data)
else
skey.save
end
else
# default mode key creation
@raw_data[key] = val
end
end
end
# save raw data
if Chef::Config[:solo_legacy_mode]
save_solo(item_id)
else
super
end
# clear write-back cache
@cache = {}
end

def destroy
Expand Down Expand Up @@ -129,6 +204,22 @@ def self.load(vault, name)

# @private

def sparse_id(key, item_id = @raw_data["id"])
"#{item_id}_key_#{key}"
end

def sparse_key(sid)
if Chef::Config[:solo_legacy_mode]
load_solo(sid)
else
begin
Chef::DataBagItem.load(@data_bag, sid)
rescue Net::HTTPServerException => http_error
nil if http_error.response.code == "404"
end
end
end

def self.encode_key(key_string, data_bag_shared_secret)
public_key = OpenSSL::PKey::RSA.new(key_string)
Base64.encode64(public_key.public_encrypt(data_bag_shared_secret))
Expand Down
12 changes: 11 additions & 1 deletion lib/chef-vault/mixins.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def find_solo_path(item_id)
[data_bag_path, data_bag_item_path]
end

def save_solo(item_id = @raw_data["id"])
def save_solo(item_id = @raw_data["id"], raw_data = @raw_data)
data_bag_path, data_bag_item_path = find_solo_path(item_id)

FileUtils.mkdir(data_bag_path) unless File.exist?(data_bag_path)
Expand All @@ -32,5 +32,15 @@ def save_solo(item_id = @raw_data["id"])

raw_data
end

def delete_solo(item_id = @raw_data["id"])
_data_bag_path, data_bag_item_path = find_solo_path(item_id)
FileUtils.rm(data_bag_item_path) if File.exist?(data_bag_item_path)
end

def load_solo(item_id = @raw_data["id"])
_data_bag_path, data_bag_item_path = find_solo_path(item_id)
JSON.parse(File.read(data_bag_item_path)) if File.exist?(data_bag_item_path)
end
end
end
62 changes: 57 additions & 5 deletions spec/chef-vault/item_keys_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
describe "#new" do
let(:keys) { ChefVault::ItemKeys.new("foo", "bar") }
let(:shared_secret) { "super_secret" }
let(:public_key_string) do
"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyMXT9IOV9pkQsxsnhSx8\n8RX6GW3caxkjcXFfHg6E7zUVBFAsfw4B1D+eHAks3qrDB7UrUxsmCBXwU4dQHaQy\ngAn5Sv0Jc4CejDNL2EeCBLZ4TF05odHmuzyDdPkSZP6utpR7+uF7SgVQedFGySIB\nih86aM+HynhkJqgJYhoxkrdo/JcWjpk7YEmWb6p4esnvPWOpbcjIoFs4OjavWBOF\niTfpkS0SkygpLi/iQu9RQfd4hDMWCc6yh3Th/1nVMUd+xQCdUK5wxluAWSv8U0zu\nhiIlZNazpCGHp+3QdP3f6rebmQA8pRM8qT5SlOvCYPk79j+IMUVSYrR4/DTZ+VM+\naQIDAQAB\n-----END PUBLIC KEY-----\n"
end

it "'foo' is assigned to @data_bag" do
expect(keys.data_bag).to eq "foo"
Expand All @@ -19,10 +22,7 @@
expect(keys["admins"]).to eq []
end

describe "key mgmt operations" do
let(:public_key_string) do
"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyMXT9IOV9pkQsxsnhSx8\n8RX6GW3caxkjcXFfHg6E7zUVBFAsfw4B1D+eHAks3qrDB7UrUxsmCBXwU4dQHaQy\ngAn5Sv0Jc4CejDNL2EeCBLZ4TF05odHmuzyDdPkSZP6utpR7+uF7SgVQedFGySIB\nih86aM+HynhkJqgJYhoxkrdo/JcWjpk7YEmWb6p4esnvPWOpbcjIoFs4OjavWBOF\niTfpkS0SkygpLi/iQu9RQfd4hDMWCc6yh3Th/1nVMUd+xQCdUK5wxluAWSv8U0zu\nhiIlZNazpCGHp+3QdP3f6rebmQA8pRM8qT5SlOvCYPk79j+IMUVSYrR4/DTZ+VM+\naQIDAQAB\n-----END PUBLIC KEY-----\n"
end
shared_context "key mgmt operations" do

shared_examples_for "proper key management" do
let(:chef_key) { ChefVault::Actor.new(type, name) }
Expand All @@ -41,6 +41,7 @@
keys.add(chef_key, shared_secret)
expect(keys[name]).to eq("encrypted_result")
expect(keys[type].include?(name)).to eq(true)
expect(keys.include?(name)).to eq(true)
end
end

Expand All @@ -53,6 +54,7 @@
keys.delete(chef_key)
expect(keys.has_key?(chef_key.name)).to eq(false)
expect(keys[type].include?(name)).to eq(false)
expect(keys.include?(name)).to eq(false)
end
end
end
Expand All @@ -75,10 +77,37 @@
before { server.start_background }
after { server.stop }

include_context "key mgmt operations"

describe "#save" do
let(:client_name) { "client_name" }
let(:chef_key) { ChefVault::Actor.new("clients", client_name) }

before do
allow(chef_key).to receive(:key) { public_key_string }
end

it "should save the key data" do
keys.add(chef_key, shared_secret)
keys.save("bar")
expect(Chef::DataBagItem.load("foo", "bar").to_hash).to include("id" => "bar")
expect(keys[client_name]).not_to be_empty
keys.delete(chef_key)
keys.save("bar")
expect(keys[client_name]).to be_nil
end

it "should save the key data in sparse mode" do
keys.add(chef_key, shared_secret)
keys.mode("sparse")
keys.save("bar")
expect(Chef::DataBagItem.load("foo", "bar").to_hash).to include("id" => "bar")
expect(Chef::DataBagItem.load("foo", "bar_key_client_name").to_hash).to include("id" => "bar_key_client_name")
expect(keys[client_name]).not_to be_empty
keys.delete(chef_key)
keys.save("bar")
expect(keys[client_name]).to be_nil
keys.mode("default")
end
end
end
Expand All @@ -87,6 +116,8 @@
before { Chef::Config[:solo_legacy_mode] = true }
after { Chef::Config[:solo_legacy_mode] = false }

include_context "key mgmt operations"

describe "#find_solo_path" do
context "when data_bag_path is an array" do
before do
Expand Down Expand Up @@ -119,15 +150,36 @@
end

describe "#save" do
let(:client_name) { "client_name" }
let(:chef_key) { ChefVault::Actor.new("clients", client_name) }
let(:data_bag_path) { Dir.mktmpdir("vault_item_keys") }

before do
Chef::Config[:data_bag_path] = data_bag_path
allow(chef_key).to receive(:key) { public_key_string }
end

it "should save the key data" do
expect(File).to receive(:exist?).with(File.join(data_bag_path, "foo")).and_call_original
keys.add(chef_key, shared_secret)
keys.save("bar")
expect(File.read(File.join(data_bag_path, "foo", "bar.json"))).to match(/"id":.*"bar"/)
expect(keys[client_name]).not_to be_empty
keys.delete(chef_key)
keys.save("bar")
expect(keys[client_name]).to be_nil
end

it "should save the key data in sparse mode" do
keys.add(chef_key, shared_secret)
keys.mode("sparse")
keys.save("bar")
expect(File.read(File.join(data_bag_path, "foo", "bar.json"))).to match(/"id":.*"bar"/)
expect(File.read(File.join(data_bag_path, "foo", "bar_key_client_name.json"))).to match(/"id":.*"bar_key_client_name"/)
expect(keys[client_name]).not_to be_empty
keys.delete(chef_key)
keys.save("bar")
expect(keys[client_name]).to be_nil
keys.mode("default")
end
end
end
Expand Down