1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
#
# 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
resource_name :ssh_known_hosts_entry
provides(:ssh_known_hosts_entry) { true }
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 "15.0"
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 ssh-keyscan 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 "ssh_known_hosts.erb"
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
|