lib/ridley/chef_objects/data_bag_item_obect.rb
require 'yaml'
module Ridley
class DataBagItemObject < ChefObject
set_chef_id "id"
set_assignment_mode :carefree
# @return [Ridley::DataBagObject]
attr_reader :data_bag
attribute :id,
type: String,
required: true
alias_method :attributes=, :mass_assign
alias_method :attributes, :_attributes_
# @param [Ridley::DataBagItemResource] resource
# @param [Ridley::DataBagObject] data_bag
# @param [#to_hash] new_attrs
def initialize(resource, data_bag, new_attrs = {})
super(resource, new_attrs)
@data_bag = data_bag
end
# Creates a resource on the target remote or updates one if the resource
# already exists.
#
# @raise [Errors::InvalidResource]
# if the resource does not pass validations
#
# @return [Boolean]
# true if successful and false for failure
def save
raise Errors::InvalidResource.new(self.errors) unless valid?
mass_assign(resource.create(data_bag, self)._attributes_)
true
rescue Errors::HTTPConflict
self.update
true
end
# Decrypts this data bag item.
#
# @return [Hash] decrypted attributes
def decrypt
decrypted_hash = Hash[_attributes_.map { |key, value| [key, key == "id" ? value : decrypt_value(value)] }]
mass_assign(decrypted_hash)
end
# Decrypts an individual value stored inside the data bag item.
#
# @example
# data_bag_item.decrypt_value("Xk0E8lV9r4BhZzcg4wal0X4w9ZexN3azxMjZ9r1MCZc=")
# => {test: {database: {username: "test"}}}
#
# @param [String] an encrypted String value
#
# @return [Hash] a decrypted attribute value
def decrypt_value(value)
case format_version_of(value)
when 0
decrypt_v0_value(value)
when 1
decrypt_v1_value(value)
else
raise NotImplementedError, "Currently decrypting only version 0 & 1 databags are supported"
end
end
# Reload the attributes of the instantiated resource
#
# @return [Object]
def reload
mass_assign(resource.find(data_bag, self)._attributes_)
self
end
# Updates the instantiated resource on the target remote with any changes made
# to self
#
# @raise [Errors::InvalidResource]
# if the resource does not pass validations
#
# @return [Boolean]
def update
raise Errors::InvalidResource.new(self.errors) unless valid?
mass_assign(resource.update(data_bag, self)._attributes_)
true
end
# @param [#to_hash] hash
#
# @return [Object]
def from_hash(hash)
hash = Hashie::Mash.new(hash.to_hash)
mass_assign(hash.has_key?(:raw_data) ? hash[:raw_data] : hash)
self
end
private
# Shamelessly lifted from https://github.com/opscode/chef/blob/2c0040c95bb942d13ad8c47498df56be43e9a82e/lib/chef/encrypted_data_bag_item.rb#L209-L215
def format_version_of(encrypted_value)
if encrypted_value.respond_to?(:key?)
encrypted_value["version"]
else
0
end
end
def decrypt_v0_value(value)
if encrypted_data_bag_secret.nil?
raise Errors::EncryptedDataBagSecretNotSet
end
decoded_value = Base64.decode64(value)
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
cipher.decrypt
cipher.pkcs5_keyivgen(encrypted_data_bag_secret)
decrypted_value = cipher.update(decoded_value) + cipher.final
YAML.load(decrypted_value)
end
def decrypt_v1_value(attrs)
if encrypted_data_bag_secret.nil?
raise Errors::EncryptedDataBagSecretNotSet
end
cipher = OpenSSL::Cipher::Cipher.new(attrs[:cipher])
cipher.decrypt
cipher.key = Digest::SHA256.digest(encrypted_data_bag_secret)
cipher.iv = Base64.decode64(attrs[:iv])
decrypted_value = cipher.update(Base64.decode64(attrs[:encrypted_data])) + cipher.final
YAML.load(decrypted_value)["json_wrapper"]
end
def encrypted_data_bag_secret
resource.encrypted_data_bag_secret
end
end
end