summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2018-06-11 12:40:17 -0700
committerGitHub <noreply@github.com>2018-06-11 12:40:17 -0700
commita729bcede36227960ee6460c47ac90b8243d3a7d (patch)
tree5bb11f6a16239d56a2d8a89d7a3d5dfa54cb929a
parentc5c28f95005f07334488f3dac9f509592b7ab271 (diff)
parentb28ed8677ab78ed5aae2181666b7a948c1361b8b (diff)
downloadchef-a729bcede36227960ee6460c47ac90b8243d3a7d.tar.gz
Merge pull request #7161 from chef/ssh_known_hosts
Add ssh_known_hosts_entry resource from ssh_known_hosts cookbook
-rw-r--r--kitchen-tests/cookbooks/base/recipes/default.rb3
-rw-r--r--lib/chef/resource/ssh_known_hosts_entry.rb146
-rw-r--r--lib/chef/resource/support/ssh_known_hosts.erb3
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--spec/unit/resource/ssh_known_hosts_entry_spec.rb55
5 files changed, 208 insertions, 0 deletions
diff --git a/kitchen-tests/cookbooks/base/recipes/default.rb b/kitchen-tests/cookbooks/base/recipes/default.rb
index ea0215ca96..9175bfbd26 100644
--- a/kitchen-tests/cookbooks/base/recipes/default.rb
+++ b/kitchen-tests/cookbooks/base/recipes/default.rb
@@ -38,6 +38,9 @@ users_manage "sysadmin" do
action [:create]
end
+ssh_known_hosts_entry "github.com"
+ssh_known_hosts_entry "travis.org"
+
sudo "sysadmins" do
group ["sysadmin", "%superadmin"]
nopasswd true
diff --git a/lib/chef/resource/ssh_known_hosts_entry.rb b/lib/chef/resource/ssh_known_hosts_entry.rb
new file mode 100644
index 0000000000..07e35587b7
--- /dev/null
+++ b/lib/chef/resource/ssh_known_hosts_entry.rb
@@ -0,0 +1,146 @@
+#
+# Author:: Seth Vargo (<sethvargo@gmail.com>)
+#
+# Copyright:: 2013-2018, Seth Vargo
+# Copyright:: 2017-2018, Chef Software, 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/resource"
+
+class Chef
+ class Resource
+ class SshKnownHostsEntry < Chef::Resource
+ preview_resource true
+ resource_name :ssh_known_hosts_entry
+
+ description "Use the ssh_known_hosts_entry resource to append an entry for the specified host in /etc/ssh/ssh_known_hosts or a user's known hosts file if specified."
+ introduced "14.3"
+
+ property :host, String,
+ description: "The host to add to the known hosts file.",
+ name_property: true
+
+ property :key, String,
+ description: "An optional key for the host. If not provided this will be automatically determined."
+
+ property :key_type, String,
+ description: "The type of key to store.",
+ default: "rsa"
+
+ property :port, Integer,
+ description: "The server port that the ssh-keyscan command will use to gather the public key.",
+ default: 22
+
+ property :timeout, Integer,
+ description: "The timeout in seconds for ssh-keyscan.",
+ default: 30
+
+ property :mode, String,
+ description: "The file mode for the ssh_known_hosts file.",
+ default: "0644"
+
+ property :owner, String,
+ description: "The file owner for the ssh_known_hosts file.",
+ default: "root"
+
+ property :group, String,
+ description: "The file group for the ssh_known_hosts file.",
+ default: lazy { node["root_group"] }
+
+ property :hash_entries, [TrueClass, FalseClass],
+ description: "Hash the hostname and addresses in the ssh_known_hosts file for privacy.",
+ default: false
+
+ property :file_location, String,
+ description: "The location of the ssh known hosts file. Change this to set a known host file for a particular user.",
+ default: "/etc/ssh/ssh_known_hosts"
+
+ action :create do
+ description "Create an entry in the ssh_known_hosts file."
+
+ key =
+ if new_resource.key
+ hoststr = (new_resource.port != 22) ? "[#{new_resource.host}]:#{new_resource.port}" : new_resource.host
+ "#{hoststr} #{type_string(new_resource.key_type)} #{new_resource.key}"
+ else
+ keyscan_cmd = ["ssh-keyscan", "-t#{new_resource.key_type}", "-p #{new_resource.port}"]
+ keyscan_cmd << "-H" if new_resource.hash_entries
+ keyscan_cmd << new_resource.host
+ keyscan = shell_out!(keyscan_cmd.join(" "), timeout: new_resource.timeout)
+ keyscan.stdout
+ end
+
+ key.sub!(/^#{new_resource.host}/, "[#{new_resource.host}]:#{new_resource.port}") if new_resource.port != 22
+
+ comment = key.split("\n").first || ""
+
+ r = with_run_context :root do
+ find_resource(:template, "update ssh known hosts file #{new_resource.file_location}") do
+ source ::File.expand_path("../support/ssh_known_hosts.erb", __FILE__)
+ local true
+ path new_resource.file_location
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode
+ action :nothing
+ delayed_action :create
+ backup false
+ variables(entries: [])
+ end
+ end
+
+ keys = r.variables[:entries].reject(&:empty?)
+
+ if key_exists?(keys, key, comment)
+ Chef::Log.debug "Known hosts key for #{new_resource.name} already exists - skipping"
+ else
+ r.variables[:entries].push(key)
+ end
+ end
+
+ # all this does is send an immediate run_action(:create) to the template resource
+ action :flush do
+ description "Immediately flush the entries to the config file. Without this the actual writing of the file is delayed in the Chef run so all entries can be accumulated before writing the file out."
+
+ with_run_context :root do
+ # if you haven't ever called ssh_known_hosts_entry before you're definitely doing it wrong so we blow up hard.
+ find_resource!(:template, "update ssh known hosts file #{new_resource.file_location}").run_action(:create)
+ # it is the user's responsibility to only call this *after* all the ssh_known_hosts_entry resources have been called.
+ # if you call this too early in your run_list you will get a partial known_host file written to disk, and the resource
+ # behavior will not be idempotent (template resources will flap and never show 0 resources updated on converged boxes).
+ Chef::Log.warn "flushed ssh_known_hosts entries to file, later ssh_known_hosts_entry resources will not have been written"
+ end
+ end
+
+ action_class do
+ def key_exists?(keys, key, comment)
+ keys.any? do |line|
+ line.match(/#{Regexp.escape(comment)}|#{Regexp.escape(key)}/)
+ end
+ end
+
+ def type_string(key_type)
+ type_map = {
+ "rsa" => "ssh-rsa",
+ "dsa" => "ssh-dss",
+ "ecdsa" => "ecdsa-sha2-nistp256",
+ "ed25519" => "ssh-ed25519",
+ }
+ type_map[key_type] || key_type
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/support/ssh_known_hosts.erb b/lib/chef/resource/support/ssh_known_hosts.erb
new file mode 100644
index 0000000000..0073b250ff
--- /dev/null
+++ b/lib/chef/resource/support/ssh_known_hosts.erb
@@ -0,0 +1,3 @@
+<% @entries.sort.each do |entry| -%>
+<%= entry %>
+<% end -%>
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index c232ce04e1..c001e56055 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -92,6 +92,7 @@ require "chef/resource/sudo"
require "chef/resource/sysctl"
require "chef/resource/swap_file"
require "chef/resource/systemd_unit"
+require "chef/resource/ssh_known_hosts_entry"
require "chef/resource/windows_service"
require "chef/resource/subversion"
require "chef/resource/smartos_package"
diff --git a/spec/unit/resource/ssh_known_hosts_entry_spec.rb b/spec/unit/resource/ssh_known_hosts_entry_spec.rb
new file mode 100644
index 0000000000..3bae37dfe7
--- /dev/null
+++ b/spec/unit/resource/ssh_known_hosts_entry_spec.rb
@@ -0,0 +1,55 @@
+#
+# Copyright:: Copyright 2018, Chef Software, 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"
+
+describe Chef::Resource::SshKnownHostsEntry do
+ let(:node) { Chef::Node.new }
+ let(:run_context) do
+ node.automatic[:root_group] = "superduper"
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ Chef::RunContext.new(node, {}, empty_events)
+ end
+ let(:resource) { Chef::Resource::SshKnownHostsEntry.new("example.com", run_context) }
+
+ it "is not a preview resource in Chef 15" do
+ pending("Chef 15") unless Chef::VERSION.start_with?("15")
+ expect(resource.class.preview_resource).to be_falsey
+ end
+
+ it "sets resource name as :ssh_known_hosts_entry" do
+ expect(resource.resource_name).to eql(:ssh_known_hosts_entry)
+ end
+
+ it "sets group property to node['root_group'] by default" do
+ expect(resource.group).to eql("superduper")
+ end
+
+ it "sets the default action as :create" do
+ expect(resource.action).to eql([:create])
+ end
+
+ it "sets the host property as its name property" do
+ expect(resource.host).to eql("example.com")
+ end
+
+ it "supports :create and :flush actions" do
+ expect { resource.action :create }.not_to raise_error
+ expect { resource.action :flush }.not_to raise_error
+ expect { resource.action :delete }.to raise_error(ArgumentError)
+ end
+end