aboutsummaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorJacob Helwig <jacob@technosorcery.net>2018-06-04 11:30:09 -0700
committerJacob Helwig <jacob@technosorcery.net>2018-06-21 14:27:04 -0700
commitd1719de1d77b9c139b1b5f5832330807c0fe11fe (patch)
tree69c541233bc64c5d47746e062e0efcba0c5436b5 /spec
downloadpuppet-sshkeys_core-d1719de1d77b9c139b1b5f5832330807c0fe11fe.tar.gz
puppet-sshkeys_core-d1719de1d77b9c139b1b5f5832330807c0fe11fe.tar.bz2
Initial sshkey type import from Puppet repository
Imported from dbf5a8964af9b87446542d24f46534cf90f11f59 in the Puppet repo.
Diffstat (limited to 'spec')
-rw-r--r--spec/acceptance/tests/resource/ssh_authorized_key/create.rb39
-rw-r--r--spec/acceptance/tests/resource/ssh_authorized_key/destroy.rb42
-rw-r--r--spec/acceptance/tests/resource/ssh_authorized_key/modify.rb43
-rw-r--r--spec/acceptance/tests/resource/ssh_authorized_key/query.rb35
-rw-r--r--spec/acceptance/tests/resource/sshkey/create.rb77
-rw-r--r--spec/default_facts.yml8
-rw-r--r--spec/fixtures/integration/provider/sshkey/sample21
-rw-r--r--spec/fixtures/unit/provider/sshkey/parsed/sample21
-rw-r--r--spec/fixtures/unit/provider/sshkey/parsed/sample_with_blank_lines8
-rw-r--r--spec/integration/provider/ssh_authorized_key_spec.rb219
-rw-r--r--spec/integration/provider/sshkey_spec.rb159
-rw-r--r--spec/lib/puppet_spec/compiler.rb112
-rw-r--r--spec/lib/puppet_spec/files.rb126
-rw-r--r--spec/spec_helper.rb45
-rw-r--r--spec/spec_helper_local.rb17
-rw-r--r--spec/unit/provider/sshkey/parsed_spec.rb93
-rw-r--r--spec/unit/type/ssh_authorized_key_spec.rb258
-rw-r--r--spec/unit/type/sshkey_spec.rb77
18 files changed, 1400 insertions, 0 deletions
diff --git a/spec/acceptance/tests/resource/ssh_authorized_key/create.rb b/spec/acceptance/tests/resource/ssh_authorized_key/create.rb
new file mode 100644
index 0000000..6b4c879
--- /dev/null
+++ b/spec/acceptance/tests/resource/ssh_authorized_key/create.rb
@@ -0,0 +1,39 @@
+test_name "should create an entry for an SSH authorized key"
+
+tag 'audit:medium',
+ 'audit:refactor', # Use block style `test_run`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+confine :except, :platform => ['windows']
+
+auth_keys = '~/.ssh/authorized_keys'
+name = "pl#{rand(999999).to_i}"
+
+agents.each do |agent|
+ teardown do
+ #(teardown) restore the #{auth_keys} file
+ on(agent, "mv /tmp/auth_keys #{auth_keys}", :acceptable_exit_codes => [0,1])
+ end
+
+ #------- SETUP -------#
+ step "(setup) backup #{auth_keys} file"
+ on(agent, "cp #{auth_keys} /tmp/auth_keys", :acceptable_exit_codes => [0,1])
+ on(agent, "chown $LOGNAME #{auth_keys}")
+
+ #------- TESTS -------#
+ step "create an authorized key entry with puppet (present)"
+ args = ['ensure=present',
+ "user=$LOGNAME",
+ "type='rsa'",
+ "key='mykey'",
+ ]
+ on(agent, puppet_resource('ssh_authorized_key', "#{name}", args))
+
+ step "verify entry in #{auth_keys}"
+ on(agent, "cat #{auth_keys}") do |res|
+ fail_test "didn't find the ssh_authorized_key for #{name}" unless stdout.include? "#{name}"
+ end
+
+end
diff --git a/spec/acceptance/tests/resource/ssh_authorized_key/destroy.rb b/spec/acceptance/tests/resource/ssh_authorized_key/destroy.rb
new file mode 100644
index 0000000..c80e967
--- /dev/null
+++ b/spec/acceptance/tests/resource/ssh_authorized_key/destroy.rb
@@ -0,0 +1,42 @@
+test_name "should delete an entry for an SSH authorized key"
+
+tag 'audit:medium',
+ 'audit:refactor', # Use block style `test_run`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+confine :except, :platform => ['windows']
+
+auth_keys = '~/.ssh/authorized_keys'
+name = "pl#{rand(999999).to_i}"
+
+agents.each do |agent|
+ teardown do
+ #(teardown) restore the #{auth_keys} file
+ on(agent, "mv /tmp/auth_keys #{auth_keys}", :acceptable_exit_codes => [0,1])
+ end
+
+ #------- SETUP -------#
+ step "(setup) backup #{auth_keys} file"
+ on(agent, "cp #{auth_keys} /tmp/auth_keys", :acceptable_exit_codes => [0,1])
+
+ step "(setup) create an authorized key in the #{auth_keys} file"
+ on(agent, "echo '' >> #{auth_keys} && echo 'ssh-rsa mykey #{name}' >> #{auth_keys}")
+ on(agent, "chown $LOGNAME #{auth_keys}")
+
+ #------- TESTS -------#
+ step "delete an authorized key entry with puppet (absent)"
+ args = ['ensure=absent',
+ "user=$LOGNAME",
+ "type='rsa'",
+ "key='mykey'",
+ ]
+ on(agent, puppet_resource('ssh_authorized_key', "#{name}", args))
+
+ step "verify entry deleted from #{auth_keys}"
+ on(agent, "cat #{auth_keys}") do |res|
+ fail_test "found the ssh_authorized_key for #{name}" if stdout.include? "#{name}"
+ end
+
+end
diff --git a/spec/acceptance/tests/resource/ssh_authorized_key/modify.rb b/spec/acceptance/tests/resource/ssh_authorized_key/modify.rb
new file mode 100644
index 0000000..0a50c31
--- /dev/null
+++ b/spec/acceptance/tests/resource/ssh_authorized_key/modify.rb
@@ -0,0 +1,43 @@
+test_name "should update an entry for an SSH authorized key"
+
+tag 'audit:medium',
+ 'audit:refactor', # Use block style `test_run`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+confine :except, :platform => ['windows']
+
+auth_keys = '~/.ssh/authorized_keys'
+name = "pl#{rand(999999).to_i}"
+
+agents.each do |agent|
+ teardown do
+ #(teardown) restore the #{auth_keys} file
+ on(agent, "mv /tmp/auth_keys #{auth_keys}", :acceptable_exit_codes => [0,1])
+ end
+
+ #------- SETUP -------#
+ step "(setup) backup #{auth_keys} file"
+ on(agent, "cp #{auth_keys} /tmp/auth_keys", :acceptable_exit_codes => [0,1])
+
+ step "(setup) create an authorized key in the #{auth_keys} file"
+ on(agent, "echo '' >> #{auth_keys} && echo 'ssh-rsa mykey #{name}' >> #{auth_keys}")
+ on(agent, "chown $LOGNAME #{auth_keys}")
+
+ #------- TESTS -------#
+ step "update an authorized key entry with puppet (present)"
+ args = ['ensure=present',
+ "user=$LOGNAME",
+ "type='rsa'",
+ "key='mynewshinykey'",
+ ]
+ on(agent, puppet_resource('ssh_authorized_key', "#{name}", args))
+
+ step "verify entry updated in #{auth_keys}"
+ on(agent, "cat #{auth_keys}") do |res|
+ fail_test "didn't find the updated key for #{name}" unless stdout.include? "mynewshinykey #{name}"
+ fail_test "Found old key mykey #{name}" if stdout.include? "mykey #{name}"
+ end
+
+end
diff --git a/spec/acceptance/tests/resource/ssh_authorized_key/query.rb b/spec/acceptance/tests/resource/ssh_authorized_key/query.rb
new file mode 100644
index 0000000..8caff85
--- /dev/null
+++ b/spec/acceptance/tests/resource/ssh_authorized_key/query.rb
@@ -0,0 +1,35 @@
+test_name "should be able to find an existing SSH authorized key"
+
+tag 'audit:medium',
+ 'audit:refactor', # Use block style `test_run`
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+skip_test("This test is blocked by PUP-1605")
+
+confine :except, :platform => ['windows']
+
+auth_keys = '~/.ssh/authorized_keys'
+name = "pl#{rand(999999).to_i}"
+
+agents.each do |agent|
+ teardown do
+ #(teardown) restore the #{auth_keys} file
+ on(agent, "mv /tmp/auth_keys #{auth_keys}", :acceptable_exit_codes => [0,1])
+ end
+
+ #------- SETUP -------#
+ step "(setup) backup #{auth_keys} file"
+ on(agent, "cp #{auth_keys} /tmp/auth_keys", :acceptable_exit_codes => [0,1])
+
+ step "(setup) create an authorized key in the #{auth_keys} file"
+ on(agent, "echo '' >> #{auth_keys} && echo 'ssh-rsa mykey #{name}' >> #{auth_keys}")
+
+ #------- TESTS -------#
+ step "verify SSH authorized key query with puppet"
+ on(agent, puppet_resource('ssh_authorized_key', "/#{name}")) do |res|
+ fail_test "Didn't find the ssh_authorized_key for #{name}" unless stdout.include? "#{name}"
+ end
+
+end
diff --git a/spec/acceptance/tests/resource/sshkey/create.rb b/spec/acceptance/tests/resource/sshkey/create.rb
new file mode 100644
index 0000000..4e75379
--- /dev/null
+++ b/spec/acceptance/tests/resource/sshkey/create.rb
@@ -0,0 +1,77 @@
+test_name "(PUP-5508) Should add an SSH key to the correct ssh_known_hosts file on OS X/macOS" do
+# TestRail test case C93370
+
+tag 'audit:medium',
+ 'audit:acceptance' # Could be done at the integration (or unit) layer though
+ # actual changing of resources could irreparably damage a
+ # host running this, or require special permissions.
+
+confine :to, :platform => /osx/
+
+keyname = "pl#{rand(999999).to_i}"
+
+# FIXME: This is bletcherous
+macos_version = fact_on(agent, "os.macosx.version.major")
+if ["10.9","10.10"].include? macos_version
+ ssh_known_hosts = '/etc/ssh_known_hosts'
+else
+ ssh_known_hosts = '/etc/ssh/ssh_known_hosts'
+end
+
+teardown do
+ puts "Restore the #{ssh_known_hosts} file"
+ agents.each do |agent|
+ # Is it present?
+ rc = on(agent, "[ -e /tmp/ssh_known_hosts ]",
+ :accept_all_exit_codes => true)
+ if rc.exit_code == 0
+ # It's present, so restore the original
+ on(agent, "mv -fv /tmp/ssh_known_hosts #{ssh_known_hosts}",
+ :accept_all_exit_codes => true)
+ else
+ # It's missing, which means there wasn't one to backup; just
+ # delete the one we laid down
+ on(agent, "rm -fv #{ssh_known_hosts}",
+ :accept_all_exit_codes => true)
+ end
+ end
+end
+
+#------- SETUP -------#
+step "Backup #{ssh_known_hosts} file, if present" do
+ # The 'cp' might fail because the source file doesn't exist
+ on(agent, "cp -fv #{ssh_known_hosts} /tmp/ssh_known_hosts",
+ :acceptable_exit_codes => [0,1])
+end
+
+#------- TESTS -------#
+step 'Verify that the default file is empty or non-existent' do
+ # Is it even there?
+ rc = on(agent, "[ ! -e #{ssh_known_hosts} ]",
+ :acceptable_exit_codes => [0, 1])
+ if rc.exit_code == 1
+ # If it's there, it should be empty
+ on(agent, "cat #{ssh_known_hosts}") do |res|
+ fail_test "Default #{ssh_known_hosts} file not empty" \
+ unless stdout.empty?
+ end
+ end
+end
+
+step "Add an sshkey to the default file" do
+ args = [
+ "ensure=present",
+ "key=how_about_the_key_of_c",
+ "type=ssh-rsa",
+ ]
+ on(agent, puppet_resource("sshkey", "#{keyname}", args))
+end
+
+step 'Verify the new entry in the default file' do
+ on(agent, "cat #{ssh_known_hosts}") do |rc|
+ fail_test "Didn't find the ssh_known_host entry for #{keyname}" \
+ unless stdout.include? "#{keyname}"
+ end
+end
+
+end
diff --git a/spec/default_facts.yml b/spec/default_facts.yml
new file mode 100644
index 0000000..3248be5
--- /dev/null
+++ b/spec/default_facts.yml
@@ -0,0 +1,8 @@
+# Use default_module_facts.yml for module specific facts.
+#
+# Facts specified here will override the values provided by rspec-puppet-facts.
+---
+concat_basedir: "/tmp"
+ipaddress: "172.16.254.254"
+is_pe: false
+macaddress: "AA:AA:AA:AA:AA:AA"
diff --git a/spec/fixtures/integration/provider/sshkey/sample b/spec/fixtures/integration/provider/sshkey/sample
new file mode 100644
index 0000000..840ed19
--- /dev/null
+++ b/spec/fixtures/integration/provider/sshkey/sample
@@ -0,0 +1,21 @@
+hosting2.planetargon.com,64.34.164.77 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAy+f2t52cDMrYkgEKQ6juqfMf/a0nDFry3JAzl+SAWQ0gTklVxNcVbfHx2pkZk66EBGQfrK33Bx1BflZ/iEDyiCwmzVtNba0X9A6ELYjB9WSkWdIqZCfPlKZMu9N//aZ6+3SDVuz/BVFsAVmtqQ4Let2QjOFiSIKXrtPqWvVT/MM=
+kirby.madstop.com,192.168.0.5 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0=
+fedora1,192.168.0.51 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAyz1rKcApU4//j8CHYKexq4qnq2WVqLPrZYGnlij1t7FscLiDVKvBuRHVkfyTNIjAM/t7tM1Dj+FuD4iWziCmf7RO9q4wI5y/1zgCiSUegnZVSmH2yxnWGMdHGpXOkN3NXcpy6jylxyBo0M7T22PSezCxyUVfMclesjOEO1jETd0=
+kirby ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0=
+sol10b,192.168.0.50 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAs37kiDwKxWqi6EfSdKwRaZXBwh2doOARRqZzyitBaPwESy26DwTx+xdQ2rwB4V2k1WIec+1f3bgTS2ArH75dQSPyba2HKqxaSRBd3Zh4z23+uUxpupEyoRdW1HolMOvuoceheSMsruiuYcuiyct41d4c/Qmr51Dv04Doi00k6Ws=
+piratehaven.org,64.81.59.88 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA7TYRRkle0wDNohZ0TNZN6R1Zp0svxgX+GJ9umI5yNM1bMxUTgeNRh5nIvZg1HgD1WRXQ57dSxxLzbvRyAqc245g6S8eWWCtenvOFLl0rOF5D3VxbQuw79sOe8/Ac8TC+c8RuWB7aaxpwL5Rv9xfDeazOtoKXj7+uwQW1PUmTaEM=
+atalanta,192.168.0.10 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAysniuWiJH6OQLXl63XXcS1b/hP2lAgSz0IutjQ6ZUfBrt1BZ8udEgSh57w5QDLsZ1lNvND61u5cy6iDKXI5TIQY4DvUmsoFZhyr4iYJbtT/h6UJSyaZtEnA7ZMRjGhUIMOn+mjbj7Z3zmJMhxtImK3Xo3O2fJ1hnK4jsBwQPSLc=
+pixie,192.168.0.9 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAzDp588CvET6w11LB2s/vPjc4tX9+u46iYJgNFfhzxrXYMVv4GF7d30IXB5+Hwyi2FOQIG1+h0kUXVGWEv64rAFBT7pD2KMFX0lcDERV4avqT0TRDIIA5OqFOhq9Ff+kOmHS2cB7eFyR5vqbN4ujOnJGTmru9dcqyL+2AcFekvh0=
+culain,192.168.0.3 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAvJ/IORhQMdAsJ7LB1sI2LlWUHc7HPTCDiEgJ96ij3jFvqaIiYieRFaNkxbbk75mPkj3vIqWIgAsAtHmKX4wDikNG/gyjs4WM4cWFKtl2jiVhqpoxqqCaVxs6Ex+vpKuKhQR6SzFBFDlBZYP9an6DPu1msTLT8/hZH2WwswVmpyU=
+cs312-server.een.orst.edu,128.193.40.66 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA+t3hsOiyp1mztt013bLdJqIAFCo8lxlz86MYEPz/mADHzWLs3Xv7xpAUv/E8pRbhEOzXo84EddORRBXz6DgVyMah8+v/u3IOkpXuZI0Iu1n5hZyf2l5DGEyGecr3oqqjUdMuM9HeXFLnqXJI3hDE7civBtqf5AJSol+TCcipeE8=
+freebsd1,192.168.0.52 ssh-dss AAAAB3NzaC1kc3MAAACBAJSiOyQhYlKAi0FDLKy42VzLDq6yJWXGXVCLSfgWyVx7QCq/3+W3C1dtHuAvjbypcjqqvsuGGITgQ1Y6B/+76n5d7FyQnj4SFZ5drOBn/TvslXhrS/Ok5KCcndfNAa+EyMnSZJ21jhoRjZftY4lmb4hy6fEF3RvjuOdf1qBN5FWpAAAAFQDcsWF0zELAW6YUlSjAyO0T0lfPbwAAAIAlOLdOd/WszzVaplCOnH5vF6LWfr6BosZKDkFi0mv6Ec636YGaj4AMxK8sRPusHv6sVByN17ntIJnLo2XD1SuoH28bZ0ZnPIdVnd0l1KqsOCuuow9LZYJUihezoUuYuTvij1jZdrwltuPNJTVLYtsZDnKE5plw+Tjzeb7ImjbXGwAAAIBT40olg3fxhRDiZECle0WK7GitgXCB3njs+4dba8VwveEJb9UuulMc1eR+zQiJR96IUBagC9NiLvUGS1IfiIHpT4FA8O/MK1W9SgxXB9d39Nk/9l8dH3U/fLnbC/hYVo8bN0or/mKxcxQMkdBwpPlWAbELRftod2BkkkvgfQdc+g==
+192.168.0.2 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgwCZ+qxpMMRJ3otGsjaYeKTKf6tuCZyK1cD+ns9Eu7V0ZJLJ/LLMxduu7n4H/ufGI5rGV5axzgx8yZhjDRzsrGjLAQYsqlomMkf901YQI6UuieSA4MZa5MDkq/Jt6Vx1kEGTpkgrfw9kRMX5BngECt1QKY4xTgC7Ex+WlFvZwk+tRUT3
+openbsd1,192.168.0.54 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvgRVrtJk0fBg9YsLf7rWR1X32ZjFcva5XBvenDlHruObaHzuGXyyr6iOCAEOc7eCZjlPBYrGZ2potqyk8HlBOHXr1cCBf49t4yAt8KgKswtzWlgdbU1UEwllSRVOpzqULAT0smv/jfaIZdvRoN1rLriqtpn4bQL//ZOHiyXCwdlJ+di8Mza2L8KZ5T75hwIFBhrgL12xfymRp3v+Iy21MjjzsF3pROIkZ7icitNqQF55U9vsHaA37vG8FepVkO10bYYP7IHPZaBPBMPx7qPyRgRDJahEUGBnkIkzwJHEmA+7YMiNTZu6MigezD+CIqY1xOi/eXZwObLwLKXo7eRmpw==
+192.168.0.60 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU=
+rh3a,192.168.0.55 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU=
+tsetse,192.168.0.7 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAwCDGn82BEMSCfcE2LZKcwdAoyIBC+u2twVwWNRm3KzyrJMQ+RbTQo4AwGOdyr/QYh6KaxTKTSoDtiBLr132uenMQKwF47gCKMA1T47uiy+TBUehgOGwOxteSYP/pQRpKXFmhOppSPyDPQVq234XvANeJp0iT8ZKEhF2FsWTs6sM=
+config.sage.org,131.106.3.205 ssh-dss AAAAB3NzaC1kc3MAAACBAL2akEcIfQsfm3zCd2hD6PgH3kWA/tqX/qbrLhL/ipX7iqK/y282GMClZQSQjc1YNi9virvLzb6u/gdZxicZ8K5O3FaJrULQJOZaP62SOHk5CUSHVnvpMCaCnbwB6gEHa2LeMWStcEfWW+g1CC2hzPJw16/S5GISGXbyanO02MnXAAAAFQDomwx/OCjTmmQljMTU5rgNn2E4gwAAAIBmtMSfcs2Tq5iFFKK5cahluv047vVNfXqYIAkeJniceL0Et16MKfuzadpq0H9ocxQYW/5Ir9nUULrdxBUN9LxNKq15/uWkJC9QCSh8PysgvFnjVZeCODua/dn6eZTZnY9DZ3S6v1pT8CP6uWr5fmZJ8FKJGrC3gYX4y1V1ZTCVewAAAIB6e7RCST6vkTS5rgn5wGbrfLK5ad+PW+2i66k77Zv1pjtfRz+0oejBjwJDPNVSc2YNEl7X0nEEMNjo/a5x8Ls+nVqhzJA+NXIwS1e/moKbXFGewW5HAtxd79gtInC8dEIO7hmnWnqF1dBkRHXg1YffYkHrMVJBxpzagw7nYa0BBQ==
+rh3b,192.168.0.56 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU=
+centos1,192.168.0.57 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA0DXqYF+3Lf68GkWBAjjKBb6UITNnzm4wiDi/AGjv5+DoVXqDcqHvZ8rZFAMgUe1dVob4pWT2ZWLHW0gicoJCdr4UQbPXlWz1F62z8fo2PRRPlG6KN1wmF7pnyml8jr0wBX8lQZJsMqi4InGozf7wFHLH/7DNGRK3MD6tSp3Z4is=
+doorstop.cafes.net,205.241.238.186 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApJKeB9/bN5t55zLETHs0MVo/vEkfQ3EzY7178GKLI/yiOFmcn+NvUvUtCQK/xKpod813LBHCODxZPG1Kb0SjlaC/EkFEenb74LNu0o1qXa1GWh3wfaIm0JRNjXqPqxAWTlMs43O2HXwOwmLVhl7SSP3xtTw6h9gREbVKmfBaRdsRfzD0etfz1NCnmGh/1Sh9+j4eeS+IBtwoR5JVhZVhuofHCqs5HZ8gLDgfn8HXP7pMbLkx54cf1R/tmFmn9JGLdTPtEGcSIiu7414XSbfChSC83rGZCDPKHq7ZodiE8GpbWLBnyPXi2AYxTPM7aZMqitIHv3MWf5suV0q0WLGdnQ==
+host.domain.com,host1.domain.com,192.168.0.1 dss thisismykey1
diff --git a/spec/fixtures/unit/provider/sshkey/parsed/sample b/spec/fixtures/unit/provider/sshkey/parsed/sample
new file mode 100644
index 0000000..840ed19
--- /dev/null
+++ b/spec/fixtures/unit/provider/sshkey/parsed/sample
@@ -0,0 +1,21 @@
+hosting2.planetargon.com,64.34.164.77 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAy+f2t52cDMrYkgEKQ6juqfMf/a0nDFry3JAzl+SAWQ0gTklVxNcVbfHx2pkZk66EBGQfrK33Bx1BflZ/iEDyiCwmzVtNba0X9A6ELYjB9WSkWdIqZCfPlKZMu9N//aZ6+3SDVuz/BVFsAVmtqQ4Let2QjOFiSIKXrtPqWvVT/MM=
+kirby.madstop.com,192.168.0.5 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0=
+fedora1,192.168.0.51 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAyz1rKcApU4//j8CHYKexq4qnq2WVqLPrZYGnlij1t7FscLiDVKvBuRHVkfyTNIjAM/t7tM1Dj+FuD4iWziCmf7RO9q4wI5y/1zgCiSUegnZVSmH2yxnWGMdHGpXOkN3NXcpy6jylxyBo0M7T22PSezCxyUVfMclesjOEO1jETd0=
+kirby ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0=
+sol10b,192.168.0.50 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAs37kiDwKxWqi6EfSdKwRaZXBwh2doOARRqZzyitBaPwESy26DwTx+xdQ2rwB4V2k1WIec+1f3bgTS2ArH75dQSPyba2HKqxaSRBd3Zh4z23+uUxpupEyoRdW1HolMOvuoceheSMsruiuYcuiyct41d4c/Qmr51Dv04Doi00k6Ws=
+piratehaven.org,64.81.59.88 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA7TYRRkle0wDNohZ0TNZN6R1Zp0svxgX+GJ9umI5yNM1bMxUTgeNRh5nIvZg1HgD1WRXQ57dSxxLzbvRyAqc245g6S8eWWCtenvOFLl0rOF5D3VxbQuw79sOe8/Ac8TC+c8RuWB7aaxpwL5Rv9xfDeazOtoKXj7+uwQW1PUmTaEM=
+atalanta,192.168.0.10 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAysniuWiJH6OQLXl63XXcS1b/hP2lAgSz0IutjQ6ZUfBrt1BZ8udEgSh57w5QDLsZ1lNvND61u5cy6iDKXI5TIQY4DvUmsoFZhyr4iYJbtT/h6UJSyaZtEnA7ZMRjGhUIMOn+mjbj7Z3zmJMhxtImK3Xo3O2fJ1hnK4jsBwQPSLc=
+pixie,192.168.0.9 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAzDp588CvET6w11LB2s/vPjc4tX9+u46iYJgNFfhzxrXYMVv4GF7d30IXB5+Hwyi2FOQIG1+h0kUXVGWEv64rAFBT7pD2KMFX0lcDERV4avqT0TRDIIA5OqFOhq9Ff+kOmHS2cB7eFyR5vqbN4ujOnJGTmru9dcqyL+2AcFekvh0=
+culain,192.168.0.3 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAvJ/IORhQMdAsJ7LB1sI2LlWUHc7HPTCDiEgJ96ij3jFvqaIiYieRFaNkxbbk75mPkj3vIqWIgAsAtHmKX4wDikNG/gyjs4WM4cWFKtl2jiVhqpoxqqCaVxs6Ex+vpKuKhQR6SzFBFDlBZYP9an6DPu1msTLT8/hZH2WwswVmpyU=
+cs312-server.een.orst.edu,128.193.40.66 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA+t3hsOiyp1mztt013bLdJqIAFCo8lxlz86MYEPz/mADHzWLs3Xv7xpAUv/E8pRbhEOzXo84EddORRBXz6DgVyMah8+v/u3IOkpXuZI0Iu1n5hZyf2l5DGEyGecr3oqqjUdMuM9HeXFLnqXJI3hDE7civBtqf5AJSol+TCcipeE8=
+freebsd1,192.168.0.52 ssh-dss AAAAB3NzaC1kc3MAAACBAJSiOyQhYlKAi0FDLKy42VzLDq6yJWXGXVCLSfgWyVx7QCq/3+W3C1dtHuAvjbypcjqqvsuGGITgQ1Y6B/+76n5d7FyQnj4SFZ5drOBn/TvslXhrS/Ok5KCcndfNAa+EyMnSZJ21jhoRjZftY4lmb4hy6fEF3RvjuOdf1qBN5FWpAAAAFQDcsWF0zELAW6YUlSjAyO0T0lfPbwAAAIAlOLdOd/WszzVaplCOnH5vF6LWfr6BosZKDkFi0mv6Ec636YGaj4AMxK8sRPusHv6sVByN17ntIJnLo2XD1SuoH28bZ0ZnPIdVnd0l1KqsOCuuow9LZYJUihezoUuYuTvij1jZdrwltuPNJTVLYtsZDnKE5plw+Tjzeb7ImjbXGwAAAIBT40olg3fxhRDiZECle0WK7GitgXCB3njs+4dba8VwveEJb9UuulMc1eR+zQiJR96IUBagC9NiLvUGS1IfiIHpT4FA8O/MK1W9SgxXB9d39Nk/9l8dH3U/fLnbC/hYVo8bN0or/mKxcxQMkdBwpPlWAbELRftod2BkkkvgfQdc+g==
+192.168.0.2 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgwCZ+qxpMMRJ3otGsjaYeKTKf6tuCZyK1cD+ns9Eu7V0ZJLJ/LLMxduu7n4H/ufGI5rGV5axzgx8yZhjDRzsrGjLAQYsqlomMkf901YQI6UuieSA4MZa5MDkq/Jt6Vx1kEGTpkgrfw9kRMX5BngECt1QKY4xTgC7Ex+WlFvZwk+tRUT3
+openbsd1,192.168.0.54 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvgRVrtJk0fBg9YsLf7rWR1X32ZjFcva5XBvenDlHruObaHzuGXyyr6iOCAEOc7eCZjlPBYrGZ2potqyk8HlBOHXr1cCBf49t4yAt8KgKswtzWlgdbU1UEwllSRVOpzqULAT0smv/jfaIZdvRoN1rLriqtpn4bQL//ZOHiyXCwdlJ+di8Mza2L8KZ5T75hwIFBhrgL12xfymRp3v+Iy21MjjzsF3pROIkZ7icitNqQF55U9vsHaA37vG8FepVkO10bYYP7IHPZaBPBMPx7qPyRgRDJahEUGBnkIkzwJHEmA+7YMiNTZu6MigezD+CIqY1xOi/eXZwObLwLKXo7eRmpw==
+192.168.0.60 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU=
+rh3a,192.168.0.55 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU=
+tsetse,192.168.0.7 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAwCDGn82BEMSCfcE2LZKcwdAoyIBC+u2twVwWNRm3KzyrJMQ+RbTQo4AwGOdyr/QYh6KaxTKTSoDtiBLr132uenMQKwF47gCKMA1T47uiy+TBUehgOGwOxteSYP/pQRpKXFmhOppSPyDPQVq234XvANeJp0iT8ZKEhF2FsWTs6sM=
+config.sage.org,131.106.3.205 ssh-dss AAAAB3NzaC1kc3MAAACBAL2akEcIfQsfm3zCd2hD6PgH3kWA/tqX/qbrLhL/ipX7iqK/y282GMClZQSQjc1YNi9virvLzb6u/gdZxicZ8K5O3FaJrULQJOZaP62SOHk5CUSHVnvpMCaCnbwB6gEHa2LeMWStcEfWW+g1CC2hzPJw16/S5GISGXbyanO02MnXAAAAFQDomwx/OCjTmmQljMTU5rgNn2E4gwAAAIBmtMSfcs2Tq5iFFKK5cahluv047vVNfXqYIAkeJniceL0Et16MKfuzadpq0H9ocxQYW/5Ir9nUULrdxBUN9LxNKq15/uWkJC9QCSh8PysgvFnjVZeCODua/dn6eZTZnY9DZ3S6v1pT8CP6uWr5fmZJ8FKJGrC3gYX4y1V1ZTCVewAAAIB6e7RCST6vkTS5rgn5wGbrfLK5ad+PW+2i66k77Zv1pjtfRz+0oejBjwJDPNVSc2YNEl7X0nEEMNjo/a5x8Ls+nVqhzJA+NXIwS1e/moKbXFGewW5HAtxd79gtInC8dEIO7hmnWnqF1dBkRHXg1YffYkHrMVJBxpzagw7nYa0BBQ==
+rh3b,192.168.0.56 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU=
+centos1,192.168.0.57 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA0DXqYF+3Lf68GkWBAjjKBb6UITNnzm4wiDi/AGjv5+DoVXqDcqHvZ8rZFAMgUe1dVob4pWT2ZWLHW0gicoJCdr4UQbPXlWz1F62z8fo2PRRPlG6KN1wmF7pnyml8jr0wBX8lQZJsMqi4InGozf7wFHLH/7DNGRK3MD6tSp3Z4is=
+doorstop.cafes.net,205.241.238.186 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApJKeB9/bN5t55zLETHs0MVo/vEkfQ3EzY7178GKLI/yiOFmcn+NvUvUtCQK/xKpod813LBHCODxZPG1Kb0SjlaC/EkFEenb74LNu0o1qXa1GWh3wfaIm0JRNjXqPqxAWTlMs43O2HXwOwmLVhl7SSP3xtTw6h9gREbVKmfBaRdsRfzD0etfz1NCnmGh/1Sh9+j4eeS+IBtwoR5JVhZVhuofHCqs5HZ8gLDgfn8HXP7pMbLkx54cf1R/tmFmn9JGLdTPtEGcSIiu7414XSbfChSC83rGZCDPKHq7ZodiE8GpbWLBnyPXi2AYxTPM7aZMqitIHv3MWf5suV0q0WLGdnQ==
+host.domain.com,host1.domain.com,192.168.0.1 dss thisismykey1
diff --git a/spec/fixtures/unit/provider/sshkey/parsed/sample_with_blank_lines b/spec/fixtures/unit/provider/sshkey/parsed/sample_with_blank_lines
new file mode 100644
index 0000000..02535df
--- /dev/null
+++ b/spec/fixtures/unit/provider/sshkey/parsed/sample_with_blank_lines
@@ -0,0 +1,8 @@
+# A comment
+
+# ... and another after a blank line. The following line includes three whitespace characters.
+
+hosting2.planetargon.com,64.34.164.77 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAy+f2t52cDMrYkgEKQ6juqfMf/a0nDFry3JAzl+SAWQ0gTklVxNcVbfHx2pkZk66EBGQfrK33Bx1BflZ/ iEDyiCwmzVtNba0X9A6ELYjB9WSkWdIqZCfPlKZMu9N//aZ6+3SDVuz/BVFsAVmtqQ4Let2QjOFiSIKXrtPqWvVT/MM=
+
+will.isawesome.com,192.168.0.5 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0=
+will.isgreat.com,192.168.0.6 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBasd=
diff --git a/spec/integration/provider/ssh_authorized_key_spec.rb b/spec/integration/provider/ssh_authorized_key_spec.rb
new file mode 100644
index 0000000..14af2de
--- /dev/null
+++ b/spec/integration/provider/ssh_authorized_key_spec.rb
@@ -0,0 +1,219 @@
+#! /usr/bin/env ruby
+
+require 'spec_helper'
+require 'puppet/file_bucket/dipper'
+
+describe Puppet::Type.type(:ssh_authorized_key).provider(:parsed), '(integration)', :unless => Puppet.features.microsoft_windows? do
+ include PuppetSpec::Files
+
+ let :fake_userfile do
+ tmpfile('authorized_keys.user')
+ end
+
+ let :fake_rootfile do
+ tmpfile('authorized_keys.root')
+ end
+
+ let :sample_rsa_keys do
+ [
+ 'AAAAB3NzaC1yc2EAAAADAQABAAAAgQCi18JBZOq10X3w4f67nVhO0O3s5Y1vHH4UgMSM3ZnQwbC5hjGyYSi9UULOoQQoQynI/a0I9NL423/Xk/XJVIKCHcS8q6V2Wmjd+fLNelOjxxoW6mbIytEt9rDvwgq3Mof3/m21L3t2byvegR00a+ikKbmInPmKwjeWZpexCIsHzQ==', # 1024 bit
+ 'AAAAB3NzaC1yc2EAAAADAQABAAAAgQDLClyvi3CsJw5Id6khZs2/+s11qOH4Gdp6iDioDsrIp0m8kSiPr71VGyQYAfPzzvHemHS7Xg0NkG1Kc8u9tRqBQfTvz7ubq0AT/g01+4P2hQ/soFkuwlUG/HVnnaYb6N0Qp5SHWvD5vBE2nFFQVpP5GrSctPtHSjzJq/i+6LYhmQ==', # 1024 bit
+ 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDLygAO6txXkh9FNV8xSsBkATeqLbHzS7sFjGI3gt0Dx6q3LjyKwbhQ1RLf28kd5G6VWiXmClU/RtiPdUz8nrGuun++2mrxzrXrvpR9dq1lygLQ2wn2cI35dN5bjRMtXy3decs6HUhFo9MoNwX250rUWfdCyNPhGIp6OOfmjdy+UeLGNxq9wDx6i4bT5tVVSqVRtsEfw9+ICXchzl85QudjneVVpP+thriPZXfXA5eaGwAo/dmoKOIhUwF96gpdLqzNtrGQuxPbV80PTbGv9ZtAtTictxaDz8muXO7he9pXmchUpxUKtMFjHkL0FAZ9tRPmv3RA30sEr2fZ8+LKvnE50w0' #2048 Bit
+ ]
+ end
+
+ let :sample_dsa_keys do
+ [
+ 'AAAAB3NzaC1kc3MAAACBAOPck2O8MIDSqxPSnvENt6tzRrKJ5oOhB6Nc6oEcWm+VEH1gvuxdiRqwoMgRwyEf1yUd+UAcLw3a6Jn+EtFyEBN/5WF+4Tt4xTxZ0Pfik2Wc5uqHbQ2dkmOoXiAOYPiD3JUQ1Xwm/J0CgetjitoLfzAGdCNhMqguqAuHcVJ78ZZbAAAAFQCIBKFYZ+I18I+dtgteirXh+VVEEwAAAIEAs1yvQ/wnLLrRCM660pF4kBiw3D6dJfMdCXWQpn0hZmkBQSIzZv4Wuk3giei5luxscDxNc+y3CTXtnyG4Kt1Yi2sOdvhRI3rX8tD+ejn8GHazM05l5VIo9uu4AQPIE32iV63IqgApSBbJ6vDJW91oDH0J492WdLCar4BS/KE3cRwAAACBAN0uSDyJqYLRsfYcFn4HyVf6TJxQm1IcwEt6GcJVzgjri9VtW7FqY5iBqa9B9Zdh5XXAYJ0XLsWQCcrmMHM2XGHGpA4gL9VlCJ/0QvOcXxD2uK7IXwAVUA7g4V4bw8EVnFv2Flufozhsp+4soo1xiYc5jiFVHwVlk21sMhAtKAeF' # 1024 Bit
+ ]
+ end
+
+ let :sample_lines do
+ [
+ "ssh-rsa #{sample_rsa_keys[1]} root@someotherhost",
+ "ssh-dss #{sample_dsa_keys[0]} root@anywhere",
+ "ssh-rsa #{sample_rsa_keys[2]} paul",
+ "ssh-rsa #{sample_rsa_keys[2]} dummy"
+ ]
+ end
+
+ let :dummy do
+ Puppet::Type.type(:ssh_authorized_key).new(
+ :name => 'dummy',
+ :target => fake_userfile,
+ :user => 'nobody',
+ :ensure => :absent
+ )
+ end
+
+ before :each do
+ File.stubs(:chown)
+ File.stubs(:chmod)
+ Puppet::Util::SUIDManager.stubs(:asuser).yields
+ end
+
+ after :each do
+ described_class.clear # Work around bug #6628
+ end
+
+ def create_fake_key(username, content)
+ filename = (username == :root ? fake_rootfile : fake_userfile )
+ File.open(filename, 'w') do |f|
+ content.each do |line|
+ f.puts line
+ end
+ end
+ end
+
+ def check_fake_key(username, expected_content)
+ filename = (username == :root ? fake_rootfile : fake_userfile )
+ content = File.readlines(filename).map(&:chomp).sort.reject{ |x| x =~ /^# HEADER:/ }
+ expect(content.join("\n")).to eq(expected_content.sort.join("\n"))
+ end
+
+ def run_in_catalog(*resources)
+ Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # Don't backup to the filebucket
+ catalog = Puppet::Resource::Catalog.new
+ catalog.host_config = false
+ resources.each do |resource|
+ resource.expects(:err).never
+ catalog.add_resource(resource)
+ end
+ catalog.apply
+ end
+
+ it "should not complain about empty lines and comments" do
+ described_class.expects(:flush).never
+ sample = ['',sample_lines[0],' ',sample_lines[1],'# just a comment','#and another']
+ create_fake_key(:user,sample)
+ run_in_catalog(dummy)
+ check_fake_key(:user, sample)
+ end
+
+ it "should keep empty lines and comments when modifying a file" do
+ create_fake_key(:user, ['',sample_lines[0],' ',sample_lines[3],'# just a comment','#and another'])
+ run_in_catalog(dummy)
+ check_fake_key(:user, ['',sample_lines[0],' ','# just a comment','#and another'])
+ end
+
+ describe "when managing one resource" do
+
+ describe "with ensure set to absent" do
+ let :resource do
+ Puppet::Type.type(:ssh_authorized_key).new(
+ :name => 'root@hostname',
+ :type => :rsa,
+ :key => sample_rsa_keys[0],
+ :target => fake_rootfile,
+ :user => 'root',
+ :ensure => :absent
+ )
+ end
+
+ it "should not modify root's keyfile if resource is currently not present" do
+ create_fake_key(:root, sample_lines)
+ run_in_catalog(resource)
+ check_fake_key(:root, sample_lines)
+ end
+
+ it "remove the key from root's keyfile if resource is currently present" do
+ create_fake_key(:root, sample_lines + ["ssh-rsa #{sample_rsa_keys[0]} root@hostname"])
+ run_in_catalog(resource)
+ check_fake_key(:root, sample_lines)
+ end
+ end
+
+ describe "when ensure is present" do
+ let :resource do
+ Puppet::Type.type(:ssh_authorized_key).new(
+ :name => 'root@hostname',
+ :type => :rsa,
+ :key => sample_rsa_keys[0],
+ :target => fake_rootfile,
+ :user => 'root',
+ :ensure => :present
+ )
+ end
+
+ # just a dummy so the parsedfile provider is aware
+ # of the user's authorized_keys file
+
+ it "should add the key if it is not present" do
+ create_fake_key(:root, sample_lines)
+ run_in_catalog(resource)
+ check_fake_key(:root, sample_lines + ["ssh-rsa #{sample_rsa_keys[0]} root@hostname" ])
+ end
+
+ it "should modify the type if type is out of sync" do
+ create_fake_key(:root,sample_lines + [ "ssh-dss #{sample_rsa_keys[0]} root@hostname" ])
+ run_in_catalog(resource)
+ check_fake_key(:root, sample_lines + [ "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ])
+ end
+
+ it "should modify the key if key is out of sync" do
+ create_fake_key(:root,sample_lines + [ "ssh-rsa #{sample_rsa_keys[1]} root@hostname" ])
+ run_in_catalog(resource)
+ check_fake_key(:root, sample_lines + [ "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ])
+ end
+
+ it "should remove the key from old file if target is out of sync" do
+ create_fake_key(:user, [ sample_lines[0], "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ])
+ create_fake_key(:root, [ sample_lines[1], sample_lines[2] ])
+ run_in_catalog(resource, dummy)
+ check_fake_key(:user, [ sample_lines[0] ])
+ #check_fake_key(:root, [ sample_lines[1], sample_lines[2], "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ])
+ end
+
+ it "should add the key to new file if target is out of sync" do
+ create_fake_key(:user, [ sample_lines[0], "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ])
+ create_fake_key(:root, [ sample_lines[1], sample_lines[2] ])
+ run_in_catalog(resource, dummy)
+ #check_fake_key(:user, [ sample_lines[0] ])
+ check_fake_key(:root, [ sample_lines[1], sample_lines[2], "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ])
+ end
+
+ it "should modify options if options are out of sync" do
+ resource[:options]=[ 'from="*.domain1,host1.domain2"', 'no-port-forwarding', 'no-pty' ]
+ create_fake_key(:root, sample_lines + [ "from=\"*.false,*.false2\",no-port-forwarding,no-pty ssh-rsa #{sample_rsa_keys[0]} root@hostname"])
+ run_in_catalog(resource)
+ check_fake_key(:root, sample_lines + [ "from=\"*.domain1,host1.domain2\",no-port-forwarding,no-pty ssh-rsa #{sample_rsa_keys[0]} root@hostname"] )
+ end
+ end
+ end
+
+ describe "when managing two resource" do
+ let :examples do
+ resources = []
+ resources << Puppet::Type.type(:ssh_authorized_key).new(
+ :name => 'root@hostname',
+ :type => :rsa,
+ :key => sample_rsa_keys[0],
+ :target => fake_rootfile,
+ :user => 'root',
+ :ensure => :present
+ )
+ resources << Puppet::Type.type(:ssh_authorized_key).new(
+ :name => 'user@hostname',
+ :key => sample_rsa_keys[1],
+ :type => :rsa,
+ :target => fake_userfile,
+ :user => 'nobody',
+ :ensure => :present
+ )
+ resources
+ end
+
+ describe "and both keys are absent" do
+ before :each do
+ create_fake_key(:root, sample_lines)
+ create_fake_key(:user, sample_lines)
+ end
+
+ it "should add both keys" do
+ run_in_catalog(*examples)
+ check_fake_key(:root, sample_lines + [ "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ])
+ check_fake_key(:user, sample_lines + [ "ssh-rsa #{sample_rsa_keys[1]} user@hostname" ])
+ end
+ end
+ end
+end
diff --git a/spec/integration/provider/sshkey_spec.rb b/spec/integration/provider/sshkey_spec.rb
new file mode 100644
index 0000000..f461460
--- /dev/null
+++ b/spec/integration/provider/sshkey_spec.rb
@@ -0,0 +1,159 @@
+#!/usr/bin/env ruby
+
+require 'spec_helper'
+require 'puppet/file_bucket/dipper'
+require 'puppet_spec/files'
+require 'puppet_spec/compiler'
+
+describe Puppet::Type.type(:sshkey).provider(:parsed), '(integration)',
+ :unless => Puppet.features.microsoft_windows? do
+ include PuppetSpec::Files
+ include PuppetSpec::Compiler
+
+ before :each do
+ # Don't backup to filebucket
+ Puppet::FileBucket::Dipper.any_instance.stubs(:backup)
+ # We don't want to execute anything
+ described_class.stubs(:filetype).
+ returns Puppet::Util::FileType::FileTypeFlat
+
+ @sshkey_file = tmpfile('sshkey_integration_specs')
+ FileUtils.cp(my_fixture('sample'), @sshkey_file)
+ end
+
+ after :each do
+ # sshkey provider class
+ described_class.clear
+ end
+
+ let(:type_under_test) { 'sshkey' }
+
+ describe "when managing a ssh known hosts file it..." do
+
+ let(:super_unique) { "my.super.unique.host" }
+ it "should create a new known_hosts file with mode 0644" do
+ target = tmpfile('ssh_known_hosts')
+ manifest = "#{type_under_test} { '#{super_unique}':
+ ensure => 'present',
+ type => 'rsa',
+ key => 'TESTKEY',
+ target => '#{target}' }"
+ apply_with_error_check(manifest)
+ expect_file_mode(target, "644")
+ end
+
+ it "should create an SSH host key entry (ensure present)" do
+ manifest = "#{type_under_test} { '#{super_unique}':
+ ensure => 'present',
+ type => 'rsa',
+ key => 'mykey',
+ target => '#{@sshkey_file}' }"
+ apply_with_error_check(manifest)
+ expect(File.read(@sshkey_file)).to match(/#{super_unique}.*mykey/)
+ end
+
+ let(:sshkey_name) { 'kirby.madstop.com' }
+ it "should delete an entry for an SSH host key" do
+ manifest = "#{type_under_test} { '#{sshkey_name}':
+ ensure => 'absent',
+ target => '#{@sshkey_file}' }"
+ apply_with_error_check(manifest)
+ expect(File.read(@sshkey_file)).not_to match(/#{sshkey_name}.*Yqk0=/)
+ end
+
+ it "should update an entry for an SSH host key" do
+ manifest = "#{type_under_test} { '#{sshkey_name}':
+ ensure => 'present',
+ type => 'rsa',
+ key => 'mynewshinykey',
+ target => '#{@sshkey_file}' }"
+ apply_with_error_check(manifest)
+ expect(File.read(@sshkey_file)).to match(/#{sshkey_name}.*mynewshinykey/)
+ expect(File.read(@sshkey_file)).not_to match(/#{sshkey_name}.*Yqk0=/)
+ end
+
+ # test all key types
+ types = ["ssh-dss", "dsa",
+ "ssh-ed25519", "ed25519",
+ "ssh-rsa", "rsa",
+ "ecdsa-sha2-nistp256",
+ "ecdsa-sha2-nistp384",
+ "ecdsa-sha2-nistp521"]
+ # these types are treated as aliases for sshkey <ahem> type
+ # so they are populated as the *values* below
+ aliases = {"dsa" => "ssh-dss",
+ "ed25519" => "ssh-ed25519",
+ "rsa" => "ssh-rsa"}
+ types.each do |type|
+ it "should update an entry with #{type} type" do
+ manifest = "#{type_under_test} { '#{sshkey_name}':
+ ensure => 'present',
+ type => '#{type}',
+ key => 'mynewshinykey',
+ target => '#{@sshkey_file}' }"
+
+ apply_with_error_check(manifest)
+ if aliases.has_key?(type)
+ full_type = aliases[type]
+ expect(File.read(@sshkey_file)).
+ to match(/#{sshkey_name}.*#{full_type}.*mynew/)
+ else
+ expect(File.read(@sshkey_file)).
+ to match(/#{sshkey_name}.*#{type}.*mynew/)
+ end
+ end
+ end
+
+ # test unknown key type fails
+ let(:invalid_type) { 'ssh-er0ck' }
+ it "should raise an error with an unknown type" do
+ manifest = "#{type_under_test} { '#{sshkey_name}':
+ ensure => 'present',
+ type => '#{invalid_type}',
+ key => 'mynewshinykey',
+ target => '#{@sshkey_file}' }"
+ expect {
+ apply_compiled_manifest(manifest)
+ }.to raise_error(Puppet::ResourceError, /Invalid value "#{invalid_type}"/)
+ end
+
+ #single host_alias
+ let(:host_alias) { 'r0ckdata.com' }
+ it "should update an entry with new host_alias" do
+ manifest = "#{type_under_test} { '#{sshkey_name}':
+ ensure => 'present',
+ host_aliases => '#{host_alias}',
+ target => '#{@sshkey_file}' }"
+ apply_with_error_check(manifest)
+ expect(File.read(@sshkey_file)).to match(/#{sshkey_name},#{host_alias}\s/)
+ expect(File.read(@sshkey_file)).not_to match(/#{sshkey_name}\s/)
+ end
+
+ #array host_alias
+ let(:host_aliases) { "r0ckdata.com,erict.net" }
+ it "should update an entry with new host_alias" do
+ manifest = "#{type_under_test} { '#{sshkey_name}':
+ ensure => 'present',
+ host_aliases => '#{host_alias}',
+ target => '#{@sshkey_file}' }"
+ apply_with_error_check(manifest)
+ expect(File.read(@sshkey_file)).to match(/#{sshkey_name},#{host_alias}\s/)
+ expect(File.read(@sshkey_file)).not_to match(/#{sshkey_name}\s/)
+ end
+
+ #puppet resource sshkey
+ it "should fetch an entry from resources" do
+ @resource_app = Puppet::Application[:resource]
+ @resource_app.preinit
+ @resource_app.command_line.stubs(:args).
+ returns([type_under_test, sshkey_name, "target=#{@sshkey_file}"])
+
+ @resource_app.expects(:puts).with do |args|
+ expect(args).to match(/#{sshkey_name}/)
+ end
+ @resource_app.main
+ end
+
+ end
+
+end
diff --git a/spec/lib/puppet_spec/compiler.rb b/spec/lib/puppet_spec/compiler.rb
new file mode 100644
index 0000000..8964a26
--- /dev/null
+++ b/spec/lib/puppet_spec/compiler.rb
@@ -0,0 +1,112 @@
+module PuppetSpec::Compiler
+ module_function
+
+ def compile_to_catalog(string, node = Puppet::Node.new('test'))
+ Puppet[:code] = string
+ # see lib/puppet/indirector/catalog/compiler.rb#filter
+ Puppet::Parser::Compiler.compile(node).filter { |r| r.virtual? }
+ end
+
+ # Does not removed virtual resources in compiled catalog (i.e. keeps unrealized)
+ def compile_to_catalog_unfiltered(string, node = Puppet::Node.new('test'))
+ Puppet[:code] = string
+ # see lib/puppet/indirector/catalog/compiler.rb#filter
+ Puppet::Parser::Compiler.compile(node)
+ end
+
+ def compile_to_ral(manifest, node = Puppet::Node.new('test'))
+ catalog = compile_to_catalog(manifest, node)
+ ral = catalog.to_ral
+ ral.finalize
+ ral
+ end
+
+ def compile_to_relationship_graph(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new)
+ ral = compile_to_ral(manifest)
+ graph = Puppet::Graph::RelationshipGraph.new(prioritizer)
+ graph.populate_from(ral)
+ graph
+ end
+
+ def apply_compiled_manifest(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new)
+ catalog = compile_to_ral(manifest)
+ if block_given?
+ catalog.resources.each { |res| yield res }
+ end
+ transaction = Puppet::Transaction.new(catalog,
+ Puppet::Transaction::Report.new,
+ prioritizer)
+ transaction.evaluate
+ transaction.report.finalize_report
+
+ transaction
+ end
+
+ def apply_with_error_check(manifest)
+ apply_compiled_manifest(manifest) do |res|
+ res.expects(:err).never
+ end
+ end
+
+ def order_resources_traversed_in(relationships)
+ order_seen = []
+ relationships.traverse { |resource| order_seen << resource.ref }
+ order_seen
+ end
+
+ def collect_notices(code, node = Puppet::Node.new('foonode'))
+ Puppet[:code] = code
+ compiler = Puppet::Parser::Compiler.new(node)
+ node.environment.check_for_reparse
+ logs = []
+ Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do
+ yield(compiler)
+ end
+ logs = logs.select { |log| log.level == :notice }.map { |log| log.message }
+ logs
+ end
+
+ def eval_and_collect_notices(code, node = Puppet::Node.new('foonode'), topscope_vars = {})
+ collect_notices(code, node) do |compiler|
+ unless topscope_vars.empty?
+ scope = compiler.topscope
+ topscope_vars.each {|k,v| scope.setvar(k, v) }
+ end
+ if block_given?
+ compiler.compile do |catalog|
+ yield(compiler.topscope, catalog)
+ catalog
+ end
+ else
+ compiler.compile
+ end
+ end
+ end
+
+ # Compiles a catalog, and if source is given evaluates it and returns its result.
+ # The catalog is returned if no source is given.
+ # Topscope variables are set before compilation
+ # Uses a created node 'testnode' if none is given.
+ # (Parameters given by name)
+ #
+ def evaluate(code: 'undef', source: nil, node: Puppet::Node.new('testnode'), variables: {})
+ source_location = caller[0]
+ Puppet[:code] = code
+ compiler = Puppet::Parser::Compiler.new(node)
+ unless variables.empty?
+ scope = compiler.topscope
+ variables.each {|k,v| scope.setvar(k, v) }
+ end
+
+ if source.nil?
+ compiler.compile
+ # see lib/puppet/indirector/catalog/compiler.rb#filter
+ return compiler.filter { |r| r.virtual? }
+ end
+
+ # evaluate given source is the context of the compiled state and return its result
+ compiler.compile do |catalog |
+ Puppet::Pops::Parser::EvaluatingParser.singleton.evaluate_string(compiler.topscope, source, source_location)
+ end
+ end
+end
diff --git a/spec/lib/puppet_spec/files.rb b/spec/lib/puppet_spec/files.rb
new file mode 100644
index 0000000..a6529f6
--- /dev/null
+++ b/spec/lib/puppet_spec/files.rb
@@ -0,0 +1,126 @@
+require 'fileutils'
+require 'tempfile'
+require 'tmpdir'
+require 'pathname'
+
+# A support module for testing files.
+module PuppetSpec::Files
+ @global_tempfiles = []
+
+ def self.cleanup
+ until @global_tempfiles.empty?
+ path = @global_tempfiles.pop
+ begin
+ Dir.unstub(:entries)
+ FileUtils.rm_rf path, secure: true
+ rescue Errno::ENOENT # rubocop:disable Lint/HandleExceptions
+ # nothing to do
+ end
+ end
+ end
+
+ def make_absolute(path)
+ PuppetSpec::Files.make_absolute(path)
+ end
+
+ def self.make_absolute(path)
+ path = File.expand_path(path)
+ path[0] = 'c' if Puppet.features.microsoft_windows?
+ path
+ end
+
+ def tmpfile(name, dir = nil)
+ PuppetSpec::Files.tmpfile(name, dir)
+ end
+
+ def self.tmpfile(name, dir = nil)
+ # Generate a temporary file, just for the name...
+ source = dir ? Tempfile.new(name, dir) : Tempfile.new(name)
+ path = Puppet::FileSystem.expand_path(source.path.encode(Encoding::UTF_8))
+ source.close!
+
+ record_tmp(File.expand_path(path))
+
+ path
+ end
+
+ def file_containing(name, contents)
+ PuppetSpec::Files.file_containing(name, contents)
+ end
+
+ def self.file_containing(name, contents)
+ file = tmpfile(name)
+ File.open(file, 'wb') { |f| f.write(contents) }
+ file
+ end
+
+ def script_containing(name, contents)
+ PuppetSpec::Files.script_containing(name, contents)
+ end
+
+ def self.script_containing(name, contents)
+ file = tmpfile(name)
+ if Puppet.features.microsoft_windows?
+ file += '.bat'
+ text = contents[:windows]
+ else
+ text = contents[:posix]
+ end
+ File.open(file, 'wb') { |f| f.write(text) }
+ Puppet::FileSystem.chmod(0o755, file)
+ file
+ end
+
+ def tmpdir(name)
+ PuppetSpec::Files.tmpdir(name)
+ end
+
+ def self.tmpdir(name)
+ dir = Puppet::FileSystem.expand_path(Dir.mktmpdir(name).encode!(Encoding::UTF_8))
+
+ record_tmp(dir)
+
+ dir
+ end
+
+ def dir_containing(name, contents_hash)
+ PuppetSpec::Files.dir_containing(name, contents_hash)
+ end
+
+ def self.dir_containing(name, contents_hash)
+ dir_contained_in(tmpdir(name), contents_hash)
+ end
+
+ def dir_contained_in(dir, contents_hash)
+ PuppetSpec::Files.dir_contained_in(dir, contents_hash)
+ end
+
+ def self.dir_contained_in(dir, contents_hash)
+ contents_hash.each do |k, v|
+ if v.is_a?(Hash)
+ Dir.mkdir(tmp = File.join(dir, k))
+ dir_contained_in(tmp, v)
+ else
+ file = File.join(dir, k)
+ File.open(file, 'wb') { |f| f.write(v) }
+ end
+ end
+ dir
+ end
+
+ def self.record_tmp(tmp)
+ # ...record it for cleanup,
+ @global_tempfiles ||= []
+ @global_tempfiles << tmp
+ end
+
+ def expect_file_mode(file, mode)
+ actual_mode = '%o' % Puppet::FileSystem.stat(file).mode
+ target_mode = if Puppet.features.microsoft_windows?
+ mode
+ else
+ '10' + '%04i' % mode.to_i
+ end
+ expect(actual_mode).to eq(target_mode)
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..e69d11d
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,45 @@
+
+require 'puppetlabs_spec_helper/module_spec_helper'
+require 'rspec-puppet-facts'
+
+begin
+ require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb'))
+rescue LoadError => loaderror
+ warn "Could not require spec_helper_local: #{loaderror.message}"
+end
+
+include RspecPuppetFacts
+
+default_facts = {
+ puppetversion: Puppet.version,
+ facterversion: Facter.version,
+}
+
+default_facts_path = File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml'))
+default_module_facts_path = File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml'))
+
+if File.exist?(default_facts_path) && File.readable?(default_facts_path)
+ default_facts.merge!(YAML.safe_load(File.read(default_facts_path)))
+end
+
+if File.exist?(default_module_facts_path) && File.readable?(default_module_facts_path)
+ default_facts.merge!(YAML.safe_load(File.read(default_module_facts_path)))
+end
+
+RSpec.configure do |c|
+ c.default_facts = default_facts
+ c.before :each do
+ # set to strictest setting for testing
+ # by default Puppet runs at warning level
+ Puppet.settings[:strict] = :warning
+ end
+end
+
+def ensure_module_defined(module_name)
+ module_name.split('::').reduce(Object) do |last_module, next_module|
+ last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module)
+ last_module.const_get(next_module)
+ end
+end
+
+# 'spec_overrides' from sync.yml will appear below this line
diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb
new file mode 100644
index 0000000..fc786a6
--- /dev/null
+++ b/spec/spec_helper_local.rb
@@ -0,0 +1,17 @@
+dir = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift File.join(dir, 'lib')
+
+# Container for various Puppet-specific RSpec helpers.
+module PuppetSpec
+end
+
+require 'puppet_spec/files'
+
+RSpec.configure do |config|
+ config.before :each do |test|
+ base = PuppetSpec::Files.tmpdir('tmp_settings')
+ Puppet[:vardir] = File.join(base, 'var')
+
+ FileUtils.mkdir_p Puppet[:statedir]
+ end
+end \ No newline at end of file
diff --git a/spec/unit/provider/sshkey/parsed_spec.rb b/spec/unit/provider/sshkey/parsed_spec.rb
new file mode 100644
index 0000000..38aa7f7
--- /dev/null
+++ b/spec/unit/provider/sshkey/parsed_spec.rb
@@ -0,0 +1,93 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+describe "sshkey parsed provider" do
+ let :type do Puppet::Type.type(:sshkey) end
+ let :provider do type.provider(:parsed) end
+ subject { provider }
+
+ after :each do
+ subject.clear
+ end
+
+ def key
+ 'AAAAB3NzaC1yc2EAAAABIwAAAQEAzwHhxXvIrtfIwrudFqc8yQcIfMudrgpnuh1F3AV6d2BrLgu/yQE7W5UyJMUjfj427sQudRwKW45O0Jsnr33F4mUw+GIMlAAmp9g24/OcrTiB8ZUKIjoPy/cO4coxGi8/NECtRzpD/ZUPFh6OEpyOwJPMb7/EC2Az6Otw4StHdXUYw22zHazBcPFnv6zCgPx1hA7QlQDWTu4YcL0WmTYQCtMUb3FUqrcFtzGDD0ytosgwSd+JyN5vj5UwIABjnNOHPZ62EY1OFixnfqX/+dUwrFSs5tPgBF/KkC6R7tmbUfnBON6RrGEmu+ajOTOLy23qUZB4CQ53V7nyAWhzqSK+hw=='
+ end
+
+ it "should parse the name from the first field" do
+ expect(subject.parse_line('test ssh-rsa '+key)[:name]).to eq("test")
+ end
+
+ it "should parse the first component of the first field as the name" do
+ expect(subject.parse_line('test,alias ssh-rsa '+key)[:name]).to eq("test")
+ end
+
+ it "should parse host_aliases from the remaining components of the first field" do
+ expect(subject.parse_line('test,alias ssh-rsa '+key)[:host_aliases]).to eq(["alias"])
+ end
+
+ it "should parse multiple host_aliases" do
+ expect(subject.parse_line('test,alias1,alias2,alias3 ssh-rsa '+key)[:host_aliases]).to eq(["alias1","alias2","alias3"])
+ end
+
+ it "should not drop an empty host_alias" do
+ expect(subject.parse_line('test,alias, ssh-rsa '+key)[:host_aliases]).to eq(["alias",""])
+ end
+
+ it "should recognise when there are no host aliases" do
+ expect(subject.parse_line('test ssh-rsa '+key)[:host_aliases]).to eq([])
+ end
+
+ context "with the sample file" do
+ ['sample', 'sample_with_blank_lines'].each do |sample_file|
+ let :fixture do my_fixture(sample_file) end
+ before :each do subject.stubs(:default_target).returns(fixture) end
+
+ it "should parse to records on prefetch" do
+ expect(subject.target_records(fixture)).to be_empty
+ subject.prefetch
+
+ records = subject.target_records(fixture)
+ expect(records).to be_an Array
+ expect(records).to be_all {|x| expect(x).to be_an Hash }
+ end
+
+ it "should reconstitute the file from records" do
+ subject.prefetch
+ records = subject.target_records(fixture)
+ text = subject.to_file(records).gsub(/^# HEADER.+\n/, '')
+
+ oldlines = File.readlines(fixture).map(&:chomp)
+ newlines = text.chomp.split("\n")
+ expect(oldlines.length).to eq(newlines.length)
+
+ oldlines.zip(newlines).each do |old, new|
+ expect(old.gsub(/\s+/, '')).to eq(new.gsub(/\s+/, ''))
+ end
+ end
+ end
+ end
+
+ context 'default ssh_known_hosts target path' do
+ ['9.10', '9.11', '10.10'].each do |version|
+ it 'should be `/etc/ssh_known_hosts` when OSX version 10.10 or older`' do
+ Facter.expects(:value).with(:operatingsystem).returns('Darwin')
+ Facter.expects(:value).with(:macosx_productversion_major).returns(version)
+ expect(subject.default_target).to eq('/etc/ssh_known_hosts')
+ end
+ end
+
+ ['10.11', '10.13', '11.0', '11.11'].each do |version|
+ it 'should be `/etc/ssh/ssh_known_hosts` when OSX version 10.11 or newer`' do
+ Facter.expects(:value).with(:operatingsystem).returns('Darwin')
+ Facter.expects(:value).with(:macosx_productversion_major).returns(version)
+ expect(subject.default_target).to eq('/etc/ssh/ssh_known_hosts')
+ end
+ end
+
+ it 'should be `/etc/ssh/ssh_known_hosts` on other operating systems' do
+ Facter.expects(:value).with(:operatingsystem).returns('RedHat')
+ expect(subject.default_target).to eq('/etc/ssh/ssh_known_hosts')
+ end
+ end
+end
diff --git a/spec/unit/type/ssh_authorized_key_spec.rb b/spec/unit/type/ssh_authorized_key_spec.rb
new file mode 100644
index 0000000..ae93667
--- /dev/null
+++ b/spec/unit/type/ssh_authorized_key_spec.rb
@@ -0,0 +1,258 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+
+describe Puppet::Type.type(:ssh_authorized_key), :unless => Puppet.features.microsoft_windows? do
+ include PuppetSpec::Files
+
+ before do
+ provider_class = stub 'provider_class', :name => "fake", :suitable? => true, :supports_parameter? => true
+ described_class.stubs(:defaultprovider).returns(provider_class)
+ described_class.stubs(:provider).returns(provider_class)
+
+ provider = stub 'provider', :class => provider_class, :file_path => make_absolute("/tmp/whatever"), :clear => nil
+ provider_class.stubs(:new).returns(provider)
+ end
+
+ it "has :name as its namevar" do
+ expect(described_class.key_attributes).to eq [:name]
+ end
+
+ describe "when validating attributes" do
+
+ [:name, :provider].each do |param|
+ it "has a #{param} parameter" do
+ expect(described_class.attrtype(param)).to eq :param
+ end
+ end
+
+ [:type, :key, :user, :target, :options, :ensure].each do |property|
+ it "has a #{property} property" do
+ expect(described_class.attrtype(property)).to eq :property
+ end
+ end
+
+ end
+
+ describe "when validating values" do
+
+ describe "for name" do
+
+ it "supports valid names" do
+ described_class.new(:name => "username", :ensure => :present, :user => "nobody")
+ described_class.new(:name => "username@hostname", :ensure => :present, :user => "nobody")
+ end
+
+ it "supports whitespace" do
+ described_class.new(:name => "my test", :ensure => :present, :user => "nobody")
+ end
+
+ end
+
+ describe "for ensure" do
+
+ it "supports :present" do
+ described_class.new(:name => "whev", :ensure => :present, :user => "nobody")
+ end
+
+ it "supports :absent" do
+ described_class.new(:name => "whev", :ensure => :absent, :user => "nobody")
+ end
+
+ it "nots support other values" do
+ expect { described_class.new(:name => "whev", :ensure => :foo, :user => "nobody") }.to raise_error(Puppet::Error, /Invalid value/)
+ end
+
+ end
+
+ describe "for type" do
+
+ [
+ :'ssh-dss', :dsa,
+ :'ssh-rsa', :rsa,
+ :'ecdsa-sha2-nistp256',
+ :'ecdsa-sha2-nistp384',
+ :'ecdsa-sha2-nistp521',
+ :ed25519, :'ssh-ed25519',
+ ].each do |keytype|
+ it "supports #{keytype}" do
+ described_class.new(:name => "whev", :type => keytype, :user => "nobody")
+ end
+ end
+
+ it "aliases :rsa to :ssh-rsa" do
+ key = described_class.new(:name => "whev", :type => :rsa, :user => "nobody")
+ expect(key.should(:type)).to eq :'ssh-rsa'
+ end
+
+ it "aliases :dsa to :ssh-dss" do
+ key = described_class.new(:name => "whev", :type => :dsa, :user => "nobody")
+ expect(key.should(:type)).to eq :'ssh-dss'
+ end
+
+ it "doesn't support values other than ssh-dss, ssh-rsa, dsa, rsa" do
+ expect { described_class.new(:name => "whev", :type => :something) }.to raise_error(Puppet::Error,/Invalid value/)
+ end
+
+ end
+
+ describe "for key" do
+
+ it "supports a valid key like a 1024 bit rsa key" do
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCPfzW2ry7XvMc6E5Kj2e5fF/YofhKEvsNMUogR3PGL/HCIcBlsEjKisrY0aYgD8Ikp7ZidpXLbz5dBsmPy8hJiBWs5px9ZQrB/EOQAwXljvj69EyhEoGawmxQMtYw+OAIKHLJYRuk1QiHAMHLp5piqem8ZCV2mLb9AsJ6f7zUVw==')}.to_not raise_error
+ end
+
+ it "supports a valid key like a 4096 bit rsa key" do
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAAAB3NzaC1yc2EAAAADAQABAAACAQDEY4pZFyzSfRc9wVWI3DfkgT/EL033UZm/7x1M+d+lBD00qcpkZ6CPT7lD3Z+vylQlJ5S8Wcw6C5Smt6okZWY2WXA9RCjNJMIHQbJAzwuQwgnwU/1VMy9YPp0tNVslg0sUUgpXb13WW4mYhwxyGmIVLJnUrjrQmIFhtfHsJAH8ZVqCWaxKgzUoC/YIu1u1ScH93lEdoBPLlwm6J0aiM7KWXRb7Oq1nEDZtug1zpX5lhgkQWrs0BwceqpUbY+n9sqeHU5e7DCyX/yEIzoPRW2fe2Gx1Iq6JKM/5NNlFfaW8rGxh3Z3S1NpzPHTRjw8js3IeGiV+OPFoaTtM1LsWgPDSBlzIdyTbSQR7gKh0qWYCNV/7qILEfa0yIFB5wIo4667iSPZw2pNgESVtenm8uXyoJdk8iWQ4mecdoposV/znknNb2GPgH+n/2vme4btZ0Sl1A6rev22GQjVgbWOn8zaDglJ2vgCN1UAwmq41RXprPxENGeLnWQppTnibhsngu0VFllZR5kvSIMlekLRSOFLFt92vfd+tk9hZIiKm9exxcbVCGGQPsf6dZ27rTOmg0xM2Sm4J6RRKuz79HQgA4Eg18+bqRP7j/itb89DmtXEtoZFAsEJw8IgIfeGGDtHTkfAlAC92mtK8byeaxGq57XCTKbO/r5gcOMElZHy1AcB8kw==')}.to_not raise_error
+ end
+
+ it "supports a valid key like a 1024 bit dsa key" do
+ expect { described_class.new(:name => "whev", :type => :dsa, :user => "nobody", :key => 'AAAAB3NzaC1kc3MAAACBAI80iR78QCgpO4WabVqHHdEDigOjUEHwIjYHIubR/7u7DYrXY+e+TUmZ0CVGkiwB/0yLHK5dix3Y/bpj8ZiWCIhFeunnXccOdE4rq5sT2V3l1p6WP33RpyVYbLmeuHHl5VQ1CecMlca24nHhKpfh6TO/FIwkMjghHBfJIhXK+0w/AAAAFQDYzLupuMY5uz+GVrcP+Kgd8YqMmwAAAIB3SVN71whLWjFPNTqGyyIlMy50624UfNOaH4REwO+Of3wm/cE6eP8n75vzTwQGBpJX3BPaBGW1S1Zp/DpTOxhCSAwZzAwyf4WgW7YyAOdxN3EwTDJZeyiyjWMAOjW9/AOWt9gtKg0kqaylbMHD4kfiIhBzo31ZY81twUzAfN7angAAAIBfva8sTSDUGKsWWIXkdbVdvM4X14K4gFdy0ZJVzaVOtZ6alysW6UQypnsl6jfnbKvsZ0tFgvcX/CPyqNY/gMR9lyh/TCZ4XQcbqeqYPuceGehz+jL5vArfqsW2fJYFzgCcklmr/VxtP5h6J/T0c9YcDgc/xIfWdZAlznOnphI/FA==')}.to_not raise_error
+ end
+
+ it "doesn't support whitespaces" do
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAA FA==')}.to raise_error(Puppet::Error,/Key must not contain whitespace/)
+ end
+
+ end
+
+ describe "for options" do
+
+ it "supports flags as options" do
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'cert-authority')}.to_not raise_error
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'no-port-forwarding')}.to_not raise_error
+ end
+
+ it "supports key-value pairs as options" do
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'command="command"')}.to_not raise_error
+ end
+
+ it "supports key-value pairs where value consist of multiple items" do
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'from="*.domain1,host1.domain2"')}.to_not raise_error
+ end
+
+ it "supports environments as options" do
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'environment="NAME=value"')}.to_not raise_error
+ end
+
+ it "supports multiple options as an array" do
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ['cert-authority','environment="NAME=value"'])}.to_not raise_error
+ end
+
+ it "doesn't support a comma separated list" do
+ expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'cert-authority,no-port-forwarding')}.to raise_error(Puppet::Error, /must be provided as an array/)
+ end
+
+ it "uses :absent as a default value" do
+ expect(described_class.new(:name => "whev", :type => :rsa, :user => "nobody").should(:options)).to eq [:absent]
+ end
+
+ it "property should return well formed string of arrays from is_to_s" do
+ resource = described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ["a","b","c"])
+ expect(resource.property(:options).is_to_s(["a","b","c"])).to eq "['a', 'b', 'c']"
+ end
+
+ it "property should return well formed string of arrays from should_to_s" do
+ resource = described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ["a","b","c"])
+ expect(resource.property(:options).should_to_s(["a","b","c"])).to eq "['a', 'b', 'c']"
+ end
+
+ end
+
+ describe "for user" do
+
+ it "supports present users" do
+ described_class.new(:name => "whev", :type => :rsa, :user => "root")
+ end
+
+ it "supports absent users" do
+ described_class.new(:name => "whev", :type => :rsa, :user => "ihopeimabsent")
+ end
+
+ end
+
+ describe "for target" do
+
+ it "supports absolute paths" do
+ described_class.new(:name => "whev", :type => :rsa, :target => "/tmp/here")
+ end
+
+ it "uses the user's path if not explicitly specified" do
+ expect(described_class.new(:name => "whev", :user => 'root').should(:target)).to eq File.expand_path("~root/.ssh/authorized_keys")
+ end
+
+ it "doesn't consider the user's path if explicitly specified" do
+ expect(described_class.new(:name => "whev", :user => 'root', :target => '/tmp/here').should(:target)).to eq '/tmp/here'
+ end
+
+ it "informs about an absent user" do
+ Puppet::Log.level = :debug
+ described_class.new(:name => "whev", :user => 'idontexist').should(:target)
+ expect(@logs.map(&:message)).to include("The required user is not yet present on the system")
+ end
+
+ end
+
+ end
+
+ describe "when neither user nor target is specified" do
+
+ it "raises an error" do
+ expect do
+ described_class.new(
+ :name => "Test",
+ :key => "AAA",
+ :type => "ssh-rsa",
+ :ensure => :present)
+ end.to raise_error(Puppet::Error,/user.*or.*target.*mandatory/)
+ end
+
+ end
+
+ describe "when both target and user are specified" do
+
+ it "uses target" do
+ resource = described_class.new(
+ :name => "Test",
+ :user => "root",
+ :target => "/tmp/blah"
+ )
+ expect(resource.should(:target)).to eq "/tmp/blah"
+ end
+
+ end
+
+
+ describe "when user is specified" do
+
+ it "determines target" do
+ resource = described_class.new(
+ :name => "Test",
+ :user => "root"
+ )
+ target = File.expand_path("~root/.ssh/authorized_keys")
+ expect(resource.should(:target)).to eq target
+ end
+
+ # Bug #2124 - ssh_authorized_key always changes target if target is not defined
+ it "doesn't raise spurious change events" do
+ resource = described_class.new(:name => "Test", :user => "root")
+ target = File.expand_path("~root/.ssh/authorized_keys")
+ expect(resource.property(:target).safe_insync?(target)).to eq true
+ end
+
+ end
+
+ describe "when calling validate" do
+
+ it "doesn't crash on a non-existent user" do
+ resource = described_class.new(
+ :name => "Test",
+ :user => "ihopesuchuserdoesnotexist"
+ )
+ resource.validate
+ end
+
+ end
+
+end
diff --git a/spec/unit/type/sshkey_spec.rb b/spec/unit/type/sshkey_spec.rb
new file mode 100644
index 0000000..d16e595
--- /dev/null
+++ b/spec/unit/type/sshkey_spec.rb
@@ -0,0 +1,77 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+
+describe Puppet::Type.type(:sshkey) do
+
+ it "uses :name as its namevar" do
+ expect(described_class.key_attributes).to eq [:name]
+ end
+
+ describe "when validating attributes" do
+ [:name, :provider].each do |param|
+ it "has a #{param} parameter" do
+ expect(described_class.attrtype(param)).to eq :param
+ end
+ end
+
+ [:host_aliases, :ensure, :key, :type].each do |property|
+ it "has a #{property} property" do
+ expect(described_class.attrtype(property)).to eq :property
+ end
+ end
+ end
+
+ describe "when validating values" do
+
+ [
+ :'ssh-dss', :dsa,
+ :'ssh-rsa', :rsa,
+ :'ecdsa-sha2-nistp256',
+ :'ecdsa-sha2-nistp384',
+ :'ecdsa-sha2-nistp521',
+ :'ssh-ed25519', :ed25519,
+ ].each do |keytype|
+ it "supports #{keytype} as a type value" do
+ described_class.new(:name => "foo", :type => keytype)
+ end
+ end
+
+ it "aliases :rsa to :ssh-rsa" do
+ key = described_class.new(:name => "foo", :type => :rsa)
+ expect(key.should(:type)).to eq :'ssh-rsa'
+ end
+
+ it "aliases :dsa to :ssh-dss" do
+ key = described_class.new(:name => "foo", :type => :dsa)
+ expect(key.should(:type)).to eq :'ssh-dss'
+ end
+
+ it "doesn't support values other than ssh-dss, ssh-rsa, dsa, rsa for type" do
+ expect {
+ described_class.new(:name => "whev", :type => :'ssh-dsa')
+ }.to raise_error(Puppet::Error, /Invalid value.*ssh-dsa/)
+ end
+
+ it "accepts one host_alias" do
+ described_class.new(:name => "foo", :host_aliases => 'foo.bar.tld')
+ end
+
+ it "accepts multiple host_aliases as an array" do
+ described_class.new(:name => "foo", :host_aliases => ['foo.bar.tld','10.0.9.9'])
+ end
+
+ it "doesn't accept spaces in any host_alias" do
+ expect {
+ described_class.new(:name => "foo", :host_aliases => ['foo.bar.tld','foo bar'])
+ }.to raise_error(Puppet::Error, /cannot include whitespace/)
+ end
+
+ it "doesn't accept aliases in the resourcename" do
+ expect {
+ described_class.new(:name => 'host,host.domain,ip')
+ }.to raise_error(Puppet::Error, /No comma in resourcename/)
+ end
+
+ end
+end