summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Ball <tyleraball@gmail.com>2014-09-29 09:35:50 -0700
committerTyler Ball <tyleraball@gmail.com>2014-09-29 09:35:50 -0700
commit0819eafa6fd664815856fe0081dfcafabd59dcf6 (patch)
treeb24f4c971587a438c1915114c69a86e4520ec529
parent5a88d0ef5b64280150f89332544e55144605eeb1 (diff)
parent049672e8335a7a3190fcf3acd59d63b42f1f0ba0 (diff)
downloadchef-0819eafa6fd664815856fe0081dfcafabd59dcf6.tar.gz
Merge pull request #2118 from opscode/tball/encrypted-data-bag-ux
Finishing encrypted data bag UX
-rw-r--r--DOC_CHANGES.md36
-rw-r--r--lib/chef/dsl/data_query.rb31
-rw-r--r--lib/chef/encrypted_data_bag_item/check_encrypted.rb56
-rw-r--r--lib/chef/knife/bootstrap.rb16
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb13
-rw-r--r--lib/chef/knife/data_bag_create.rb39
-rw-r--r--lib/chef/knife/data_bag_edit.rb67
-rw-r--r--lib/chef/knife/data_bag_from_file.rb34
-rw-r--r--lib/chef/knife/data_bag_secret_options.rb142
-rw-r--r--lib/chef/knife/data_bag_show.rb52
-rw-r--r--spec/support/shared/matchers.rb17
-rw-r--r--spec/support/shared/matchers/exit_with_code.rb28
-rw-r--r--spec/support/shared/matchers/match_environment_variable.rb17
-rw-r--r--spec/unit/dsl/data_query_spec.rb118
-rw-r--r--spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb95
-rw-r--r--spec/unit/knife/bootstrap_spec.rb64
-rw-r--r--spec/unit/knife/core/bootstrap_context_spec.rb41
-rw-r--r--spec/unit/knife/data_bag_create_spec.rb134
-rw-r--r--spec/unit/knife/data_bag_edit_spec.rb132
-rw-r--r--spec/unit/knife/data_bag_from_file_spec.rb229
-rw-r--r--spec/unit/knife/data_bag_secret_options_spec.rb165
-rw-r--r--spec/unit/knife/data_bag_show_spec.rb146
22 files changed, 948 insertions, 724 deletions
diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md
index 29fca72484..b79e06b4fc 100644
--- a/DOC_CHANGES.md
+++ b/DOC_CHANGES.md
@@ -90,6 +90,42 @@ DSL method `data_bag_item` now takes an optional String parameter `secret`, whic
If the data bag item being fetched is encrypted and no `secret` is provided, Chef looks for a secret at `Chef::Config[:encrypted_data_bag_secret]`.
If `secret` is provided, but the data bag item is not encrypted, then a regular data bag item is returned (no decryption is attempted).
+### Encrypted data bag UX
+The user can now provide a secret for data bags in 4 ways. They are, in order of descending preference:
+1. Provide the secret on the command line of `knife data bag` and `knife bootstrap` commands with `--secret`
+1. Provide the location of a file containing the secret on the command line of `knife data bag` and `knife bootstrap` commands with `--secret-file`
+1. Add the secret to your workstation config with `knife[:secret] = ...`
+1. Add the location of a file containing the secret to your workstation config with `knife[:secret-file] = ...`
+
+When adding the secret information to your workstation config, it will not be used for writeable operations unless `--encrypt` is also passed on the command line.
+Data bag read-only operations (`knife data bag show` and `knife bootstrap`) do not require `--encrypt` to be passed, and will attempt to use an available secret for decryption.
+Unencrypted data bags will not attempt to be unencrypted, even if a secret is provided.
+Trying to view an encrypted data bag without providing a secret will issue a warning and show the encrypted contents.
+Trying to edit or create an encrypted data bag without providing a secret will fail.
+
+Here are some example scenarios:
+
+```
+# Providing `knife[:secret_file] = ...` in knife.rb will create and encrypt the data bag
+knife data bag create BAG_NAME ITEM_NAME --encrypt
+
+# The same command ran with --secret will use the command line secret instead of the knife.rb secret
+knife data bag create ANOTHER_BAG ITEM_NAME --encrypt --secret 'ANOTHER_SECRET'
+
+# The next two commands will fail, because they are using the wrong secret
+knife data bag edit BAG_NAME --secret 'ANOTHER_SECRET'
+knife data bag edit ANOTHER_BAG --encrypt
+
+# The next command will unencrypt the data and show it using the `knife[:secret_file]` without passing the --encrypt flag
+knife data bag show BAG_NAME
+
+# To create an unencrypted data bag, simply do not provide `--secret`, `--secret-file` or `--encrypt`
+knife data bag create UNENCRYPTED_BAG
+
+# If a secret is available from any of the 4 possible entries, it will be copied to a bootstrapped node, even if `--encrypt` is not present
+knife bootstrap FQDN
+```
+
### Enhanced search functionality: result filtering
#### Use in recipes
`Chef::Search::Query#search` can take an optional `:filter_result` argument which returns search data in the form of the Hash specified. Suppose your data looks like
diff --git a/lib/chef/dsl/data_query.rb b/lib/chef/dsl/data_query.rb
index 3dafbca6bf..e36784271a 100644
--- a/lib/chef/dsl/data_query.rb
+++ b/lib/chef/dsl/data_query.rb
@@ -20,6 +20,7 @@ require 'chef/search/query'
require 'chef/data_bag'
require 'chef/data_bag_item'
require 'chef/encrypted_data_bag_item'
+require 'chef/encrypted_data_bag_item/check_encrypted'
class Chef
module DSL
@@ -28,6 +29,7 @@ class Chef
# Provides DSL for querying data from the chef-server via search or data
# bag.
module DataQuery
+ include Chef::EncryptedDataBagItem::CheckEncrypted
def search(*args, &block)
# If you pass a block, or have at least the start argument, do raw result parsing
@@ -78,35 +80,6 @@ class Chef
raise
end
- private
-
- # Tries to autodetect if the item's raw hash appears to be encrypted.
- def encrypted?(raw_data)
- data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key.
- # Assume hashes containing only the "id" key are not encrypted.
- # Otherwise, remove the keys that don't appear to be encrypted and compare
- # the result with the hash. If some entry has been removed, then some entry
- # doesn't appear to be encrypted and we assume the entire hash is not encrypted.
- data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data
- end
-
- # Checks if data looks like it has been encrypted by
- # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns
- # true only when there is an exact match between the VersionXEncryptor
- # keys and the hash's keys.
- def looks_like_encrypted?(data)
- return false unless data.is_a?(Hash) && data.has_key?("version")
- case data["version"]
- when 1
- Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort
- when 2
- Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort
- when 3
- Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort
- else
- false # version means something else... assume not encrypted.
- end
- end
end
end
end
diff --git a/lib/chef/encrypted_data_bag_item/check_encrypted.rb b/lib/chef/encrypted_data_bag_item/check_encrypted.rb
new file mode 100644
index 0000000000..b7cb5841b3
--- /dev/null
+++ b/lib/chef/encrypted_data_bag_item/check_encrypted.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Tyler Ball (<tball@getchef.com>)
+# Copyright:: Copyright (c) 2010-2014 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/encrypted_data_bag_item/encryptor'
+
+class Chef::EncryptedDataBagItem
+ # Common code for checking if a data bag appears encrypted
+ module CheckEncrypted
+
+ # Tries to autodetect if the item's raw hash appears to be encrypted.
+ def encrypted?(raw_data)
+ data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key.
+ # Assume hashes containing only the "id" key are not encrypted.
+ # Otherwise, remove the keys that don't appear to be encrypted and compare
+ # the result with the hash. If some entry has been removed, then some entry
+ # doesn't appear to be encrypted and we assume the entire hash is not encrypted.
+ data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data
+ end
+
+ private
+
+ # Checks if data looks like it has been encrypted by
+ # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns
+ # true only when there is an exact match between the VersionXEncryptor
+ # keys and the hash's keys.
+ def looks_like_encrypted?(data)
+ return false unless data.is_a?(Hash) && data.has_key?("version")
+ case data["version"]
+ when 1
+ Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort
+ when 2
+ Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort
+ when 3
+ Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort
+ else
+ false # version means something else... assume not encrypted.
+ end
+ end
+
+ end
+end
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index 36a0fc1e47..a992cf5779 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -17,11 +17,13 @@
#
require 'chef/knife'
+require 'chef/knife/data_bag_secret_options'
require 'erubis'
class Chef
class Knife
class Bootstrap < Knife
+ include DataBagSecretOptions
deps do
require 'chef/knife/core/bootstrap_context'
@@ -157,17 +159,6 @@ class Chef
Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new
}
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to encrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to encrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
option :bootstrap_url,
:long => "--bootstrap-url URL",
:description => "URL to a custom installation script",
@@ -248,7 +239,8 @@ class Chef
def render_template
template_file = find_template
template = IO.read(template_file).chomp
- context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config)
+ secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
+ context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config, secret)
Erubis::Eruby.new(template).evaluate(context)
end
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index 03e3a42e4a..e681d7a49b 100644
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ b/lib/chef/knife/core/bootstrap_context.rb
@@ -30,10 +30,11 @@ class Chef
#
class BootstrapContext
- def initialize(config, run_list, chef_config)
+ def initialize(config, run_list, chef_config, secret)
@config = config
@run_list = run_list
@chef_config = chef_config
+ @secret = secret
end
def bootstrap_environment
@@ -45,15 +46,7 @@ class Chef
end
def encrypted_data_bag_secret
- knife_config[:secret] || begin
- secret_file_path = knife_config[:secret_file]
- expanded_secret_file_path = File.expand_path(secret_file_path.to_s)
- if secret_file_path && File.exist?(expanded_secret_file_path)
- IO.read(expanded_secret_file_path)
- else
- nil
- end
- end
+ @secret
end
def trusted_certs
diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb
index bc49c68448..f8a7619a8a 100644
--- a/lib/chef/knife/data_bag_create.rb
+++ b/lib/chef/knife/data_bag_create.rb
@@ -18,10 +18,12 @@
#
require 'chef/knife'
+require 'chef/knife/data_bag_secret_options'
class Chef
class Knife
class DataBagCreate < Knife
+ include DataBagSecretOptions
deps do
require 'chef/data_bag'
@@ -31,33 +33,6 @@ class Chef
banner "knife data bag create BAG [ITEM] (options)"
category "data bag"
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to encrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to encrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
- def read_secret
- if config[:secret]
- config[:secret]
- else
- Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
- end
- end
-
- def use_encryption
- if config[:secret] && config[:secret_file]
- ui.fatal("please specify only one of --secret, --secret-file")
- exit(1)
- end
- config[:secret] || config[:secret_file]
- end
-
def run
@data_bag_name, @data_bag_item_name = @name_args
@@ -87,11 +62,11 @@ class Chef
if @data_bag_item_name
create_object({ "id" => @data_bag_item_name }, "data_bag_item[#{@data_bag_item_name}]") do |output|
item = Chef::DataBagItem.from_hash(
- if use_encryption
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret)
- else
- output
- end)
+ if encryption_secret_provided?
+ Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret)
+ else
+ output
+ end)
item.data_bag(@data_bag_name)
rest.post_rest("data/#{@data_bag_name}", item)
end
diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb
index b3f53af919..6ef4b33f59 100644
--- a/lib/chef/knife/data_bag_edit.rb
+++ b/lib/chef/knife/data_bag_edit.rb
@@ -18,10 +18,12 @@
#
require 'chef/knife'
+require 'chef/knife/data_bag_secret_options'
class Chef
class Knife
class DataBagEdit < Knife
+ include DataBagSecretOptions
deps do
require 'chef/data_bag_item'
@@ -31,48 +33,17 @@ class Chef
banner "knife data bag edit BAG ITEM (options)"
category "data bag"
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to encrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to encrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
- def read_secret
- if config[:secret]
- config[:secret]
- else
- Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
- end
- end
-
- def use_encryption
- if config[:secret] && config[:secret_file]
- stdout.puts "please specify only one of --secret, --secret-file"
- exit(1)
- end
- config[:secret] || config[:secret_file]
- end
-
def load_item(bag, item_name)
item = Chef::DataBagItem.load(bag, item_name)
- if use_encryption
- Chef::EncryptedDataBagItem.new(item, read_secret).to_hash
+ if encrypted?(item.raw_data)
+ if encryption_secret_provided_ignore_encrypt_flag?
+ return Chef::EncryptedDataBagItem.new(item, read_secret).to_hash, true
+ else
+ ui.fatal("You cannot edit an encrypted data bag without providing the secret.")
+ exit(1)
+ end
else
- item
- end
- end
-
- def edit_item(item)
- output = edit_data(item)
- if use_encryption
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret)
- else
- output
+ return item, false
end
end
@@ -82,11 +53,21 @@ class Chef
stdout.puts opt_parser
exit 1
end
- item = load_item(@name_args[0], @name_args[1])
- output = edit_item(item)
- rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", output)
+
+ item, was_encrypted = load_item(@name_args[0], @name_args[1])
+ edited_item = edit_data(item)
+
+ if was_encrypted || encryption_secret_provided?
+ ui.info("Encrypting data bag using provided secret.")
+ item_to_save = Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited_item, read_secret)
+ else
+ ui.info("Saving data bag unencrypted. To encrypt it, provide an appropriate secret.")
+ item_to_save = edited_item
+ end
+
+ rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save)
stdout.puts("Saved data_bag_item[#{@name_args[1]}]")
- ui.output(output) if config[:print_after]
+ ui.output(edited_item) if config[:print_after]
end
end
end
diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb
index 2ff79b6256..d1b7daa4a2 100644
--- a/lib/chef/knife/data_bag_from_file.rb
+++ b/lib/chef/knife/data_bag_from_file.rb
@@ -19,10 +19,12 @@
require 'chef/knife'
require 'chef/util/path_helper'
+require 'chef/knife/data_bag_secret_options'
class Chef
class Knife
class DataBagFromFile < Knife
+ include DataBagSecretOptions
deps do
require 'chef/data_bag'
@@ -35,38 +37,11 @@ class Chef
banner "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)"
category "data bag"
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to encrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to encrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
option :all,
:short => "-a",
:long => "--all",
:description => "Upload all data bags or all items for specified data bags"
- def read_secret
- if config[:secret]
- config[:secret]
- else
- Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
- end
- end
-
- def use_encryption
- if config[:secret] && config[:secret_file]
- ui.fatal("please specify only one of --secret, --secret-file")
- exit(1)
- end
- config[:secret] || config[:secret_file]
- end
-
def loader
@loader ||= Knife::Core::ObjectLoader.new(DataBagItem, ui)
end
@@ -109,9 +84,8 @@ class Chef
item_paths = normalize_item_paths(items)
item_paths.each do |item_path|
item = loader.load_from("#{data_bags_path}", data_bag, item_path)
- item = if use_encryption
- secret = read_secret
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, secret)
+ item = if encryption_secret_provided?
+ Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, read_secret)
else
item
end
diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb
new file mode 100644
index 0000000000..766006089e
--- /dev/null
+++ b/lib/chef/knife/data_bag_secret_options.rb
@@ -0,0 +1,142 @@
+#
+# Author:: Tyler Ball (<tball@opscode.com>)
+# Copyright:: Copyright (c) 2014 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'mixlib/cli'
+require 'chef/config'
+require 'chef/encrypted_data_bag_item/check_encrypted'
+
+class Chef
+ class Knife
+ module DataBagSecretOptions
+ include Mixlib::CLI
+ include Chef::EncryptedDataBagItem::CheckEncrypted
+
+ # The config object is populated by knife#merge_configs with knife.rb `knife[:*]` config values, but they do
+ # not overwrite the command line properties. It does mean, however, that `knife[:secret]` and `--secret-file`
+ # passed at the same time populate both `config[:secret]` and `config[:secret_file]`. We cannot differentiate
+ # the valid case (`knife[:secret]` in config file and `--secret-file` on CL) and the invalid case (`--secret`
+ # and `--secret-file` on the CL) - thats why I'm storing the CL options in a different config key if they
+ # are provided.
+
+ def self.included(base)
+ base.option :secret,
+ :short => "-s SECRET",
+ :long => "--secret ",
+ :description => "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'",
+ # Need to store value from command line in separate variable - knife#merge_configs populates same keys
+ # on config object from
+ :proc => Proc.new { |s| set_cl_secret(s) }
+
+ base.option :secret_file,
+ :long => "--secret-file SECRET_FILE",
+ :description => "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'",
+ :proc => Proc.new { |sf| set_cl_secret_file(sf) }
+
+ base.option :encrypt,
+ :long => "--encrypt",
+ :description => "If 'secret' or 'secret_file' is present in your config, then encrypt data bags using it",
+ :boolean => true,
+ :default => false
+ end
+
+ def encryption_secret_provided?
+ base_encryption_secret_provided?
+ end
+
+ def encryption_secret_provided_ignore_encrypt_flag?
+ base_encryption_secret_provided?(false)
+ end
+
+ def read_secret
+ # Moving the non 'compile-time' requires into here to speed up knife command loading
+ # IE, if we are not running 'knife data bag *' we don't need to load 'chef/encrypted_data_bag_item'
+ require 'chef/encrypted_data_bag_item'
+
+ if has_cl_secret?
+ config[:secret]
+ elsif has_cl_secret_file?
+ Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
+ elsif secret = knife_config[:secret]
+ secret
+ else
+ secret_file = knife_config[:secret_file]
+ Chef::EncryptedDataBagItem.load_secret(secret_file)
+ end
+ end
+
+ def validate_secrets
+ if has_cl_secret? && has_cl_secret_file?
+ ui.fatal("Please specify only one of --secret, --secret-file")
+ exit(1)
+ end
+
+ if knife_config[:secret] && knife_config[:secret_file]
+ ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config file")
+ exit(1)
+ end
+ end
+
+ private
+
+ ##
+ # Determine if the user has specified an appropriate secret for encrypting data bag items.
+ # @returns boolean
+ def base_encryption_secret_provided?(need_encrypt_flag = true)
+ validate_secrets
+
+ return true if has_cl_secret? || has_cl_secret_file?
+
+ if need_encrypt_flag
+ if config[:encrypt]
+ unless knife_config[:secret] || knife_config[:secret_file]
+ ui.fatal("No secret or secret_file specified in config, unable to encrypt item.")
+ exit(1)
+ end
+ return true
+ end
+ return false
+ elsif knife_config[:secret] || knife_config[:secret_file]
+ # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret
+ return true
+ end
+ return false
+ end
+
+ def has_cl_secret?
+ Chef::Config[:knife].has_key?(:cl_secret)
+ end
+
+ def self.set_cl_secret(s)
+ Chef::Config[:knife][:cl_secret] = s
+ end
+
+ def has_cl_secret_file?
+ Chef::Config[:knife].has_key?(:cl_secret_file)
+ end
+
+ def self.set_cl_secret_file(sf)
+ Chef::Config[:knife][:cl_secret_file] = sf
+ end
+
+ def knife_config
+ Chef::Config.key?(:knife) ? Chef::Config[:knife] : {}
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb
index 519859ca2d..36715286e8 100644
--- a/lib/chef/knife/data_bag_show.rb
+++ b/lib/chef/knife/data_bag_show.rb
@@ -18,10 +18,12 @@
#
require 'chef/knife'
+require 'chef/knife/data_bag_secret_options'
class Chef
class Knife
class DataBagShow < Knife
+ include DataBagSecretOptions
deps do
require 'chef/data_bag'
@@ -31,45 +33,29 @@ class Chef
banner "knife data bag show BAG [ITEM] (options)"
category "data bag"
- option :secret,
- :short => "-s SECRET",
- :long => "--secret ",
- :description => "The secret key to use to decrypt data bag item values",
- :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
-
- option :secret_file,
- :long => "--secret-file SECRET_FILE",
- :description => "A file containing the secret key to use to decrypt data bag item values",
- :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
-
- def read_secret
- if config[:secret]
- config[:secret]
- else
- Chef::EncryptedDataBagItem.load_secret(config[:secret_file])
- end
- end
-
- def use_encryption
- if config[:secret] && config[:secret_file]
- stdout.puts "please specify only one of --secret, --secret-file"
- exit(1)
- end
- config[:secret] || config[:secret_file]
- end
-
def run
display = case @name_args.length
- when 2
- if use_encryption
+ when 2 # Bag and Item names provided
+ secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
+ raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data
+ encrypted = encrypted?(raw_data)
+
+ if encrypted && secret
+ # Users do not need to pass --encrypt to read data, we simply try to use the provided secret
+ ui.info("Encrypted data bag detected, decrypting with provided secret.")
raw = Chef::EncryptedDataBagItem.load(@name_args[0],
@name_args[1],
- read_secret)
+ secret)
format_for_display(raw.to_hash)
+ elsif encrypted && !secret
+ ui.warn("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
+ format_for_display(raw_data)
else
- format_for_display(Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data)
+ ui.info("Unencrypted data bag detected, ignoring any provided secret options.")
+ format_for_display(raw_data)
end
- when 1
+
+ when 1 # Only Bag name provided
format_list_for_display(Chef::DataBag.load(@name_args[0]))
else
stdout.puts opt_parser
@@ -77,7 +63,7 @@ class Chef
end
output(display)
end
+
end
end
end
-
diff --git a/spec/support/shared/matchers.rb b/spec/support/shared/matchers.rb
deleted file mode 100644
index 2e1c660c19..0000000000
--- a/spec/support/shared/matchers.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-
-require 'rspec/expectations'
-require 'spec/support/platform_helpers'
-
-RSpec::Matchers.define :match_environment_variable do |varname|
- match do |actual|
- expected = if windows? && ENV[varname].nil?
- # On Windows, if an environment variable is not set, the command
- # `echo %VARNAME%` outputs %VARNAME%
- "%#{varname}%"
- else
- ENV[varname].to_s
- end
-
- actual == expected
- end
-end
diff --git a/spec/support/shared/matchers/exit_with_code.rb b/spec/support/shared/matchers/exit_with_code.rb
new file mode 100644
index 0000000000..957586c85d
--- /dev/null
+++ b/spec/support/shared/matchers/exit_with_code.rb
@@ -0,0 +1,28 @@
+require 'rspec/expectations'
+
+# Lifted from http://stackoverflow.com/questions/1480537/how-can-i-validate-exits-and-aborts-in-rspec
+RSpec::Matchers.define :exit_with_code do |exp_code|
+ actual = nil
+ match do |block|
+ begin
+ block.call
+ rescue SystemExit => e
+ actual = e.status
+ end
+ actual and actual == exp_code
+ end
+
+ failure_message_for_should do |block|
+ "expected block to call exit(#{exp_code}) but exit" +
+ (actual.nil? ? " not called" : "(#{actual}) was called")
+ end
+
+ failure_message_for_should_not do |block|
+ "expected block not to call exit(#{exp_code})"
+ end
+
+ description do
+ "expect block to call exit(#{exp_code})"
+ end
+
+end
diff --git a/spec/support/shared/matchers/match_environment_variable.rb b/spec/support/shared/matchers/match_environment_variable.rb
new file mode 100644
index 0000000000..c8c905f44a
--- /dev/null
+++ b/spec/support/shared/matchers/match_environment_variable.rb
@@ -0,0 +1,17 @@
+
+require 'rspec/expectations'
+require 'spec/support/platform_helpers'
+
+RSpec::Matchers.define :match_environment_variable do |varname|
+ match do |actual|
+ expected = if windows? && ENV[varname].nil?
+ # On Windows, if an environment variable is not set, the command
+ # `echo %VARNAME%` outputs %VARNAME%
+ "%#{varname}%"
+ else
+ ENV[varname].to_s
+ end
+
+ actual == expected
+ end
+end
diff --git a/spec/unit/dsl/data_query_spec.rb b/spec/unit/dsl/data_query_spec.rb
index 8a985437b7..78cd5569e8 100644
--- a/spec/unit/dsl/data_query_spec.rb
+++ b/spec/unit/dsl/data_query_spec.rb
@@ -86,123 +86,21 @@ describe Chef::DSL::DataQuery do
end
context "when the item is encrypted" do
- let(:default_secret) { "abc123SECRET" }
-
- let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret) }
-
- let(:item) do
- item = Chef::DataBagItem.new
- item.data_bag(bag_name)
- item.raw_data = encoded_data
- item
- end
+ let(:secret) { "abc123SECRET" }
+ let(:enc_data_bag) { double("Chef::EncryptedDataBagItem") }
before do
allow( Chef::DataBagItem ).to receive(:load).with(bag_name, item_name).and_return(item)
+ expect(language).to receive(:encrypted?).and_return(true)
+ expect( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_return(secret)
end
- shared_examples_for "encryption detected" do
- let(:encoded_data) do
- Chef::Config[:data_bag_encrypt_version] = version
- Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret)
- end
-
- before do
- allow( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_return(default_secret)
- end
-
- it "detects encrypted data bag" do
- expect( encryptor ).to receive(:encryptor_keys).at_least(:once).and_call_original
- expect( Chef::Log ).to receive(:debug).with(/Data bag item looks encrypted/)
- language.data_bag_item(bag_name, item_name)
- end
- end
-
- context "when encryption version is 1" do
- include_examples "encryption detected" do
- let(:version) { 1 }
- let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor }
- end
- end
-
- context "when encryption version is 2" do
- include_examples "encryption detected" do
- let(:version) { 2 }
- let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor }
- end
+ it "detects encrypted data bag" do
+ expect( Chef::EncryptedDataBagItem ).to receive(:new).with(raw_data, secret).and_return(enc_data_bag)
+ expect( Chef::Log ).to receive(:debug).with(/Data bag item looks encrypted/)
+ expect(language.data_bag_item(bag_name, item_name)).to eq(enc_data_bag)
end
- context "when encryption version is 3", :ruby_20_only do
- include_examples "encryption detected" do
- let(:version) { 3 }
- let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor }
- end
- end
-
- shared_examples_for "an encrypted data bag item" do
- it "returns an encrypted data bag item" do
- expect( language.data_bag_item(bag_name, item_name, secret) ).to be_a_kind_of(Chef::EncryptedDataBagItem)
- end
-
- it "decrypts the contents of the data bag item" do
- expect( language.data_bag_item(bag_name, item_name, secret).to_hash ).to eql raw_data
- end
- end
-
- context "when a secret is supplied" do
- include_examples "an encrypted data bag item" do
- let(:secret) { default_secret }
- end
- end
-
- context "when a secret is not supplied" do
- before do
- allow( Chef::Config ).to receive(:[]).and_call_original
- expect( Chef::Config ).to receive(:[]).with(:encrypted_data_bag_secret).and_return(path)
- expect( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_call_original
- end
-
- context "when a secret is located at Chef::Config[:encrypted_data_bag_secret]" do
- let(:path) { "/tmp/my_secret" }
-
- before do
- expect( File ).to receive(:exist?).with(path).and_return(true)
- expect( IO ).to receive(:read).with(path).and_return(default_secret)
- end
-
- include_examples "an encrypted data bag item" do
- let(:secret) { nil }
- end
- end
-
- shared_examples_for "no secret file" do
- it "should fail to load the data bag item" do
- expect( Chef::Log ).to receive(:error).with(/Failed to load secret for encrypted data bag item/)
- expect( Chef::Log ).to receive(:error).with(/Failed to load data bag item/)
- expect{ language.data_bag_item(bag_name, item_name) }.to raise_error(error_type, error_message)
- end
- end
-
- context "when Chef::Config[:encrypted_data_bag_secret] is not configured" do
- include_examples "no secret file" do
- let(:path) { nil }
- let(:error_type) { ArgumentError }
- let(:error_message) { /No secret specified and no secret found/ }
- end
- end
-
- context "when Chef::Config[:encrypted_data_bag_secret] does not exist" do
- include_examples "no secret file" do
- before do
- expect( File ).to receive(:exist?).with(path).and_return(false)
- end
-
- let(:path) { "/tmp/my_secret" }
- let(:error_type) { Errno::ENOENT }
- let(:error_message) { /file not found/ }
- end
- end
- end
end
end
end
diff --git a/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb b/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb
new file mode 100644
index 0000000000..1da5efb36e
--- /dev/null
+++ b/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb
@@ -0,0 +1,95 @@
+#
+# Author:: Tyler Ball (<tball@getchef.com>)
+# Copyright:: Copyright (c) 2010-2014 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/encrypted_data_bag_item/check_encrypted'
+
+class CheckEncryptedTester
+ include Chef::EncryptedDataBagItem::CheckEncrypted
+end
+
+describe Chef::EncryptedDataBagItem::CheckEncrypted do
+
+ let(:tester) { CheckEncryptedTester.new }
+
+ it "detects the item is not encrypted when the data is empty" do
+ expect(tester.encrypted?({})).to eq(false)
+ end
+
+ it "detects the item is not encrypted when the data only contains an id" do
+ expect(tester.encrypted?({id: "foo"})).to eq(false)
+ end
+
+ context "when the item is encrypted" do
+
+ let(:default_secret) { "abc123SECRET" }
+ let(:item_name) { "item_name" }
+ let(:raw_data) {{
+ "id" => item_name,
+ "greeting" => "hello",
+ "nested" => {
+ "a1" => [1, 2, 3],
+ "a2" => { "b1" => true }
+ }
+ }}
+
+ let(:version) { 1 }
+ let(:encoded_data) do
+ Chef::Config[:data_bag_encrypt_version] = version
+ Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret)
+ end
+
+ it "does not detect encryption when the item version is unknown" do
+ # It shouldn't be possible for someone to normally encrypt an item with an unknown version - they would have to
+ # do something funky like encrypting it and then manually changing the version
+ modified_encoded_data = encoded_data
+ modified_encoded_data["greeting"]["version"] = 4
+ expect(tester.encrypted?(modified_encoded_data)).to eq(false)
+ end
+
+ shared_examples_for "encryption detected" do
+ it "detects encrypted data bag" do
+ expect( encryptor ).to receive(:encryptor_keys).at_least(:once).and_call_original
+ expect(tester.encrypted?(encoded_data)).to eq(true)
+ end
+ end
+
+ context "when encryption version is 1" do
+ include_examples "encryption detected" do
+ let(:version) { 1 }
+ let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor }
+ end
+ end
+
+ context "when encryption version is 2" do
+ include_examples "encryption detected" do
+ let(:version) { 2 }
+ let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor }
+ end
+ end
+
+ context "when encryption version is 3", :ruby_20_only do
+ include_examples "encryption detected" do
+ let(:version) { 3 }
+ let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor }
+ end
+ end
+
+ end
+
+end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
index a1c5d168dd..d5c668753e 100644
--- a/spec/unit/knife/bootstrap_spec.rb
+++ b/spec/unit/knife/bootstrap_spec.rb
@@ -30,6 +30,7 @@ describe Chef::Knife::Bootstrap do
k.merge_configs
k.ui.stub(:stderr).and_return(stderr)
+ allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
k
end
@@ -221,10 +222,6 @@ describe Chef::Knife::Bootstrap do
k
end
- # Include a data bag secret in the options to prevent Bootstrap from
- # attempting to access /etc/chef/encrypted_data_bag_secret, which
- # can fail when the file exists but can't be accessed by the user
- # running the tests.
let(:options){ ["--bootstrap-no-proxy", setting, "-s", "foo"] }
let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
let(:rendered_template) do
@@ -290,7 +287,6 @@ describe Chef::Knife::Bootstrap do
describe "specifying the encrypted data bag secret key" do
let(:secret) { "supersekret" }
- let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') }
let(:options) { [] }
let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) }
let(:rendered_template) do
@@ -299,60 +295,18 @@ describe Chef::Knife::Bootstrap do
knife.render_template
end
- context "via --secret" do
- let(:options){ ["--secret", secret] }
-
- it "creates a secret file" do
- rendered_template.should match(%r{#{secret}})
- end
-
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
- end
-
- context "via --secret-file" do
- let(:options) { ["--secret-file", secret_file] }
- let(:secret) { IO.read(secret_file) }
-
- it "creates a secret file" do
- rendered_template.should match(%r{#{secret}})
- end
-
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
+ it "creates a secret file" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ rendered_template.should match(%r{#{secret}})
end
- context "secret via config" do
- before do
- Chef::Config[:knife][:secret] = secret
- end
-
- it "creates a secret file" do
- rendered_template.should match(%r{#{secret}})
- end
-
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
+ it "renders the client.rb with an encrypted_data_bag_secret entry" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
end
- context "secret-file via config" do
- let(:secret) { IO.read(secret_file) }
-
- before do
- Chef::Config[:knife][:secret_file] = secret_file
- end
-
- it "creates a secret file" do
- rendered_template.should match(%r{#{secret}})
- end
-
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
- end
end
describe "when transferring trusted certificates" do
diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb
index 6427071a6b..266991a7dd 100644
--- a/spec/unit/knife/core/bootstrap_context_spec.rb
+++ b/spec/unit/knife/core/bootstrap_context_spec.rb
@@ -29,9 +29,10 @@ describe Chef::Knife::Core::BootstrapContext do
:validation_client_name => 'chef-validator-testing'
}
end
- let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') }
- subject(:bootstrap_context) { described_class.new(config, run_list, chef_config) }
+ let(:secret) { nil }
+
+ subject(:bootstrap_context) { described_class.new(config, run_list, chef_config, secret) }
it "runs chef with the first-boot.json in the _default environment" do
bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -E _default"
@@ -105,39 +106,9 @@ EXPECTED
end
describe "when an encrypted_data_bag_secret is provided" do
- context "via config[:secret]" do
- let(:chef_config) do
- {
- :knife => {:secret => "supersekret" }
- }
- end
- it "reads the encrypted_data_bag_secret" do
- bootstrap_context.encrypted_data_bag_secret.should eq "supersekret"
- end
- end
-
- context "via config[:secret_file]" do
- let(:chef_config) do
- {
- :knife => {:secret_file => secret_file}
- }
- end
- it "reads the encrypted_data_bag_secret" do
- bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file)
- end
- end
-
- context "via config[:secret_file] with short home path" do
- let(:chef_config) do
- home_path = File.expand_path("~")
- shorted_secret_file_path = secret_file.gsub(home_path, "~")
- {
- :knife => {:secret_file => shorted_secret_file_path}
- }
- end
- it "reads the encrypted_data_bag_secret" do
- bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file)
- end
+ let(:secret) { "supersekret" }
+ it "reads the encrypted_data_bag_secret" do
+ bootstrap_context.encrypted_data_bag_secret.should eq "supersekret"
end
end
diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb
index 984be8e58a..c31c88577d 100644
--- a/spec/unit/knife/data_bag_create_spec.rb
+++ b/spec/unit/knife/data_bag_create_spec.rb
@@ -20,97 +20,89 @@
require 'spec_helper'
require 'tempfile'
-module ChefSpecs
- class ChefRest
- attr_reader :args_received
- def initialize
- @args_received = []
- end
-
- def post_rest(*args)
- @args_received << args
- end
+describe Chef::Knife::DataBagCreate do
+ let(:knife) do
+ k = Chef::Knife::DataBagCreate.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
end
-end
+ let(:rest) { double("Chef::REST") }
+ let(:stdout) { StringIO.new }
-describe Chef::Knife::DataBagCreate do
- before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::DataBagCreate.new
- @rest = ChefSpecs::ChefRest.new
- @knife.stub(:rest).and_return(@rest)
- @stdout = StringIO.new
- @knife.ui.stub(:stdout).and_return(@stdout)
- end
+ let(:bag_name) { "sudoing_admins" }
+ let(:item_name) { "ME" }
+
+ let(:secret) { "abc123SECRET" }
+ let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }}
- it "creates a data bag when given one argument" do
- @knife.name_args = ['sudoing_admins']
- @rest.should_receive(:post_rest).with("data", {"name" => "sudoing_admins"})
- @knife.ui.should_receive(:info).with("Created data_bag[sudoing_admins]")
+ let(:config) { {} }
- @knife.run
+ before do
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ knife.name_args = [bag_name, item_name]
+ allow(knife).to receive(:config).and_return(config)
end
it "tries to create a data bag with an invalid name when given one argument" do
- @knife.name_args = ['invalid&char']
- @knife.should_receive(:exit).with(1)
-
- @knife.run
+ knife.name_args = ['invalid&char']
+ expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName)
+ expect {knife.run}.to exit_with_code(1)
end
- it "creates a data bag item when given two arguments" do
- @knife.name_args = ['sudoing_admins', 'ME']
- user_supplied_hash = {"login_name" => "alphaomega", "id" => "ME"}
- data_bag_item = Chef::DataBagItem.from_hash(user_supplied_hash)
- data_bag_item.data_bag("sudoing_admins")
- @knife.should_receive(:create_object).and_yield(user_supplied_hash)
- @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered
- @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered
+ context "when given one argument" do
+ before do
+ knife.name_args = [bag_name]
+ end
+
+ it "creates a data bag" do
+ expect(rest).to receive(:post_rest).with("data", {"name" => bag_name})
+ expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]")
- @knife.run
+ knife.run
+ end
end
- describe "encrypted data bag items" do
- before(:each) do
- @secret = "abc123SECRET"
- @plain_data = {"login_name" => "alphaomega", "id" => "ME"}
- @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
- @secret)
- @knife.name_args = ['sudoing_admins', 'ME']
- @knife.should_receive(:create_object).and_yield(@plain_data)
- data_bag_item = Chef::DataBagItem.from_hash(@enc_data)
- data_bag_item.data_bag("sudoing_admins")
-
- # Random IV is used each time the data bag item is encrypted, so values
- # will not be equal if we re-encrypt.
- Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_data)
-
- @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered
- @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered
-
- @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- @secret_file.puts(@secret)
- @secret_file.flush
+ context "no secret is specified for encryption" do
+ let(:item) do
+ item = Chef::DataBagItem.from_hash(raw_hash)
+ item.data_bag(bag_name)
+ item
end
- after do
- @secret_file.close
- @secret_file.unlink
+ it "creates a data bag item" do
+ expect(knife).to receive(:create_object).and_yield(raw_hash)
+ expect(knife).to receive(:encryption_secret_provided?).and_return(false)
+ expect(rest).to receive(:post_rest).with("data", {'name' => bag_name}).ordered
+ expect(rest).to receive(:post_rest).with("data/#{bag_name}", item).ordered
+
+ knife.run
end
+ end
+
+ context "a secret is specified for encryption" do
+ let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) }
- it "creates an encrypted data bag item via --secret" do
- @knife.stub(:config).and_return({:secret => @secret})
- @knife.run
+ let(:item) do
+ item = Chef::DataBagItem.from_hash(encoded_data)
+ item.data_bag(bag_name)
+ item
end
- it "creates an encrypted data bag item via --secret_file" do
- secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- secret_file.puts(@secret)
- secret_file.flush
- @knife.stub(:config).and_return({:secret_file => secret_file.path})
- @knife.run
+ it "creates an encrypted data bag item" do
+ expect(knife).to receive(:create_object).and_yield(raw_hash)
+ expect(knife).to receive(:encryption_secret_provided?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::EncryptedDataBagItem)
+ .to receive(:encrypt_data_bag_item)
+ .with(raw_hash, secret)
+ .and_return(encoded_data)
+ expect(rest).to receive(:post_rest).with("data", {"name" => bag_name}).ordered
+ expect(rest).to receive(:post_rest).with("data/#{bag_name}", item).ordered
+
+ knife.run
end
end
diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb
index 866ca99174..6f19b5e63e 100644
--- a/spec/unit/knife/data_bag_edit_spec.rb
+++ b/spec/unit/knife/data_bag_edit_spec.rb
@@ -21,73 +21,107 @@ require 'tempfile'
describe Chef::Knife::DataBagEdit do
before do
- @plain_data = {"login_name" => "alphaomega", "id" => "item_name"}
- @edited_data = {
- "login_name" => "rho", "id" => "item_name",
- "new_key" => "new_value" }
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ knife.name_args = [bag_name, item_name]
+ allow(knife).to receive(:config).and_return(config)
+ end
+
+ let(:knife) do
+ k = Chef::Knife::DataBagEdit.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:raw_hash) { {"login_name" => "alphaomega", "id" => "item_name"} }
+ let(:db) { Chef::DataBagItem.from_hash(raw_hash)}
+ let(:raw_edited_hash) { {"login_name" => "rho", "id" => "item_name", "new_key" => "new_value"} }
+
+ let(:rest) { double("Chef::REST") }
+ let(:stdout) { StringIO.new }
- Chef::Config[:node_name] = "webmonkey.example.com"
+ let(:bag_name) { "sudoing_admins" }
+ let(:item_name) { "ME" }
- @knife = Chef::Knife::DataBagEdit.new
- @rest = double('chef-rest-mock')
- @knife.stub(:rest).and_return(@rest)
+ let(:secret) { "abc123SECRET" }
- @stdout = StringIO.new
- @knife.stub(:stdout).and_return(@stdout)
- @log = Chef::Log
- @knife.name_args = ['bag_name', 'item_name']
+ let(:config) { {} }
+
+ let(:is_encrypted?) { false }
+ let(:transmitted_hash) { raw_edited_hash }
+ let(:data_to_edit) { db }
+
+ shared_examples_for "editing a data bag" do
+ it "correctly edits then uploads the data bag" do
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db)
+ expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(is_encrypted?)
+ expect(knife).to receive(:edit_data).with(data_to_edit).and_return(raw_edited_hash)
+ expect(rest).to receive(:put_rest).with("data/#{bag_name}/#{item_name}", transmitted_hash).ordered
+
+ knife.run
+ end
end
it "requires data bag and item arguments" do
- @knife.name_args = []
- lambda { @knife.run }.should raise_error(SystemExit)
- @stdout.string.should match(/^You must supply the data bag and an item to edit/)
+ knife.name_args = []
+ expect(stdout).to receive(:puts).twice.with(anything)
+ expect {knife.run}.to exit_with_code(1)
+ expect(stdout.string).to eq("")
end
- it "saves edits on a data bag item" do
- Chef::DataBagItem.stub(:load).with('bag_name', 'item_name').and_return(@plain_data)
- @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data)
- @rest.should_receive(:put_rest).with("data/bag_name/item_name", @edited_data).ordered
- @knife.run
+ context "when no secret is provided" do
+ include_examples "editing a data bag"
end
- describe "encrypted data bag items" do
- before(:each) do
- @secret = "abc123SECRET"
- @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
- @secret)
- @enc_edited_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@edited_data,
- @secret)
- Chef::DataBagItem.stub(:load).with('bag_name', 'item_name').and_return(@enc_data)
-
- # Random IV is used each time the data bag item is encrypted, so values
- # will not be equal if we encrypt same value twice.
- Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_edited_data)
-
- @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- @secret_file.puts(@secret)
- @secret_file.flush
+ context "when config[:print_after] is set" do
+ let(:config) { {:print_after => true} }
+ before do
+ expect(knife.ui).to receive(:output).with(raw_edited_hash)
end
- after do
- @secret_file.close
- @secret_file.unlink
+ include_examples "editing a data bag"
+ end
+
+ context "when a secret is provided" do
+ let!(:enc_raw_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) }
+ let!(:enc_edited_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_edited_hash, secret) }
+ let(:transmitted_hash) { enc_edited_hash }
+
+ before(:each) do
+ expect(knife).to receive(:read_secret).at_least(1).times.and_return(secret)
+ expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(raw_edited_hash, secret).and_return(enc_edited_hash)
end
- it "decrypts and encrypts via --secret" do
- @knife.stub(:config).and_return({:secret => @secret})
- @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data)
- @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered
+ context "the data bag starts encrypted" do
+ let(:is_encrypted?) { true }
+ let(:db) { Chef::DataBagItem.from_hash(enc_raw_hash) }
+ # If the data bag is encrypted, it gets passed to `edit` as a hash. Otherwise, it gets passed as a DataBag
+ let (:data_to_edit) { raw_hash }
+
+ before(:each) do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ end
- @knife.run
+ include_examples "editing a data bag"
end
- it "decrypts and encrypts via --secret_file" do
- @knife.stub(:config).and_return({:secret_file => @secret_file.path})
- @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data)
- @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered
+ context "the data bag starts unencrypted" do
+ before(:each) do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).exactly(0).times
+ expect(knife).to receive(:encryption_secret_provided?).and_return(true)
+ end
- @knife.run
+ include_examples "editing a data bag"
end
end
+
+ it "fails to edit an encrypted data bag if the secret is missing" do
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db)
+ expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(true)
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
+
+ expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.")
+ expect {knife.run}.to exit_with_code(1)
+ end
+
end
diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb
index 1ad6b4712c..662af3f0d6 100644
--- a/spec/unit/knife/data_bag_from_file_spec.rb
+++ b/spec/unit/knife/data_bag_from_file_spec.rb
@@ -26,168 +26,145 @@ Chef::Knife::DataBagFromFile.load_deps
describe Chef::Knife::DataBagFromFile do
before :each do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::DataBagFromFile.new
- @rest = double("Chef::REST")
- @knife.stub(:rest).and_return(@rest)
- @stdout = StringIO.new
- @knife.ui.stub(:stdout).and_return(@stdout)
- @tmp_dir = Dir.mktmpdir
- @db_folder = File.join(@tmp_dir, 'data_bags', 'bag_name')
- FileUtils.mkdir_p(@db_folder)
- @db_file = Tempfile.new(["data_bag_from_file_test", ".json"], @db_folder)
- @db_file2 = Tempfile.new(["data_bag_from_file_test2", ".json"], @db_folder)
- @db_folder2 = File.join(@tmp_dir, 'data_bags', 'bag_name2')
- FileUtils.mkdir_p(@db_folder2)
- @db_file3 = Tempfile.new(["data_bag_from_file_test3", ".json"], @db_folder2)
- @plain_data = {
- "id" => "item_name",
- "greeting" => "hello",
- "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }}
- }
- @db_file.write(@plain_data.to_json)
- @db_file.flush
- @knife.instance_variable_set(:@name_args, ['bag_name', @db_file.path])
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ FileUtils.mkdir_p([db_folder, db_folder2])
+ db_file.write(plain_data.to_json)
+ db_file.flush
+ allow(knife).to receive(:config).and_return(config)
+ allow(Chef::Knife::Core::ObjectLoader).to receive(:new).and_return(loader)
end
# We have to explicitly clean up Tempfile on Windows because it said so.
after :each do
- @db_file.close
- @db_file2.close
- @db_file3.close
- FileUtils.rm_rf(@db_folder)
- FileUtils.rm_rf(@db_folder2)
- FileUtils.remove_entry_secure @tmp_dir
+ db_file.close
+ db_file2.close
+ db_file3.close
+ FileUtils.rm_rf(db_folder)
+ FileUtils.rm_rf(db_folder2)
+ FileUtils.remove_entry_secure tmp_dir
end
+ let(:knife) do
+ k = Chef::Knife::DataBagFromFile.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:tmp_dir) { Dir.mktmpdir }
+ let(:db_folder) { File.join(tmp_dir, data_bags_path, bag_name) }
+ let(:db_file) { Tempfile.new(["data_bag_from_file_test", ".json"], db_folder) }
+ let(:db_file2) { Tempfile.new(["data_bag_from_file_test2", ".json"], db_folder) }
+ let(:db_folder2) { File.join(tmp_dir, data_bags_path, bag_name2) }
+ let(:db_file3) { Tempfile.new(["data_bag_from_file_test3", ".json"], db_folder2) }
+
+ def new_bag_expects(b = bag_name, d = plain_data)
+ data_bag = double
+ expect(data_bag).to receive(:data_bag).with(b)
+ expect(data_bag).to receive(:raw_data=).with(d)
+ expect(data_bag).to receive(:save)
+ expect(data_bag).to receive(:data_bag)
+ expect(data_bag).to receive(:id)
+ data_bag
+ end
+
+ let(:loader) { double("Knife::Core::ObjectLoader") }
+
+ let(:data_bags_path) { "data_bags" }
+ let(:plain_data) { {
+ "id" => "item_name",
+ "greeting" => "hello",
+ "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }}
+ } }
+ let(:enc_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(plain_data, secret) }
+
+ let(:rest) { double("Chef::REST") }
+ let(:stdout) { StringIO.new }
+
+ let(:bag_name) { "sudoing_admins" }
+ let(:bag_name2) { "sudoing_admins2" }
+ let(:item_name) { "ME" }
+
+ let(:secret) { "abc123SECRET" }
+
+ let(:config) { {} }
+
it "loads from a file and saves" do
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save)
- @knife.run
-
- dbag.data_bag.should == 'bag_name'
- dbag.raw_data.should == @plain_data
+ knife.name_args = [bag_name, db_file.path]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects)
+
+ knife.run
end
- it "loads all from a mutiple files and saves" do
- @knife.name_args = [ 'bag_name', @db_file.path, @db_file2.path ]
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save).twice
- @knife.run
-
- dbag.data_bag.should == 'bag_name'
- dbag.raw_data.should == @plain_data
+ it "loads all from multiple files and saves" do
+ knife.name_args = [ bag_name, db_file.path, db_file2.path ]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects)
+
+ knife.run
end
it "loads all from a folder and saves" do
- @knife.name_args = [ 'bag_name', @db_folder ]
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
- @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save).twice
- @knife.run
+ knife.name_args = [ bag_name, db_folder ]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects)
+
+ knife.run
end
describe "loading all data bags" do
- before do
- @pwd = Dir.pwd
- Dir.chdir(@tmp_dir)
- end
-
- after do
- Dir.chdir(@pwd)
- end
-
it "loads all data bags when -a or --all options is provided" do
- @knife.name_args = []
- @knife.stub(:config).and_return({:all => true})
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file.path)).
- and_return(@plain_data)
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file2.path)).
- and_return(@plain_data)
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)).
- and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save).exactly(3).times
- @knife.run
+ knife.name_args = []
+ config[:all] = true
+ expect(loader).to receive(:find_all_object_dirs).with("./#{data_bags_path}").and_return([bag_name, bag_name2])
+ expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name}").and_return([File.basename(db_file.path), File.basename(db_file2.path)])
+ expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)])
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data)
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(new_bag_expects, new_bag_expects, new_bag_expects(bag_name2))
+
+ knife.run
end
it "loads all data bags items when -a or --all options is provided" do
- @knife.name_args = ["bag_name2"]
- @knife.stub(:config).and_return({:all => true})
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)).
- and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save)
- @knife.run
- dbag.data_bag.should == 'bag_name2'
- dbag.raw_data.should == @plain_data
+ knife.name_args = [bag_name2]
+ config[:all] = true
+ expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)])
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name2))
+
+ knife.run
end
end
describe "encrypted data bag items" do
before(:each) do
- @secret = "abc123SECRET"
- @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
- @secret)
-
- # Random IV is used each time the data bag item is encrypted, so values
- # will not be equal if we re-encrypt.
- Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_data)
-
- @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- @secret_file.puts(@secret)
- @secret_file.flush
- end
-
- after do
- @secret_file.close
- @secret_file.unlink
+ expect(knife).to receive(:encryption_secret_provided?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(plain_data, secret).and_return(enc_data)
end
it "encrypts values when given --secret" do
- @knife.stub(:config).and_return({:secret => @secret})
-
- @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", @db_file.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save)
- @knife.run
- dbag.data_bag.should == 'bag_name'
- dbag.raw_data.should == @enc_data
- end
-
- it "encrypts values when given --secret_file" do
- @knife.stub(:config).and_return({:secret_file => @secret_file.path})
+ knife.name_args = [bag_name, db_file.path]
+ expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data)
+ expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name, enc_data))
- @knife.loader.stub(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
- dbag = Chef::DataBagItem.new
- Chef::DataBagItem.stub(:new).and_return(dbag)
- dbag.should_receive(:save)
- @knife.run
- dbag.data_bag.should == 'bag_name'
- dbag.raw_data.should == @enc_data
+ knife.run
end
end
describe "command line parsing" do
it "prints help if given no arguments" do
- @knife.instance_variable_set(:@name_args, [])
- lambda { @knife.run }.should raise_error(SystemExit)
- help_text = "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)"
- help_text_regex = Regexp.new("^#{Regexp.escape(help_text)}")
- @stdout.string.should match(help_text_regex)
+ knife.name_args = [bag_name]
+ expect {knife.run}.to exit_with_code(1)
+ expect(stdout.string).to start_with("knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)")
end
end
diff --git a/spec/unit/knife/data_bag_secret_options_spec.rb b/spec/unit/knife/data_bag_secret_options_spec.rb
new file mode 100644
index 0000000000..0a2d8ca4bf
--- /dev/null
+++ b/spec/unit/knife/data_bag_secret_options_spec.rb
@@ -0,0 +1,165 @@
+#
+# Author:: Tyler Ball (<tball@opscode.com>)
+# Copyright:: Copyright (c) 2009-2014 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/knife'
+require 'chef/config'
+require 'tempfile'
+
+class ExampleDataBagCommand < Chef::Knife
+ include Chef::Knife::DataBagSecretOptions
+end
+
+describe Chef::Knife::DataBagSecretOptions do
+ let(:example_db) do
+ k = ExampleDataBagCommand.new
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
+ end
+
+ let(:stdout) { StringIO.new }
+
+ let(:secret) { "abc123SECRET" }
+ let(:secret_file) do
+ sfile = Tempfile.new("encrypted_data_bag_secret")
+ sfile.puts(secret)
+ sfile.flush
+ sfile
+ end
+
+ after do
+ secret_file.close
+ secret_file.unlink
+ end
+
+ describe "#validate_secrets" do
+
+ it "throws an error when provided with both --secret and --secret-file on the CL" do
+ Chef::Config[:knife][:cl_secret_file] = secret_file.path
+ Chef::Config[:knife][:cl_secret] = secret
+ expect(example_db).to receive(:exit).with(1)
+ expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file")
+
+ example_db.validate_secrets
+ end
+
+ it "throws an error when provided with `secret` and `secret_file` in knife.rb" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db).to receive(:exit).with(1)
+ expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config file")
+
+ example_db.validate_secrets
+ end
+
+ end
+
+ describe "#read_secret" do
+
+ it "returns the secret first" do
+ Chef::Config[:knife][:cl_secret] = secret
+ expect(example_db).to receive(:config).and_return({ :secret => secret })
+ expect(example_db.read_secret).to eq(secret)
+ end
+
+ it "returns the secret_file only if secret does not exist" do
+ Chef::Config[:knife][:cl_secret_file] = secret_file.path
+ expect(example_db).to receive(:config).and_return({ :secret_file => secret_file.path })
+ expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents")
+ expect(example_db.read_secret).to eq("secret file contents")
+ end
+
+ it "returns the secret from the knife.rb config" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db.read_secret).to eq(secret)
+ end
+
+ it "returns the secret_file from the knife.rb config only if the secret does not exist" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents")
+ expect(example_db.read_secret).to eq("secret file contents")
+ end
+
+ end
+
+ describe "#encryption_secret_provided?" do
+
+ it "returns true if the secret is passed on the CL" do
+ Chef::Config[:knife][:cl_secret] = secret
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "returns true if the secret_file is passed on the CL" do
+ Chef::Config[:knife][:cl_secret_file] = secret_file.path
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "returns true if --encrypt is passed on the CL and :secret is in config" do
+ expect(example_db).to receive(:config).and_return({ :encrypt => true })
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "returns true if --encrypt is passed on the CL and :secret_file is in config" do
+ expect(example_db).to receive(:config).and_return({ :encrypt => true })
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ expect(example_db.encryption_secret_provided?).to eq(true)
+ end
+
+ it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the config" do
+ expect(example_db).to receive(:config).and_return({ :encrypt => true })
+ expect(example_db).to receive(:exit).with(1)
+ expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.")
+ example_db.encryption_secret_provided?
+ end
+
+ it "returns false if no secret is passed" do
+ expect(example_db).to receive(:config).and_return({})
+ expect(example_db.encryption_secret_provided?).to eq(false)
+ end
+
+ it "returns false if --encrypt is not provided and :secret is in the config" do
+ expect(example_db).to receive(:config).and_return({})
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db.encryption_secret_provided?).to eq(false)
+ end
+
+ it "returns false if --encrypt is not provided and :secret_file is in the config" do
+ expect(example_db).to receive(:config).and_return({})
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ expect(example_db.encryption_secret_provided?).to eq(false)
+ end
+
+ it "returns true if --encrypt is not provided, :secret is in the config and need_encrypt_flag is false" do
+ Chef::Config[:knife][:secret] = secret
+ expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true)
+ end
+
+ it "returns true if --encrypt is not provided, :secret_file is in the config and need_encrypt_flag is false" do
+ Chef::Config[:knife][:secret_file] = secret_file.path
+ expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true)
+ end
+
+ it "returns false if --encrypt is not provided and need_encrypt_flag is false" do
+ expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(false)
+ end
+
+ end
+
+end
diff --git a/spec/unit/knife/data_bag_show_spec.rb b/spec/unit/knife/data_bag_show_spec.rb
index 4aa642fc4b..1125d99c2a 100644
--- a/spec/unit/knife/data_bag_show_spec.rb
+++ b/spec/unit/knife/data_bag_show_spec.rb
@@ -25,97 +25,99 @@ require 'chef/json_compat'
require 'tempfile'
describe Chef::Knife::DataBagShow do
+
before do
- Chef::Config[:node_name] = "webmonkey.example.com"
- @knife = Chef::Knife::DataBagShow.new
- @knife.config[:format] = 'json'
- @rest = double("Chef::REST")
- allow(@knife).to receive(:rest).and_return(@rest)
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ Chef::Config[:node_name] = "webmonkey.example.com"
+ knife.name_args = [bag_name, item_name]
+ allow(knife).to receive(:config).and_return(config)
end
-
- it "prints the ids of the data bag items when given a bag name" do
- @knife.instance_variable_set(:@name_args, ['bag_o_data'])
- data_bag_contents = { "baz"=>"http://localhost:4000/data/bag_o_data/baz",
- "qux"=>"http://localhost:4000/data/bag_o_data/qux"}
- expect(Chef::DataBag).to receive(:load).and_return(data_bag_contents)
- expected = %q|[
- "baz",
- "qux"
-]|
- @knife.run
- expect(@stdout.string.strip).to eq(expected)
+ let(:knife) do
+ k = Chef::Knife::DataBagShow.new
+ allow(k).to receive(:rest).and_return(rest)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
+ k
end
- it "prints the contents of the data bag item when given a bag and item name" do
- @knife.instance_variable_set(:@name_args, ['bag_o_data', 'an_item'])
- data_item = Chef::DataBagItem.new.tap {|item| item.raw_data = {"id" => "an_item", "zsh" => "victory_through_tabbing"}}
+ let(:rest) { double("Chef::REST") }
+ let(:stdout) { StringIO.new }
- expect(Chef::DataBagItem).to receive(:load).with('bag_o_data', 'an_item').and_return(data_item)
-
- @knife.run
- expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(data_item.raw_data)
- end
+ let(:bag_name) { "sudoing_admins" }
+ let(:item_name) { "ME" }
- it "should pretty print the data bag contents" do
- @knife.instance_variable_set(:@name_args, ['bag_o_data', 'an_item'])
- data_item = Chef::DataBagItem.new.tap {|item| item.raw_data = {"id" => "an_item", "zsh" => "victory_through_tabbing"}}
+ let(:data_bag_contents) { { "id" => "id", "baz"=>"http://localhost:4000/data/bag_o_data/baz",
+ "qux"=>"http://localhost:4000/data/bag_o_data/qux"} }
+ let(:enc_hash) {Chef::EncryptedDataBagItem.encrypt_data_bag_item(data_bag_contents, secret)}
+ let(:data_bag) {Chef::DataBagItem.from_hash(data_bag_contents)}
+ let(:data_bag_with_encoded_hash) { Chef::DataBagItem.from_hash(enc_hash) }
+ let(:enc_data_bag) { Chef::EncryptedDataBagItem.new(enc_hash, secret) }
- expect(Chef::DataBagItem).to receive(:load).with('bag_o_data', 'an_item').and_return(data_item)
+ let(:secret) { "abc123SECRET" }
+ #
+ # let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }}
+ #
+ let(:config) { {format: "json"} }
- @knife.run
- expect(@stdout.string).to eql("{\n \"id\": \"an_item\",\n \"zsh\": \"victory_through_tabbing\"\n}\n")
- end
+ context "Data bag to show is encrypted" do
+ before do
+ allow(knife).to receive(:encrypted?).and_return(true)
+ end
- describe "encrypted data bag items" do
- before(:each) do
- @secret = "abc123SECRET"
- @plain_data = {
- "id" => "item_name",
- "greeting" => "hello",
- "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }}
- }
- @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
- @secret)
- @knife.instance_variable_set(:@name_args, ['bag_name', 'item_name'])
-
- @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
- @secret_file.puts(@secret)
- @secret_file.flush
+ it "decrypts and displays the encrypted data bag when the secret is provided" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash)
+ expect(knife.ui).to receive(:info).with("Encrypted data bag detected, decrypting with provided secret.")
+ expect(Chef::EncryptedDataBagItem).to receive(:load).with(bag_name, item_name, secret).and_return(enc_data_bag)
+
+ expected = %q|baz: http://localhost:4000/data/bag_o_data/baz
+id: id
+qux: http://localhost:4000/data/bag_o_data/qux|
+ knife.run
+ expect(stdout.string.strip).to eq(expected)
end
- after do
- @secret_file.close
- @secret_file.unlink
+ it "displays the encrypted data bag when the secret is not provided" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash)
+ expect(knife.ui).to receive(:warn).with("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
+
+ knife.run
+ expect(stdout.string.strip).to include("baz", "qux", "cipher")
end
+ end
- it "prints the decrypted contents of an item when given --secret" do
- allow(@knife).to receive(:config).and_return({:secret => @secret})
- expect(Chef::EncryptedDataBagItem).to receive(:load).
- with('bag_name', 'item_name', @secret).
- and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret))
- @knife.run
- expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(@plain_data)
+ context "Data bag to show is not encrypted" do
+ before do
+ allow(knife).to receive(:encrypted?).and_return(false)
+ expect(knife).to receive(:read_secret).exactly(0).times
end
- it "prints the decrypted contents of an item when given --secret_file" do
- allow(@knife).to receive(:config).and_return({:secret_file => @secret_file.path})
- expect(Chef::EncryptedDataBagItem).to receive(:load).
- with('bag_name', 'item_name', @secret).
- and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret))
- @knife.run
- expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(@plain_data)
+ it "displays the data bag" do
+ expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag)
+ expect(knife.ui).to receive(:info).with("Unencrypted data bag detected, ignoring any provided secret options.")
+
+ expected = %q|baz: http://localhost:4000/data/bag_o_data/baz
+id: id
+qux: http://localhost:4000/data/bag_o_data/qux|
+ knife.run
+ expect(stdout.string.strip).to eq(expected)
end
end
- describe "command line parsing" do
- it "prints help if given no arguments" do
- @knife.instance_variable_set(:@name_args, [])
- expect { @knife.run }.to raise_error(SystemExit)
- expect(@stdout.string).to match(/^knife data bag show BAG \[ITEM\] \(options\)/)
- end
+ it "displays the list of items in the data bag when only one @name_arg is provided" do
+ knife.name_args = [bag_name]
+ expect(Chef::DataBag).to receive(:load).with(bag_name).and_return({})
+
+ knife.run
+ expect(stdout.string.strip).to eq("")
+ end
+
+ it "raises an error when no @name_args are provided" do
+ knife.name_args = []
+
+ expect {knife.run}.to exit_with_code(1)
+ expect(stdout.string).to start_with("knife data bag show BAG [ITEM] (options)")
end
end