Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .kitchen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ suites:
- name: config
run_list:
- recipe[ssh_test::config]
- name: authorized_keys
run_list:
- recipe[ssh_test::authorized_keys]
10 changes: 9 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
AllCops:
Exclude:
- 'bin/**/*'
- '.kitchen/**/*'

AlignParameters:
Enabled: false
ClassAndModuleChildren:
Expand All @@ -16,5 +21,8 @@ MethodLength:
Severity: refactor
SingleSpaceBeforeFirstArg:
Enabled: false
AbcSize:
Enabled: false
Documentation:
Enabled: false
Enabled: false

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# CHANGELOG for ssh
## 0.10.6
* add authorized_keys resource

## 0.10.5
* add support for RHEL family

Expand Down
98 changes: 97 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Description

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

## Setup

Expand Down Expand Up @@ -171,3 +171,99 @@ some of your cookbooks may not be as generous.
options 'User' => 'git', 'IdentityFile' => '/var/apps/github_deploy_key'
user 'webapp'
end

### authorized_keys
The authorized_keys LWRP is considered _Beta_ due to the lack of tests for this resource. Use at your own risk,
and feel free to submit a PR for adding more tests.

Also of important note, typically when SSH keys are generated, the resulting file will have the type, key, and a comment.
The typical comment is just the `username@host`. This is __NOT__ part of the key. When setting your attributes,
please be sure to set only the key in the `key` field. See the example if you are still uncertain.

#### Actions

<table>
<thead>
<tr>
<th>Action</th><th>Description</th><th>Default</th>
</tr>
</thead>
<tbody>
<tr>
<td>add</td>
<td>Adds an entry to the given user's authorized_keys file</td>
<td>Yes</td>
</tr>
<tr>
<td>remove</td>
<td>Removes an entry from the given user's authorized_keys file</td>
<td>&nbsp;</td>
<\tr>
<tr>
<td>modify</td>
<td>Updates an existing entry to the user's authorized_keys file, but only if the indicated `key` is present</td>
<td>&nbsp;</td>
<\tr>
</tbody>
</table>

__* please note that there is no `name` attribute for this resource. The name you assign is not used in the provider__

#### Attributes

<table>
<thead>
<tr>
<th>Attribute</th><th>Description</th><th>Default Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>type</td>
<td>
A string representing the type of key. Options include `id-rsa, ssh-dss, ssh-ed25519` and others
</td>
<td><code>id-rsa</code></td>
</tr>
<tr>
<td>options</td>
<td>
A hash containing the key-value pairs for options. Binary options such as `no-port-forwarding` should have a value of `nil`
</td>
<td><code>{}</code></td>
</tr>
<tr>
<td>user</td>
<td>
The user for which this key should be added
</td>
<td>none - __REQUIRED__</td>
</tr>
<tr>
<td>comment</td>
<td>
a comment to add to this entry (generally the `useranme@host` is added as a comment, but this is not required)
</td>
<td><code>''</code></td>
</tr>
<tr>
<td>key</td>
<td>
the actual key
</td>
<td>none - __REQUIRED__</td>
</tr>
</tbody>
</table>

#### Example

<syntaxhightlight lang="ruby">
ssh_authorized_key "for remote access" do
options { 'cert-authority' => nil, :command => '/usr/bin/startup' }
user 'admin'
key 'AAAAB3NzaC1yc2EAAAADAQABAAACAQDzB76TOkrDRaevO3I1qzosRXliAuYdjcMejHwwL5v2hRqTrBePlMW6nqz8/JgLTzHn/KxzkrKLb0GlpPDrJ1KByWGYZsfydUfv7n1+5ogoA7UW7dUc4DoQtGPuy4Xe0enr88VfALlT11aWKAw8K/I39zWiPvJNX3Mks0f3/3smjLaQEnDWWWiawp5YgzJmyzsqZFZrrFCUgv7AP1EjZofWUcRvYEEjMhKsK+G2H2VCN7MpH0cJ97E0bKNQjHBrwGyMLQZUOndGakCuOuTLpikOXSpUUz5LwqCiRIj6iUtWevwk+AYLZwxPYQpCxFceVFDhPDaJQ85vweSq+HEg7hRujq9jO7vM9LIgjqg7fwQ2Ql6zO9NjXv2UalzBi0H2AbKT1V/PpNufPgolyb/dK7Jqpqu7Ytggctl2fGyLe8yVaC9gD+/BBeCl82LZI142kdXmf4WYcZgOgcRgGJrbSZjeMzX6zZpiD1AG3T7xyEn2twmC/TqptmQEAG2BBzGum+S6pU0rnOt2UJngRnviK2vptAWtRlSlsopySOXv+VbqUXhRjHRT/+2nq5Q4BWcjsZaaoo1uWh2glATRnGK995A1zJ3gWrBA+IaC6stKzjSG0KPwLjzHfPKbWjDX76D/qdo0qBN5hBiHDRfmiNqpNYS9NHACDZNVPBS5N1d5BUkyKw=='
type 'id-rsa'
comment 'gdidy@coolman.com'
end
</syntaxhighlight>
4 changes: 2 additions & 2 deletions libraries/ssh_config_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def default?(path)
def parse_file(path)
entries = {}
return entries unless ::File.exist?(path)
name = nil?
name = nil
IO.foreach(path) do |line|
next if line.match(/^\s*(#|\r?\n|\s*$)/) # skip lines with only comments or whitespace

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

Chef::Log.error("Line |#{line}| does not parse correctly")
return nil, nil
nil
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
license 'Apache 2.0'
description 'LWRPs for managing SSH known_hosts and config files'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '0.10.5'
version '0.10.6'

supports 'ubuntu'
supports 'rhel'
157 changes: 157 additions & 0 deletions providers/authorized_keys.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
include Chef::SSH::Helpers

use_inline_resources

def whyrun_supported?
true
end

action :add do
validate_options(new_resource.options, "Resource #{new_resource.name}") if new_resource.options
validate_type(new_resource.type, "Resource #{new_resource.name}")
if @current_resource.exists?
action_update
else
@lines << { :options => new_resource.options,
:type => new_resource.type,
:key => new_resource.key,
:comment => new_resource.comment }
update_file
end
end

action :update do
return unless @current_resource.exists?
current = @lines.find { |line| line[:key] == new_resource.key }
current[:options] = new_resource.options
current[:type] = new_resource.type
current[:comment] = new_resource.comment
update_file
end

action :remove do
return unless @current_resource.exists?
@lines.reject! { |line| line[:key] == new_resource.key }
update_file
end

def update_file
directory ::File.dirname(@path) do
action :create
owner new_resource.user
mode 00700
end

file @path do
action :create
mode 00600
owner new_resource.user
content format_lines
end
end

def format_lines
@lines.collect do |line|
joined = ''
if line[:options]
joined << line[:options].collect do |key, value|
if value.nil? || value.empty?
key.to_s
elsif value.include?(' ') && !value.include?('"')
"#{key}=\"#{value}\""
else
"#{key}=#{value}"
end
end.join(',')
joined << ' '
end
joined << line[:type] << ' ' << line[:key]
line[:comment] && (joined << ' ' << line[:comment])
joined
end.join("\n") + "\n"
end

def initialize(new_resource, run_context)
super(new_resource, run_context)

@path = ::File.join(user_dir(new_resource.user), '.ssh', 'authorized_keys')

load_current_resource
end

def load_current_resource
@lines = ::File.exist?(@path) ? parse(::IO.readlines(@path)) : []

current_line = @lines.find { |line| line[:key] == @new_resource.key }
@current_resource = Chef::Resource::SshKnownHosts.new(@new_resource.name)
@current_resource.exists = current_line
end

protected

def parse(current)
current.reduce([]) do |memo, row|
line = {}
fields = extract_fields(row)
line[:options] = parse_options(fields.shift) unless types.include? fields[0]
validate_type(fields[0], @path)
line[:type] = fields[0]
line[:key] = fields[1]
line[:comment] = fields[2..-1].join(' ') if row[2]
memo << line
end
end

def extract_fields(row)
return :comment => row if row.empty? || row[0] == '#'

quotes = 0
fields = []
row.scan(/\S+/) do |match|
if quotes.even? || quotes == 0
fields << match
else
fields[-1] << " #{match}"
end
quotes += match.count('"')
end
fields
end

def parse_options(text)
options = {}
split = text.split(',')
split.each do |group|
validate_options(group, @path)
group = group.split('=')
options[group[0]] = group[1]
end
options
end

def types
%w(id-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 ssh-dss)
end

def validate_type(type, source)
valid = types
fail "Invalid Type #{type} in #{source}" unless valid.include? type.to_s
end

def validate_options(option, source)
if option.is_a? Hash
option.each { |o| validate_options o, source }
return
elsif option.is_a? String
option = option.split('=')
end

binary_options = %w(cert-authority no-agent-forwarding no-port-forwarding no-pty no-user-rc no-X11-forwarding)
other_options = %w(command environment from permitopen principals tunnel)

if option[1].nil? || option[1].empty?
fail "Invalid Option in #{source}: #{option}" unless binary_options.include? option[0].to_s
else
fail "Invalid Option in #{source}: #{option}" unless other_options.include? option[0].to_s
end
end
11 changes: 5 additions & 6 deletions providers/known_hosts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@ def whyrun_supported?
end

action :remove do
if @current_resource.exists? # ~FC023
execute "remove known_host entry for #{new_resource.host}" do
command "ssh-keygen -R #{Shellwords.escape(new_resource.host)} -f #{new_resource.path}"
user new_resource.user if new_resource.user
umask new_resource.user ? 0077 : 0022
end
return unless @current_resource.exists?
execute "remove known_host entry for #{new_resource.host}" do
command "ssh-keygen -R #{Shellwords.escape(new_resource.host)} -f #{new_resource.path}"
user new_resource.user if new_resource.user
umask new_resource.user ? 0077 : 0022
end
end

Expand Down
2 changes: 1 addition & 1 deletion recipes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
# Recipe: default.rb

node['ssh']['packages'].each do |package_name|
package package_name
package package_name
end
21 changes: 21 additions & 0 deletions resources/authorized_keys.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
actions :add, :remove, :update
default_action :add

attribute :type,
:kind_of => String,
:default => 'id-rsa',
:equal_to => [
'id-rsa',
'ecdsa-sha2-nistp256',
'ecdsa-sha2-nistp384',
'ecdsa-sha2-nistp521',
'ssh-ed25519',
'ssh-dss'
]
attribute :options, :kind_of => Hash, :default => {}
attribute :key, :kind_of => String, :required => true
attribute :comment, :kind_of => String
attribute :user, :kind_of => String, :required => true

attr_accessor :exists
alias_method :exists?, :exists
Loading