diff options
author | John Keiser <jkeiser@opscode.com> | 2013-10-15 20:41:52 -0700 |
---|---|---|
committer | John Keiser <jkeiser@opscode.com> | 2013-10-15 20:41:52 -0700 |
commit | e1c6bef68597f5c4f418107ef9e3638e44cc8cf7 (patch) | |
tree | c6871e3dcded48265f2b82c26ab13cd62a0ba2be | |
parent | 2da5abd0648f3ba6977d3a69d858078237bee585 (diff) | |
download | chef-e1c6bef68597f5c4f418107ef9e3638e44cc8cf7.tar.gz |
Add --config-file-jail to avoid loading user knife.rb in tests
-rw-r--r-- | lib/chef/application.rb | 51 | ||||
-rw-r--r-- | lib/chef/application/client.rb | 5 | ||||
-rw-r--r-- | lib/chef/chef_fs/config.rb | 10 | ||||
-rw-r--r-- | lib/chef/chef_fs/path_utils.rb | 5 | ||||
-rw-r--r-- | lib/chef/config.rb | 9 | ||||
-rw-r--r-- | lib/chef/knife.rb | 12 | ||||
-rw-r--r-- | spec/integration/client/client_spec.rb | 47 | ||||
-rw-r--r-- | spec/unit/application_spec.rb | 5 | ||||
-rw-r--r-- | spec/unit/knife/config_file_selection_spec.rb | 17 |
9 files changed, 113 insertions, 48 deletions
diff --git a/lib/chef/application.rb b/lib/chef/application.rb index ca9cfff291..2987f58839 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -25,6 +25,8 @@ require 'chef/platform' require 'mixlib/cli' require 'tmpdir' require 'rbconfig' +require 'pathname' +require 'chef/chef_fs/path_utils' class Chef::Application include Mixlib::CLI @@ -75,7 +77,21 @@ class Chef::Application # Parse the config file def load_config_file if !config[:config_file] - Chef::Log.warn("No config file found or specified on command line, not loading.") + Chef::Log.warn("No config file found or specified on command line, using command line options.") + Chef::Config.merge!(config) + return + end + + if !Chef::Application.config_file_exists?(config[:config_file]) + Chef::Log.warn("*****************************************") + if !File.exists?(config[:config_file]) + Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.") + else + Chef::Log.warn("Config file #{config[:config_file]} not inside the jail #{Chef::Config.config_file_jail}, using command line options.", 2) + end + Chef::Log.warn("*****************************************") + + Chef::Config.merge!(config) return end @@ -86,12 +102,6 @@ class Chef::Application else ::File::open(config[:config_file]) { |f| apply_config(f.path) } end - rescue Errno::ENOENT => error - Chef::Log.warn("*****************************************") - Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.") - Chef::Log.warn("*****************************************") - - Chef::Config.merge!(config) rescue SocketError => error Chef::Application.fatal!("Error getting config file #{config[:config_file]}", 2) rescue Chef::Exceptions::ConfigurationError => error @@ -101,6 +111,33 @@ class Chef::Application end end + # Determines whether a config file exists, taking into account file existence + # as well as Chef::Config.config_file_jail (if they are not under the jail, + # this method will return false). Takes symlinks and relative paths into + # account. + def self.config_file_exists?(config_file) + if config_file =~ /^(http|https):\/\// + return true + end + + begin + real_config_file = Pathname.new(config_file).realpath.to_s + rescue Errno::ENOENT + return false + end + + return true if !Chef::Config.config_file_jail + + begin + jail = Pathname.new(Chef::Config.config_file_jail).realpath.to_s + rescue Errno::ENOENT + Chef::Log.warn("Config file jail #{Chef::Config.config_file_jail} does not exist: will not load any config file.") + return false + end + + Chef::ChefFS::PathUtils.descendant_of?(real_config_file, jail) + end + # Initialize and configure the logger. # === Loggers and Formatters # In Chef 10.x and previous, the Logger was the primary/only way that Chef diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb index ea8d599b4c..0964101fad 100644 --- a/lib/chef/application/client.rb +++ b/lib/chef/application/client.rb @@ -206,6 +206,10 @@ class Chef::Application::Client < Chef::Application :long => "--chef-zero-port PORT", :description => "Port to start chef-zero on" + option :config_file_jail, + :long => "--config-file-jail PATH", + :description => "Directory under which config files are allowed to be loaded (no client.rb or knife.rb outside this path will be loaded)." + if Chef::Platform.windows? option :fatal_windows_admin_check, :short => "-A", @@ -272,6 +276,7 @@ class Chef::Application::Client < Chef::Application end def load_config_file + Chef::Config.config_file_jail = config[:config_file_jail] if config[:config_file_jail] if !config.has_key?(:config_file) if config[:local_mode] require 'chef/knife' diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb index bfbe379775..e08b976961 100644 --- a/lib/chef/chef_fs/config.rb +++ b/lib/chef/chef_fs/config.rb @@ -84,16 +84,14 @@ class Chef # If the path does not reach into ANY specified directory, nil is returned. def server_path(file_path) pwd = File.expand_path(Dir.pwd) - absolute_path = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd)) + absolute_pwd = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd)) # Check all object paths (cookbooks_dir, data_bags_dir, etc.) object_paths.each_pair do |name, paths| paths.each do |path| realest_path = Chef::ChefFS::PathUtils.realest_path(path) - if absolute_path[0,realest_path.length] == realest_path && - (absolute_path.length == realest_path.length || - absolute_path[realest_path.length,1] =~ /#{PathUtils.regexp_path_separator}/) - relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_path, realest_path) + if PathUtils.descendant_of?(absolute_pwd, realest_path) + relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_pwd, realest_path) return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}" end end @@ -102,7 +100,7 @@ class Chef # Check chef_repo_path Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path| realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path) - if absolute_path == realest_chef_repo_path + if absolute_pwd == realest_chef_repo_path return '/' end end diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb index 805b092b3a..9ef75ce2e5 100644 --- a/lib/chef/chef_fs/path_utils.rb +++ b/lib/chef/chef_fs/path_utils.rb @@ -82,6 +82,11 @@ class Chef end end + def self.descendant_of?(path, ancestor) + path[0,ancestor.length] == ancestor && + (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/) + end + def self.is_absolute?(path) path =~ /^#{regexp_path_separator}/ end diff --git a/lib/chef/config.rb b/lib/chef/config.rb index 0a4ee6e94c..f2610f01e2 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -73,6 +73,13 @@ class Chef formatters << [name, file_path] end + # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.) + configurable(:config_file) + + # No config file (client.rb / knife.rb / etc.) will be loaded outside this path. + # Major use case is tests, where we don't want to load the user's config files. + configurable(:config_file_jail) + default :formatters, [] # Override the config dispatch to set the value of multiple server options simultaneously @@ -215,6 +222,8 @@ class Chef # An array of paths to search for knife exec scripts if they aren't in the current directory default :script_path, [] + # The root of all caches (checksums, cache and backup). If local mode is on, + # this is under the user's home directory. default(:cache_path) do if local_mode "#{user_home}#{platform_path_separator}.chef#{platform_path_separator}local-mode-cache" diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 5ff6b92643..dbb719c30a 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -335,8 +335,7 @@ class Chef end candidate_configs.each do | candidate_config | - candidate_config = File.expand_path(candidate_config) - if File.exist?(candidate_config) + if Chef::Application.config_file_exists?(candidate_config) return candidate_config end end @@ -403,12 +402,17 @@ class Chef end def configure_chef - unless config[:config_file] + if config[:config_file] + if !Chef::Application.config_file_exists?(config[:config_file]) + ui.error("Specified config file #{config[:config_file]} does not exist#{Chef::Config.config_file_jail ? " or is not under config file jail #{Chef::Config.config_file_jail}" : ""}!") + exit 1 + end + else located_config_file = self.class.locate_config_file config[:config_file] = located_config_file if located_config_file end - # Don't try to load a knife.rb if it doesn't exist. + # Don't try to load a knife.rb if it wasn't specified. if config[:config_file] Chef::Log.debug("Using configuration from #{config[:config_file]}") read_config_file(config[:config_file]) diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb index a91c5966a1..839899d059 100644 --- a/spec/integration/client/client_spec.rb +++ b/spec/integration/client/client_spec.rb @@ -19,29 +19,40 @@ EOM result.error! end - it 'should complete with success when cwd is just above cookbooks and paths are not specified' do - chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin")) - result = shell_out("#{chef_dir}/chef-client -z -o 'x::default'", :cwd => path_to('')) - result.error! - end + context 'and no config file' do + it 'should complete with success when cwd is just above cookbooks and paths are not specified' do + chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin")) + result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('')}\"", :cwd => path_to('')) + result.error! + end - it 'should complete with success when cwd is below cookbooks and paths are not specified' do - chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin")) - result = shell_out("#{chef_dir}/chef-client -z -o 'x::default'", :cwd => path_to('cookbooks/x')) - result.error! - end + it 'should complete with success when cwd is below cookbooks and paths are not specified' do + chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin")) + result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('')}\"", :cwd => path_to('cookbooks/x')) + result.error! + end - it 'should fail when cwd is below high above and paths are not specified' do - chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin")) - result = shell_out("#{chef_dir}/chef-client -z -o 'x::default'", :cwd => File.expand_path('..', path_to(''))) - result.exitstatus.should == 1 + it 'should fail when cwd is below high above and paths are not specified' do + chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin")) + result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('')}\"", :cwd => File.expand_path('..', path_to(''))) + result.exitstatus.should == 1 + end end - it 'should load .chef/knife.rb when -z is specified' do + context 'and a config file under .chef/knife.rb' do file '.chef/knife.rb', 'xxx.xxx' - chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin")) - result = shell_out("#{chef_dir}/chef-client -z -o 'x::default'", :cwd => path_to('')) - result.exitstatus.should == 2 + + it 'should load .chef/knife.rb when -z is specified' do + chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin")) + result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('')}\"", :cwd => path_to('')) + result.exitstatus.should == 2 + end + + it 'fails to load .chef/knife.rb when -z is specified and --config-file-jail does not include the .chef/knife.rb' do + chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin")) + result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('roles')}\"", :cwd => path_to('')) + result.error! + end end it "should complete with success" do diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index 9d473bbefb..e27a5cddb8 100644 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -108,12 +108,14 @@ describe Chef::Application do end it "should configure chef::config from a file" do + Chef::Application.should_receive(:config_file_exists?).with('/etc/chef/default.rb').and_return(true) File.should_receive(:open).with("/etc/chef/default.rb").and_yield(@config_file) Chef::Config.should_receive(:from_file).with(@config_file.path) @app.configure_chef end it "should merge the local config hash into chef::config" do + Chef::Application.should_receive(:config_file_exists?).with('/etc/chef/default.rb').and_return(true) File.should_receive(:open).with("/etc/chef/default.rb").and_yield(@config_file) @app.configure_chef Chef::Config.rspec_ran.should == "true" @@ -128,7 +130,7 @@ describe Chef::Application do it "should emit a warning" do Chef::Config.should_not_receive(:from_file).with("/etc/chef/default.rb") - Chef::Log.should_receive(:warn).with("No config file found or specified on command line, not loading.") + Chef::Log.should_receive(:warn).with("No config file found or specified on command line, using command line options.") @app.configure_chef end end @@ -164,6 +166,7 @@ describe Chef::Application do after {@config_file.unlink} it "should configure chef::config from an URL" do + Chef::Application.should_receive(:config_file_exists?).with('http://example.com/foo.rb').and_call_original Chef::REST.should_receive(:new).with("", nil, nil).at_least(1).times.and_return(@rest) @rest.should_receive(:fetch).with("http://example.com/foo.rb").and_yield(@config_file) @app.configure_chef diff --git a/spec/unit/knife/config_file_selection_spec.rb b/spec/unit/knife/config_file_selection_spec.rb index 33aaddbf48..0aa77392f3 100644 --- a/spec/unit/knife/config_file_selection_spec.rb +++ b/spec/unit/knife/config_file_selection_spec.rb @@ -38,6 +38,7 @@ describe Chef::Knife do env_config = File.expand_path(File.join(Dir.tmpdir, 'knife.rb')) File.stub!(:exist?).and_return(false) File.stub!(:exist?).with(env_config).and_return(true) + Chef::Application.stub(:config_file_exists?) { |arg| arg == env_config } ENV['KNIFE_HOME'] = Dir.tmpdir @knife = Chef::Knife.new @@ -47,9 +48,7 @@ describe Chef::Knife do it "configure knife from PWD" do pwd_config = "#{Dir.pwd}/knife.rb" - File.stub!(:exist?).and_return do | arg | - [ pwd_config ].include? arg - end + Chef::Application.stub(:config_file_exists?) { |arg| arg == pwd_config } @knife = Chef::Knife.new @knife.configure_chef @@ -59,9 +58,7 @@ describe Chef::Knife do it "configure knife from UPWARD" do upward_dir = File.expand_path "#{Dir.pwd}/.chef" upward_config = File.expand_path "#{upward_dir}/knife.rb" - File.stub!(:exist?).and_return do | arg | - [ upward_config ].include? arg - end + Chef::Application.stub(:config_file_exists?) { |arg| arg == upward_config } Chef::Knife.stub!(:chef_config_dir).and_return(upward_dir) @knife = Chef::Knife.new @@ -71,9 +68,7 @@ describe Chef::Knife do it "configure knife from HOME" do home_config = File.expand_path(File.join("#{ENV['HOME']}", "/.chef/knife.rb")) - File.stub!(:exist?).and_return do | arg | - [ home_config ].include? arg - end + Chef::Application.stub(:config_file_exists?) { |arg| arg == home_config } @knife = Chef::Knife.new @knife.configure_chef @@ -95,9 +90,7 @@ describe Chef::Knife do upward_config = File.expand_path "#{upward_dir}/knife.rb" home_config = File.expand_path(File.join("#{ENV['HOME']}", "/.chef/knife.rb")) configs = [ env_config, pwd_config, upward_config, home_config ] - File.stub!(:exist?).and_return do | arg | - configs.include? arg - end + Chef::Application.stub(:config_file_exists?) { |arg| configs.include?(arg) } Chef::Knife.stub!(:chef_config_dir).and_return(upward_dir) ENV['KNIFE_HOME'] = Dir.tmpdir |