Skip to content

Commit 8b22bfe

Browse files
committed
Merge pull request #5 from markolson/master
Add authorized_keys
2 parents 1702485 + abb512d commit 8b22bfe

File tree

13 files changed

+337
-12
lines changed

13 files changed

+337
-12
lines changed

.kitchen.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ suites:
2929
- name: config
3030
run_list:
3131
- recipe[ssh_test::config]
32+
- name: authorized_keys
33+
run_list:
34+
- recipe[ssh_test::authorized_keys]

.rubocop.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
AllCops:
2+
Exclude:
3+
- 'bin/**/*'
4+
- '.kitchen/**/*'
5+
16
AlignParameters:
27
Enabled: false
38
ClassAndModuleChildren:
@@ -16,5 +21,8 @@ MethodLength:
1621
Severity: refactor
1722
SingleSpaceBeforeFirstArg:
1823
Enabled: false
24+
AbcSize:
25+
Enabled: false
1926
Documentation:
20-
Enabled: false
27+
Enabled: false
28+

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# CHANGELOG for ssh
2+
## 0.10.6
3+
* add authorized_keys resource
4+
25
## 0.10.5
36
* add support for RHEL family
47

README.md

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Description
44

5-
Provides 2 LWRPs to manage system-wide and per-user `ssh_config` and `known_host` files.
5+
Provides 3 LWRPs to manage system-wide and per-user `ssh_config` and `known_host` files.
66

77
## Setup
88

@@ -171,3 +171,99 @@ some of your cookbooks may not be as generous.
171171
options 'User' => 'git', 'IdentityFile' => '/var/apps/github_deploy_key'
172172
user 'webapp'
173173
end
174+
175+
### authorized_keys
176+
The authorized_keys LWRP is considered _Beta_ due to the lack of tests for this resource. Use at your own risk,
177+
and feel free to submit a PR for adding more tests.
178+
179+
Also of important note, typically when SSH keys are generated, the resulting file will have the type, key, and a comment.
180+
The typical comment is just the `username@host`. This is __NOT__ part of the key. When setting your attributes,
181+
please be sure to set only the key in the `key` field. See the example if you are still uncertain.
182+
183+
#### Actions
184+
185+
<table>
186+
<thead>
187+
<tr>
188+
<th>Action</th><th>Description</th><th>Default</th>
189+
</tr>
190+
</thead>
191+
<tbody>
192+
<tr>
193+
<td>add</td>
194+
<td>Adds an entry to the given user's authorized_keys file</td>
195+
<td>Yes</td>
196+
</tr>
197+
<tr>
198+
<td>remove</td>
199+
<td>Removes an entry from the given user's authorized_keys file</td>
200+
<td>&nbsp;</td>
201+
<\tr>
202+
<tr>
203+
<td>modify</td>
204+
<td>Updates an existing entry to the user's authorized_keys file, but only if the indicated `key` is present</td>
205+
<td>&nbsp;</td>
206+
<\tr>
207+
</tbody>
208+
</table>
209+
210+
__* please note that there is no `name` attribute for this resource. The name you assign is not used in the provider__
211+
212+
#### Attributes
213+
214+
<table>
215+
<thead>
216+
<tr>
217+
<th>Attribute</th><th>Description</th><th>Default Value</th>
218+
</tr>
219+
</thead>
220+
<tbody>
221+
<tr>
222+
<td>type</td>
223+
<td>
224+
A string representing the type of key. Options include `id-rsa, ssh-dss, ssh-ed25519` and others
225+
</td>
226+
<td><code>id-rsa</code></td>
227+
</tr>
228+
<tr>
229+
<td>options</td>
230+
<td>
231+
A hash containing the key-value pairs for options. Binary options such as `no-port-forwarding` should have a value of `nil`
232+
</td>
233+
<td><code>{}</code></td>
234+
</tr>
235+
<tr>
236+
<td>user</td>
237+
<td>
238+
The user for which this key should be added
239+
</td>
240+
<td>none - __REQUIRED__</td>
241+
</tr>
242+
<tr>
243+
<td>comment</td>
244+
<td>
245+
a comment to add to this entry (generally the `useranme@host` is added as a comment, but this is not required)
246+
</td>
247+
<td><code>''</code></td>
248+
</tr>
249+
<tr>
250+
<td>key</td>
251+
<td>
252+
the actual key
253+
</td>
254+
<td>none - __REQUIRED__</td>
255+
</tr>
256+
</tbody>
257+
</table>
258+
259+
#### Example
260+
261+
<syntaxhightlight lang="ruby">
262+
ssh_authorized_key "for remote access" do
263+
options { 'cert-authority' => nil, :command => '/usr/bin/startup' }
264+
user 'admin'
265+
key 'AAAAB3NzaC1yc2EAAAADAQABAAACAQDzB76TOkrDRaevO3I1qzosRXliAuYdjcMejHwwL5v2hRqTrBePlMW6nqz8/JgLTzHn/KxzkrKLb0GlpPDrJ1KByWGYZsfydUfv7n1+5ogoA7UW7dUc4DoQtGPuy4Xe0enr88VfALlT11aWKAw8K/I39zWiPvJNX3Mks0f3/3smjLaQEnDWWWiawp5YgzJmyzsqZFZrrFCUgv7AP1EjZofWUcRvYEEjMhKsK+G2H2VCN7MpH0cJ97E0bKNQjHBrwGyMLQZUOndGakCuOuTLpikOXSpUUz5LwqCiRIj6iUtWevwk+AYLZwxPYQpCxFceVFDhPDaJQ85vweSq+HEg7hRujq9jO7vM9LIgjqg7fwQ2Ql6zO9NjXv2UalzBi0H2AbKT1V/PpNufPgolyb/dK7Jqpqu7Ytggctl2fGyLe8yVaC9gD+/BBeCl82LZI142kdXmf4WYcZgOgcRgGJrbSZjeMzX6zZpiD1AG3T7xyEn2twmC/TqptmQEAG2BBzGum+S6pU0rnOt2UJngRnviK2vptAWtRlSlsopySOXv+VbqUXhRjHRT/+2nq5Q4BWcjsZaaoo1uWh2glATRnGK995A1zJ3gWrBA+IaC6stKzjSG0KPwLjzHfPKbWjDX76D/qdo0qBN5hBiHDRfmiNqpNYS9NHACDZNVPBS5N1d5BUkyKw=='
266+
type 'id-rsa'
267+
268+
end
269+
</syntaxhighlight>

libraries/ssh_config_helpers.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def default?(path)
1515
def parse_file(path)
1616
entries = {}
1717
return entries unless ::File.exist?(path)
18-
name = nil?
18+
name = nil
1919
IO.foreach(path) do |line|
2020
next if line.match(/^\s*(#|\r?\n|\s*$)/) # skip lines with only comments or whitespace
2121

@@ -50,7 +50,7 @@ def parse_line(line)
5050
return matchdata.captures[0], matchdata.captures[1].strip if matchdata
5151

5252
Chef::Log.error("Line |#{line}| does not parse correctly")
53-
return nil, nil
53+
nil
5454
end
5555
end
5656
end

metadata.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
license 'Apache 2.0'
55
description 'LWRPs for managing SSH known_hosts and config files'
66
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
7-
version '0.10.5'
7+
version '0.10.6'
88

99
supports 'ubuntu'
1010
supports 'rhel'

providers/authorized_keys.rb

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
include Chef::SSH::Helpers
2+
3+
use_inline_resources
4+
5+
def whyrun_supported?
6+
true
7+
end
8+
9+
action :add do
10+
validate_options(new_resource.options, "Resource #{new_resource.name}") if new_resource.options
11+
validate_type(new_resource.type, "Resource #{new_resource.name}")
12+
if @current_resource.exists?
13+
action_update
14+
else
15+
@lines << { :options => new_resource.options,
16+
:type => new_resource.type,
17+
:key => new_resource.key,
18+
:comment => new_resource.comment }
19+
update_file
20+
end
21+
end
22+
23+
action :update do
24+
return unless @current_resource.exists?
25+
current = @lines.find { |line| line[:key] == new_resource.key }
26+
current[:options] = new_resource.options
27+
current[:type] = new_resource.type
28+
current[:comment] = new_resource.comment
29+
update_file
30+
end
31+
32+
action :remove do
33+
return unless @current_resource.exists?
34+
@lines.reject! { |line| line[:key] == new_resource.key }
35+
update_file
36+
end
37+
38+
def update_file
39+
directory ::File.dirname(@path) do
40+
action :create
41+
owner new_resource.user
42+
mode 00700
43+
end
44+
45+
file @path do
46+
action :create
47+
mode 00600
48+
owner new_resource.user
49+
content format_lines
50+
end
51+
end
52+
53+
def format_lines
54+
@lines.collect do |line|
55+
joined = ''
56+
if line[:options]
57+
joined << line[:options].collect do |key, value|
58+
if value.nil? || value.empty?
59+
key.to_s
60+
elsif value.include?(' ') && !value.include?('"')
61+
"#{key}=\"#{value}\""
62+
else
63+
"#{key}=#{value}"
64+
end
65+
end.join(',')
66+
joined << ' '
67+
end
68+
joined << line[:type] << ' ' << line[:key]
69+
line[:comment] && (joined << ' ' << line[:comment])
70+
joined
71+
end.join("\n") + "\n"
72+
end
73+
74+
def initialize(new_resource, run_context)
75+
super(new_resource, run_context)
76+
77+
@path = ::File.join(user_dir(new_resource.user), '.ssh', 'authorized_keys')
78+
79+
load_current_resource
80+
end
81+
82+
def load_current_resource
83+
@lines = ::File.exist?(@path) ? parse(::IO.readlines(@path)) : []
84+
85+
current_line = @lines.find { |line| line[:key] == @new_resource.key }
86+
@current_resource = Chef::Resource::SshKnownHosts.new(@new_resource.name)
87+
@current_resource.exists = current_line
88+
end
89+
90+
protected
91+
92+
def parse(current)
93+
current.reduce([]) do |memo, row|
94+
line = {}
95+
fields = extract_fields(row)
96+
line[:options] = parse_options(fields.shift) unless types.include? fields[0]
97+
validate_type(fields[0], @path)
98+
line[:type] = fields[0]
99+
line[:key] = fields[1]
100+
line[:comment] = fields[2..-1].join(' ') if row[2]
101+
memo << line
102+
end
103+
end
104+
105+
def extract_fields(row)
106+
return :comment => row if row.empty? || row[0] == '#'
107+
108+
quotes = 0
109+
fields = []
110+
row.scan(/\S+/) do |match|
111+
if quotes.even? || quotes == 0
112+
fields << match
113+
else
114+
fields[-1] << " #{match}"
115+
end
116+
quotes += match.count('"')
117+
end
118+
fields
119+
end
120+
121+
def parse_options(text)
122+
options = {}
123+
split = text.split(',')
124+
split.each do |group|
125+
validate_options(group, @path)
126+
group = group.split('=')
127+
options[group[0]] = group[1]
128+
end
129+
options
130+
end
131+
132+
def types
133+
%w(id-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 ssh-dss)
134+
end
135+
136+
def validate_type(type, source)
137+
valid = types
138+
fail "Invalid Type #{type} in #{source}" unless valid.include? type.to_s
139+
end
140+
141+
def validate_options(option, source)
142+
if option.is_a? Hash
143+
option.each { |o| validate_options o, source }
144+
return
145+
elsif option.is_a? String
146+
option = option.split('=')
147+
end
148+
149+
binary_options = %w(cert-authority no-agent-forwarding no-port-forwarding no-pty no-user-rc no-X11-forwarding)
150+
other_options = %w(command environment from permitopen principals tunnel)
151+
152+
if option[1].nil? || option[1].empty?
153+
fail "Invalid Option in #{source}: #{option}" unless binary_options.include? option[0].to_s
154+
else
155+
fail "Invalid Option in #{source}: #{option}" unless other_options.include? option[0].to_s
156+
end
157+
end

providers/known_hosts.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,11 @@ def whyrun_supported?
3434
end
3535

3636
action :remove do
37-
if @current_resource.exists? # ~FC023
38-
execute "remove known_host entry for #{new_resource.host}" do
39-
command "ssh-keygen -R #{Shellwords.escape(new_resource.host)} -f #{new_resource.path}"
40-
user new_resource.user if new_resource.user
41-
umask new_resource.user ? 0077 : 0022
42-
end
37+
return unless @current_resource.exists?
38+
execute "remove known_host entry for #{new_resource.host}" do
39+
command "ssh-keygen -R #{Shellwords.escape(new_resource.host)} -f #{new_resource.path}"
40+
user new_resource.user if new_resource.user
41+
umask new_resource.user ? 0077 : 0022
4342
end
4443
end
4544

recipes/default.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
# Recipe: default.rb
44

55
node['ssh']['packages'].each do |package_name|
6-
package package_name
6+
package package_name
77
end

resources/authorized_keys.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
actions :add, :remove, :update
2+
default_action :add
3+
4+
attribute :type,
5+
:kind_of => String,
6+
:default => 'id-rsa',
7+
:equal_to => [
8+
'id-rsa',
9+
'ecdsa-sha2-nistp256',
10+
'ecdsa-sha2-nistp384',
11+
'ecdsa-sha2-nistp521',
12+
'ssh-ed25519',
13+
'ssh-dss'
14+
]
15+
attribute :options, :kind_of => Hash, :default => {}
16+
attribute :key, :kind_of => String, :required => true
17+
attribute :comment, :kind_of => String
18+
attribute :user, :kind_of => String, :required => true
19+
20+
attr_accessor :exists
21+
alias_method :exists?, :exists

0 commit comments

Comments
 (0)