diff --git a/lib/puppet/provider/ssh_authorized_key/parsed.rb b/lib/puppet/provider/ssh_authorized_key/parsed.rb index f60a13173..af0e082ea 100644 --- a/lib/puppet/provider/ssh_authorized_key/parsed.rb +++ b/lib/puppet/provider/ssh_authorized_key/parsed.rb @@ -1,89 +1,89 @@ require 'puppet/provider/parsedfile' Puppet::Type.type(:ssh_authorized_key).provide( :parsed, :parent => Puppet::Provider::ParsedFile, :filetype => :flat, :default_target => '' ) do desc "Parse and generate authorized_keys files for SSH." text_line :comment, :match => /^\s*#/ text_line :blank, :match => /^\s*$/ record_line :parsed, :fields => %w{options type key name}, :optional => %w{options}, :rts => /^\s+/, - :match => /^(?:(.+) )?(ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521) ([^ ]+) ?(.*)$/, + :match => /^(?:(.+) )?(ssh-dss|ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521) ([^ ]+) ?(.*)$/, :post_parse => proc { |h| h[:name] = "" if h[:name] == :absent h[:options] ||= [:absent] h[:options] = Puppet::Type::Ssh_authorized_key::ProviderParsed.parse_options(h[:options]) if h[:options].is_a? String }, :pre_gen => proc { |h| h[:options] = [] if h[:options].include?(:absent) h[:options] = h[:options].join(',') } record_line :key_v1, :fields => %w{options bits exponent modulus name}, :optional => %w{options}, :rts => /^\s+/, :match => /^(?:(.+) )?(\d+) (\d+) (\d+)(?: (.+))?$/ def dir_perm 0700 end def file_perm 0600 end def user uid = Puppet::FileSystem.stat(target).uid Etc.getpwuid(uid).name end def flush raise Puppet::Error, "Cannot write SSH authorized keys without user" unless @resource.should(:user) raise Puppet::Error, "User '#{@resource.should(:user)}' does not exist" unless Puppet::Util.uid(@resource.should(:user)) # ParsedFile usually calls backup_target much later in the flush process, # but our SUID makes that fail to open filebucket files for writing. # Fortunately, there's already logic to make sure it only ever happens once, # so calling it here supresses the later attempt by our superclass's flush method. self.class.backup_target(target) Puppet::Util::SUIDManager.asuser(@resource.should(:user)) do unless Puppet::FileSystem.exist?(dir = File.dirname(target)) Puppet.debug "Creating #{dir}" Dir.mkdir(dir, dir_perm) end super File.chmod(file_perm, target) end end # parse sshv2 option strings, wich is a comma separated list of # either key="values" elements or bare-word elements def self.parse_options(options) result = [] scanner = StringScanner.new(options) while !scanner.eos? scanner.skip(/[ \t]*/) # scan a long option if out = scanner.scan(/[-a-z0-9A-Z_]+=\".*?\"/) or out = scanner.scan(/[-a-z0-9A-Z_]+/) result << out else # found an unscannable token, let's abort break end # eat a comma scanner.skip(/[ \t]*,[ \t]*/) end result end end diff --git a/lib/puppet/type/sshkey.rb b/lib/puppet/type/sshkey.rb index 41948ed98..db3c52c85 100644 --- a/lib/puppet/type/sshkey.rb +++ b/lib/puppet/type/sshkey.rb @@ -1,72 +1,73 @@ module Puppet newtype(:sshkey) do @doc = "Installs and manages ssh host keys. At this point, this type only knows how to install keys into `/etc/ssh/ssh_known_hosts`. See the `ssh_authorized_key` type to manage authorized keys." ensurable newproperty(:type) do desc "The encryption type used. Probably ssh-dss or ssh-rsa." - newvalues :'ssh-dss', :'ssh-rsa', :'ecdsa-sha2-nistp256', :'ecdsa-sha2-nistp384', :'ecdsa-sha2-nistp521' + newvalues :'ssh-dss', :'ssh-ed25519', :'ssh-rsa', :'ecdsa-sha2-nistp256', :'ecdsa-sha2-nistp384', :'ecdsa-sha2-nistp521' aliasvalue(:dsa, :'ssh-dss') + aliasvalue(:ed25519, :'ssh-ed25519') aliasvalue(:rsa, :'ssh-rsa') end newproperty(:key) do desc "The key itself; generally a long string of hex digits." end # FIXME This should automagically check for aliases to the hosts, just # to see if we can automatically glean any aliases. newproperty(:host_aliases) do desc 'Any aliases the host might have. Multiple values must be specified as an array.' attr_accessor :meta def insync?(is) is == @should end # We actually want to return the whole array here, not just the first # value. def should defined?(@should) ? @should : nil end validate do |value| if value =~ /\s/ raise Puppet::Error, "Aliases cannot include whitespace" end if value =~ /,/ raise Puppet::Error, "Aliases must be provided as an array, not a comma-separated list" end end end newparam(:name) do desc "The host name that the key is associated with." isnamevar validate do |value| raise Puppet::Error, "Resourcename cannot include whitespaces" if value =~ /\s/ raise Puppet::Error, "No comma in resourcename allowed. If you want to specify aliases use the host_aliases property" if value.include?(',') end end newproperty(:target) do desc "The file in which to store the ssh key. Only used by the `parsed` provider." defaultto { if @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile) @resource.class.defaultprovider.default_target else nil end } end end end diff --git a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb index 9a908a380..d0cd4e850 100755 --- a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb +++ b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb @@ -1,259 +1,255 @@ #! /usr/bin/env ruby require 'spec_helper' require 'shared_behaviours/all_parsedfile_providers' require 'puppet_spec/files' provider_class = Puppet::Type.type(:ssh_authorized_key).provider(:parsed) describe provider_class, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :each do @keyfile = tmpfile('authorized_keys') @provider_class = provider_class @provider_class.initvars @provider_class.any_instance.stubs(:target).returns @keyfile @user = 'random_bob' Puppet::Util.stubs(:uid).with(@user).returns 12345 end def mkkey(args) args[:target] = @keyfile args[:user] = @user resource = Puppet::Type.type(:ssh_authorized_key).new(args) key = @provider_class.new(resource) args.each do |p,v| key.send(p.to_s + "=", v) end key end def genkey(key) @provider_class.stubs(:filetype).returns(Puppet::Util::FileType::FileTypeRam) File.stubs(:chown) File.stubs(:chmod) Puppet::Util::SUIDManager.stubs(:asuser).yields key.flush @provider_class.target_object(@keyfile).read end it_should_behave_like "all parsedfile providers", provider_class it "should be able to generate a basic authorized_keys file" do key = mkkey(:name => "Just_Testing", :key => "AAAAfsfddsjldjgksdflgkjsfdlgkj", :type => "ssh-dss", :ensure => :present, :options => [:absent] ) genkey(key).should == "ssh-dss AAAAfsfddsjldjgksdflgkjsfdlgkj Just_Testing\n" end it "should be able to generate an authorized_keys file with options" do key = mkkey(:name => "root@localhost", :key => "AAAAfsfddsjldjgksdflgkjsfdlgkj", :type => "ssh-rsa", :ensure => :present, :options => ['from="192.168.1.1"', "no-pty", "no-X11-forwarding"] ) genkey(key).should == "from=\"192.168.1.1\",no-pty,no-X11-forwarding ssh-rsa AAAAfsfddsjldjgksdflgkjsfdlgkj root@localhost\n" end it "should parse comments" do result = [{ :record_type => :comment, :line => "# hello" }] @provider_class.parse("# hello\n").should == result end it "should parse comments with leading whitespace" do result = [{ :record_type => :comment, :line => " # hello" }] @provider_class.parse(" # hello\n").should == result end it "should skip over lines with only whitespace" do result = [{ :record_type => :comment, :line => "#before" }, { :record_type => :blank, :line => " " }, { :record_type => :comment, :line => "#after" }] @provider_class.parse("#before\n \n#after\n").should == result end it "should skip over completely empty lines" do result = [{ :record_type => :comment, :line => "#before"}, { :record_type => :blank, :line => ""}, { :record_type => :comment, :line => "#after"}] @provider_class.parse("#before\n\n#after\n").should == result end it "should be able to parse name if it includes whitespace" do @provider_class.parse_line('ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7pHZ1XRj3tXbFpPFhMGU1bVwz7jr13zt/wuE+pVIJA8GlmHYuYtIxHPfDHlkixdwLachCpSQUL9NbYkkRFRn9m6PZ7125ohE4E4m96QS6SGSQowTiRn4Lzd9LV38g93EMHjPmEkdSq7MY4uJEd6DUYsLvaDYdIgBiLBIWPA3OrQ== fancy user')[:name].should == 'fancy user' @provider_class.parse_line('from="host1.reductlivelabs.com,host.reductivelabs.com",command="/usr/local/bin/run",ssh-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7pHZ1XRj3tXbFpPFhMGU1bVwz7jr13zt/wuE+pVIJA8GlmHYuYtIxHPfDHlkixdwLachCpSQUL9NbYkkRFRn9m6PZ7125ohE4E4m96QS6SGSQowTiRn4Lzd9LV38g93EMHjPmEkdSq7MY4uJEd6DUYsLvaDYdIgBiLBIWPA3OrQ== fancy user')[:name].should == 'fancy user' end it "should be able to parse options containing commas via its parse_options method" do options = %w{from="host1.reductlivelabs.com,host.reductivelabs.com" command="/usr/local/bin/run" ssh-pty} optionstr = options.join(", ") @provider_class.parse_options(optionstr).should == options end it "should use '' as name for entries that lack a comment" do line = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAut8aOSxenjOqF527dlsdHWV4MNoAsX14l9M297+SQXaQ5Z3BedIxZaoQthkDALlV/25A1COELrg9J2MqJNQc8Xe9XQOIkBQWWinUlD/BXwoOTWEy8C8zSZPHZ3getMMNhGTBO+q/O+qiJx3y5cA4MTbw2zSxukfWC87qWwcZ64UUlegIM056vPsdZWFclS9hsROVEa57YUMrehQ1EGxT4Z5j6zIopufGFiAPjZigq/vqgcAqhAKP6yu4/gwO6S9tatBeEjZ8fafvj1pmvvIplZeMr96gHE7xS3pEEQqnB3nd4RY7AF6j9kFixnsytAUO7STPh/M3pLiVQBN89TvWPQ==" @provider_class.parse(line)[0][:name].should == "" end - ['ssh-dss', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521'].each do |keytype| + { + # ssh-keygen -t dsa -b 1024 + 'ssh-dss' => 'AAAAB3NzaC1kc3MAAACBANGTefWMXS780qLMMgysq3GNMKzg55LXZODif6Tqv1vtTh4Wuk3J5X5u644jTyNdAIn1RiBI9MnwnZMZ6nXKvucMcMQWMibYS9W2MhkRj3oqsLWMMsdGXJL18SWM5A6oC3oIRC4JHJZtkm0OctR2trKxmX+MGhdCd+Xpsh9CNK8XAAAAFQD4olFiwv+QQUFdaZbWUy1CLEG9xQAAAIByCkXKgoriZF8bQ0OX1sKuR69M/6n5ngmQGVBKB7BQkpUjbK/OggB6iJgst5utKkDcaqYRnrTYG9q3jJ/flv7yYePuoSreS0nCMMx9gpEYuq+7Sljg9IecmN/IHrNd9qdYoASy5iuROQMvEZM7KFHA8vBv0tWdBOsp4hZKyiL1DAAAAIEAjkZlOps9L+cD/MTzxDj7toYYypdLOvjlcPBaglkPZoFZ0MAKTI0zXlVX1cWAnkd0Yfo4EpP+6XAjlZkod+QXKXM4Tb4PnR34ASMeU6sEjM61Na24S7JD3gpPKataFU/oH3hzXsBdK2ttKYmoqvf61h32IA/3Z5PjCCD9pPLPpAY', + # ssh-keygen -t rsa -b 2048 + 'ssh-rsa' => 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDYtEaWa1mlxaAh9vtiz6RCVKDiJHDY15nsqqWU7F7A1+U1498+sWDyRDkZ8vXWQpzyOMBzBSHIxhsprlKhkjomy8BuJP+bHDBIKx4zgSFDrklrPIf467Iuug8J0qqDLxO4rOOjeAiLEyC0t2ZGnsTEea+rmat0bJ2cv3g5L4gH/OFz2pI4ZLp1HGN83ipl5UH8CjXQKwo3Db1E3WJCqKgszVX0Z4/qjnBRxFMoqky/1mGb/mX1eoT9JyQ8OhU9uENZOShkksSpgUqjlrjpj0Yd14hBlnE3M18pE4ivxjzectA/XRKNZaxOL1YREtU8sXusAwmlEY4aJ64aR0JrXfgx', + # ssh-keygen -t ecdsa -b 256 + 'ecdsa-sha2-nistp256' => 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBO5PfBf0c2jAuqD+Lj3j+SuXOXNT2uqESLVOn5jVQfEF9GzllOw+CMOpUvV1CiOOn+F1ET15vcsfmD7z05WUTA=', + # ssh-keygen -t ecdsa -b 384 + 'ecdsa-sha2-nistp384' => 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJIfxNoVK4FX3RuMlkHOwwxXwAh6Fqx5uAp4ftXrJ+64qYuIzb+/zSAkJV698Sre1b1lb0G4LyDdVAvXwaYK9kN25vy8umV3WdfZeHKXJGCcrplMCbbOERWARlpiPNEblg==', + # ssh-keygen -t ecdsa -b 521 + 'ecdsa-sha2-nistp521' => 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADLK+u12xwB0JOwpmaxYXv8KnPK4p+SE2405qoo+vpAQ569fMwPMgKzltd770amdeuFogw/MJu17PN9LDdrD3o0uwHMjWee6TpHQDkuEetaxiou6K0WAzgbxx9QsY0MsJgXf1BuMLqdK+xT183wOSXwwumv99G7T32dOJZ5tYrH0y4XMw==', + # ssh-keygen -t ed25519 + 'ssh-ed25519' => 'AAAAC3NzaC1lZDI1NTE5AAAAIBWvu7D1KHBPaNXQcEuBsp48+JyPelXAq8ds6K5Du9gd', + }.each_pair do |keytype, keydata| it "should be able to parse a #{keytype} key entry" do - # use some real world examples generated with ssh-keygen - key = case keytype - when 'ssh-dss' # ssh-keygen -t dsa -b 1024 - 'AAAAB3NzaC1kc3MAAACBANGTefWMXS780qLMMgysq3GNMKzg55LXZODif6Tqv1vtTh4Wuk3J5X5u644jTyNdAIn1RiBI9MnwnZMZ6nXKvucMcMQWMibYS9W2MhkRj3oqsLWMMsdGXJL18SWM5A6oC3oIRC4JHJZtkm0OctR2trKxmX+MGhdCd+Xpsh9CNK8XAAAAFQD4olFiwv+QQUFdaZbWUy1CLEG9xQAAAIByCkXKgoriZF8bQ0OX1sKuR69M/6n5ngmQGVBKB7BQkpUjbK/OggB6iJgst5utKkDcaqYRnrTYG9q3jJ/flv7yYePuoSreS0nCMMx9gpEYuq+7Sljg9IecmN/IHrNd9qdYoASy5iuROQMvEZM7KFHA8vBv0tWdBOsp4hZKyiL1DAAAAIEAjkZlOps9L+cD/MTzxDj7toYYypdLOvjlcPBaglkPZoFZ0MAKTI0zXlVX1cWAnkd0Yfo4EpP+6XAjlZkod+QXKXM4Tb4PnR34ASMeU6sEjM61Na24S7JD3gpPKataFU/oH3hzXsBdK2ttKYmoqvf61h32IA/3Z5PjCCD9pPLPpAY' - when 'ssh-rsa' # ssh-keygen -t rsa -b 2048 - 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDYtEaWa1mlxaAh9vtiz6RCVKDiJHDY15nsqqWU7F7A1+U1498+sWDyRDkZ8vXWQpzyOMBzBSHIxhsprlKhkjomy8BuJP+bHDBIKx4zgSFDrklrPIf467Iuug8J0qqDLxO4rOOjeAiLEyC0t2ZGnsTEea+rmat0bJ2cv3g5L4gH/OFz2pI4ZLp1HGN83ipl5UH8CjXQKwo3Db1E3WJCqKgszVX0Z4/qjnBRxFMoqky/1mGb/mX1eoT9JyQ8OhU9uENZOShkksSpgUqjlrjpj0Yd14hBlnE3M18pE4ivxjzectA/XRKNZaxOL1YREtU8sXusAwmlEY4aJ64aR0JrXfgx' - when 'ecdsa-sha2-nistp256' # ssh-keygen -t ecdsa -b 256 - 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBO5PfBf0c2jAuqD+Lj3j+SuXOXNT2uqESLVOn5jVQfEF9GzllOw+CMOpUvV1CiOOn+F1ET15vcsfmD7z05WUTA=' - when 'ecdsa-sha2-nistp384' # ssh-keygen -t ecdsa -b 384 - 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJIfxNoVK4FX3RuMlkHOwwxXwAh6Fqx5uAp4ftXrJ+64qYuIzb+/zSAkJV698Sre1b1lb0G4LyDdVAvXwaYK9kN25vy8umV3WdfZeHKXJGCcrplMCbbOERWARlpiPNEblg==' - when 'ecdsa-sha2-nistp521' #ssh-keygen -t ecdsa -b 521 - 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADLK+u12xwB0JOwpmaxYXv8KnPK4p+SE2405qoo+vpAQ569fMwPMgKzltd770amdeuFogw/MJu17PN9LDdrD3o0uwHMjWee6TpHQDkuEetaxiou6K0WAzgbxx9QsY0MsJgXf1BuMLqdK+xT183wOSXwwumv99G7T32dOJZ5tYrH0y4XMw==' - when 'ssh-ed25519' #ssh-keygen -t ed25519 - 'AAAAC3NzaC1lZDI1NTE5AAAAIBWvu7D1KHBPaNXQcEuBsp48+JyPelXAq8ds6K5Du9gd' - else - pending("No sample key for #{keytype} yet") - end comment = 'sample_key' - record = @provider_class.parse_line("#{keytype} #{key} #{comment}") + record = @provider_class.parse_line("#{keytype} #{keydata} #{comment}") record.should_not be_nil record[:name].should == comment - record[:key].should == key + record[:key].should == keydata record[:type].should == keytype end end end describe provider_class, :unless => Puppet.features.microsoft_windows? do before :each do @resource = Puppet::Type.type(:ssh_authorized_key).new(:name => "foo", :user => "random_bob") @provider = provider_class.new(@resource) provider_class.stubs(:filetype).returns(Puppet::Util::FileType::FileTypeRam) Puppet::Util::SUIDManager.stubs(:asuser).yields provider_class.initvars end describe "when flushing" do before :each do # Stub file and directory operations Dir.stubs(:mkdir) File.stubs(:chmod) File.stubs(:chown) end describe "and both a user and a target have been specified" do before :each do Puppet::Util.stubs(:uid).with("random_bob").returns 12345 @resource[:user] = "random_bob" target = "/tmp/.ssh_dir/place_to_put_authorized_keys" @resource[:target] = target end it "should create the directory" do Puppet::FileSystem.stubs(:exist?).with("/tmp/.ssh_dir").returns false Dir.expects(:mkdir).with("/tmp/.ssh_dir", 0700) @provider.flush end it "should absolutely not chown the directory to the user" do uid = Puppet::Util.uid("random_bob") File.expects(:chown).never @provider.flush end it "should absolutely not chown the key file to the user" do uid = Puppet::Util.uid("random_bob") File.expects(:chown).never @provider.flush end it "should chmod the key file to 0600" do File.expects(:chmod).with(0600, "/tmp/.ssh_dir/place_to_put_authorized_keys") @provider.flush end end describe "and a user has been specified with no target" do before :each do @resource[:user] = "nobody" # # I'd like to use random_bob here and something like # # File.stubs(:expand_path).with("~random_bob/.ssh").returns "/users/r/random_bob/.ssh" # # but mocha objects strenuously to stubbing File.expand_path # so I'm left with using nobody. @dir = File.expand_path("~nobody/.ssh") end it "should create the directory if it doesn't exist" do Puppet::FileSystem.stubs(:exist?).with(@dir).returns false Dir.expects(:mkdir).with(@dir,0700) @provider.flush end it "should not create or chown the directory if it already exist" do Puppet::FileSystem.stubs(:exist?).with(@dir).returns false Dir.expects(:mkdir).never @provider.flush end it "should absolutely not chown the directory to the user if it creates it" do Puppet::FileSystem.stubs(:exist?).with(@dir).returns false Dir.stubs(:mkdir).with(@dir,0700) uid = Puppet::Util.uid("nobody") File.expects(:chown).never @provider.flush end it "should not create or chown the directory if it already exist" do Puppet::FileSystem.stubs(:exist?).with(@dir).returns false Dir.expects(:mkdir).never File.expects(:chown).never @provider.flush end it "should absolutely not chown the key file to the user" do uid = Puppet::Util.uid("nobody") File.expects(:chown).never @provider.flush end it "should chmod the key file to 0600" do File.expects(:chmod).with(0600, File.expand_path("~nobody/.ssh/authorized_keys")) @provider.flush end end describe "and a target has been specified with no user" do it "should raise an error" do @resource = Puppet::Type.type(:ssh_authorized_key).new(:name => "foo", :target => "/tmp/.ssh_dir/place_to_put_authorized_keys") @provider = provider_class.new(@resource) proc { @provider.flush }.should raise_error end end describe "and an invalid user has been specified with no target" do it "should catch an exception and raise a Puppet error" do @resource[:user] = "thisusershouldnotexist" lambda { @provider.flush }.should raise_error(Puppet::Error) end end end end diff --git a/spec/unit/type/ssh_authorized_key_spec.rb b/spec/unit/type/ssh_authorized_key_spec.rb index 70861e61d..fa82941c7 100755 --- a/spec/unit/type/ssh_authorized_key_spec.rb +++ b/spec/unit/type/ssh_authorized_key_spec.rb @@ -1,255 +1,258 @@ #! /usr/bin/env ruby require 'spec_helper' -ssh_authorized_key = Puppet::Type.type(:ssh_authorized_key) -describe ssh_authorized_key, :unless => Puppet.features.microsoft_windows? do +describe Puppet::Type.type(:ssh_authorized_key), :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before do - @class = Puppet::Type.type(:ssh_authorized_key) + 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_class = stub 'provider_class', :name => "fake", :suitable? => true, :supports_parameter? => true - @class.stubs(:defaultprovider).returns(@provider_class) - @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) - @catalog = Puppet::Resource::Catalog.new + provider = stub 'provider', :class => provider_class, :file_path => make_absolute("/tmp/whatever"), :clear => nil + provider_class.stubs(:new).returns(provider) end - it "should have :name be its namevar" do - @class.key_attributes.should == [:name] + 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 "should have a #{param} parameter" do - @class.attrtype(param).should == :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 "should have a #{property} property" do - @class.attrtype(property).should == :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 "should support valid names" do - proc { @class.new(:name => "username", :ensure => :present, :user => "nobody") }.should_not raise_error - proc { @class.new(:name => "username@hostname", :ensure => :present, :user => "nobody") }.should_not raise_error + 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 "should support whitespace" do - proc { @class.new(:name => "my test", :ensure => :present, :user => "nobody") }.should_not raise_error + it "supports whitespace" do + described_class.new(:name => "my test", :ensure => :present, :user => "nobody") end end describe "for ensure" do - it "should support :present" do - proc { @class.new(:name => "whev", :ensure => :present, :user => "nobody") }.should_not raise_error + it "supports :present" do + described_class.new(:name => "whev", :ensure => :present, :user => "nobody") end - it "should support :absent" do - proc { @class.new(:name => "whev", :ensure => :absent, :user => "nobody") }.should_not raise_error + it "supports :absent" do + described_class.new(:name => "whev", :ensure => :absent, :user => "nobody") end - it "should not support other values" do - proc { @class.new(:name => "whev", :ensure => :foo, :user => "nobody") }.should raise_error(Puppet::Error, /Invalid value/) + 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', :'ssh-rsa', :rsa, :dsa, :'ecdsa-sha2-nistp256', :'ecdsa-sha2-nistp384', :'ecdsa-sha2-nistp521'].each do |keytype| - it "should support #{keytype}" do - proc { @class.new(:name => "whev", :type => keytype, :user => "nobody") }.should_not raise_error + [ + :'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 "should alias :rsa to :ssh-rsa" do - key = @class.new(:name => "whev", :type => :rsa, :user => "nobody") - key.should(:type).should == :'ssh-rsa' + 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 "should alias :dsa to :ssh-dss" do - key = @class.new(:name => "whev", :type => :dsa, :user => "nobody") - key.should(:type).should == :'ssh-dss' + 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 "should not support values other than ssh-dss, ssh-rsa, dsa, rsa" do - proc { @class.new(:name => "whev", :type => :something) }.should raise_error(Puppet::Error,/Invalid value/) + 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 "should support a valid key like a 1024 bit rsa key" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCPfzW2ry7XvMc6E5Kj2e5fF/YofhKEvsNMUogR3PGL/HCIcBlsEjKisrY0aYgD8Ikp7ZidpXLbz5dBsmPy8hJiBWs5px9ZQrB/EOQAwXljvj69EyhEoGawmxQMtYw+OAIKHLJYRuk1QiHAMHLp5piqem8ZCV2mLb9AsJ6f7zUVw==')}.should_not raise_error + 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 "should support a valid key like a 4096 bit rsa key" do - proc { @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==')}.should_not raise_error + 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 "should support a valid key like a 1024 bit dsa key" do - proc { @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==')}.should_not raise_error + 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 "should not support whitespaces" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAA FA==')}.should raise_error(Puppet::Error,/Key must not contain whitespace/) + 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 "should support flags as options" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'cert-authority')}.should_not raise_error - proc { @class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'no-port-forwarding')}.should_not raise_error + 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 "should support key-value pairs as options" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'command="command"')}.should_not raise_error + 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 "should support key-value pairs where value consist of multiple items" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'from="*.domain1,host1.domain2"')}.should_not raise_error + 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 "should support environments as options" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'environment="NAME=value"')}.should_not raise_error + 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 "should support multiple options as an array" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ['cert-authority','environment="NAME=value"'])}.should_not raise_error + 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 "should not support a comma separated list" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'cert-authority,no-port-forwarding')}.should raise_error(Puppet::Error, /must be provided as an array/) + 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 "should use :absent as a default value" do - @class.new(:name => "whev", :type => :rsa, :user => "nobody").should(:options).should == [:absent] + 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 = @class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ["a","b","c"]) - resource.property(:options).is_to_s(["a","b","c"]).should == "a,b,c" + 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 = @class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ["a","b","c"]) - resource.property(:options).should_to_s(["a","b","c"]).should == "a,b,c" + 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 "should support present users" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "root") }.should_not raise_error + it "supports present users" do + described_class.new(:name => "whev", :type => :rsa, :user => "root") end - it "should support absent users" do - proc { @class.new(:name => "whev", :type => :rsa, :user => "ihopeimabsent") }.should_not raise_error + it "supports absent users" do + described_class.new(:name => "whev", :type => :rsa, :user => "ihopeimabsent") end end describe "for target" do - it "should support absolute paths" do - proc { @class.new(:name => "whev", :type => :rsa, :target => "/tmp/here") }.should_not raise_error + it "supports absolute paths" do + described_class.new(:name => "whev", :type => :rsa, :target => "/tmp/here") end - it "should use the user's path if not explicitly specified" do - @class.new(:name => "whev", :user => 'root').should(:target).should == File.expand_path("~root/.ssh/authorized_keys") + 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 "should not consider the user's path if explicitly specified" do - @class.new(:name => "whev", :user => 'root', :target => '/tmp/here').should(:target).should == '/tmp/here' + 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 "should inform about an absent user" do + it "informs about an absent user" do Puppet::Log.level = :debug - @class.new(:name => "whev", :user => 'idontexist').should(:target) + described_class.new(:name => "whev", :user => 'idontexist').should(:target) @logs.map(&:message).should include("The required user is not yet present on the system") end end end describe "when neither user nor target is specified" do - it "should raise an error" do - proc do - @class.new( + it "raises an error" do + expect do + described_class.new( :name => "Test", :key => "AAA", :type => "ssh-rsa", :ensure => :present) - end.should raise_error(Puppet::Error,/user.*or.*target.*mandatory/) + end.to raise_error(Puppet::Error,/user.*or.*target.*mandatory/) end end describe "when both target and user are specified" do - it "should use target" do - resource = @class.new( + it "uses target" do + resource = described_class.new( :name => "Test", :user => "root", :target => "/tmp/blah" ) - resource.should(:target).should == "/tmp/blah" + expect(resource.should(:target)).to eq "/tmp/blah" end end describe "when user is specified" do - it "should determine target" do - resource = @class.new( + it "determines target" do + resource = described_class.new( :name => "Test", :user => "root" ) target = File.expand_path("~root/.ssh/authorized_keys") - resource.should(:target).should == target + expect(resource.should(:target)).to eq target end # Bug #2124 - ssh_authorized_key always changes target if target is not defined - it "should not raise spurious change events" do - resource = @class.new(:name => "Test", :user => "root") + it "doesn't raise spurious change events" do + resource = described_class.new(:name => "Test", :user => "root") target = File.expand_path("~root/.ssh/authorized_keys") - resource.property(:target).safe_insync?(target).should == true + expect(resource.property(:target).safe_insync?(target)).to eq true end end describe "when calling validate" do - it "should not crash on a non-existant user" do - resource = @class.new( + it "doesn't crash on a non-existant user" do + resource = described_class.new( :name => "Test", :user => "ihopesuchuserdoesnotexist" ) - proc { resource.validate }.should_not raise_error + resource.validate end end end diff --git a/spec/unit/type/sshkey_spec.rb b/spec/unit/type/sshkey_spec.rb index 37a4865b7..d16e59556 100755 --- a/spec/unit/type/sshkey_spec.rb +++ b/spec/unit/type/sshkey_spec.rb @@ -1,68 +1,77 @@ #! /usr/bin/env ruby require 'spec_helper' -sshkey = Puppet::Type.type(:sshkey) -describe sshkey do - before do - @class = sshkey - end +describe Puppet::Type.type(:sshkey) do - it "should have :name its namevar" do - @class.key_attributes.should == [:name] + 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 "should have a #{param} parameter" do - @class.attrtype(param).should == :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 "should have a #{property} property" do - @class.attrtype(property).should == :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', :'ssh-rsa', :rsa, :dsa, :'ecdsa-sha2-nistp256', :'ecdsa-sha2-nistp384', :'ecdsa-sha2-nistp521'].each do |keytype| - it "should support #{keytype} as a type value" do - proc { @class.new(:name => "foo", :type => keytype) }.should_not raise_error + [ + :'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 "should alias :rsa to :ssh-rsa" do - key = @class.new(:name => "foo", :type => :rsa) - key.should(:type).should == :'ssh-rsa' + 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 "should alias :dsa to :ssh-dss" do - key = @class.new(:name => "foo", :type => :dsa) - key.should(:type).should == :'ssh-dss' + 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 "should not support values other than ssh-dss, ssh-rsa, dsa, rsa for type" do - proc { @class.new(:name => "whev", :type => :'ssh-dsa') }.should raise_error(Puppet::Error) + 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 "should accept one host_alias" do - proc { @class.new(:name => "foo", :host_aliases => 'foo.bar.tld') }.should_not raise_error + it "accepts one host_alias" do + described_class.new(:name => "foo", :host_aliases => 'foo.bar.tld') end - it "should accept multiple host_aliases as an array" do - proc { @class.new(:name => "foo", :host_aliases => ['foo.bar.tld','10.0.9.9']) }.should_not raise_error + 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 "should not accept spaces in any host_alias" do - proc { @class.new(:name => "foo", :host_aliases => ['foo.bar.tld','foo bar']) }.should raise_error(Puppet::Error) + 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 "should not accept aliases in the resourcename" do - proc { @class.new(:name => 'host,host.domain,ip') }.should raise_error(Puppet::Error) + 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