diff --git a/lib/puppet/provider/ssh_authorized_key/parsed.rb b/lib/puppet/provider/ssh_authorized_key/parsed.rb index 77af58ef5..5604ba32a 100644 --- a/lib/puppet/provider/ssh_authorized_key/parsed.rb +++ b/lib/puppet/provider/ssh_authorized_key/parsed.rb @@ -1,95 +1,135 @@ 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 => /^#/ text_line :blank, :match => /^\s+/ record_line :parsed, :fields => %w{options type key name}, :optional => %w{options}, :rts => /^\s+/, :match => /^(?:(.+) )?(ssh-dss|ssh-rsa) ([^ ]+)(?: (.+))?$/, :post_parse => proc { |record| if record[:options].nil? record[:options] = [:absent] else record[:options] = Puppet::Type::Ssh_authorized_key::ProviderParsed.parse_options(record[:options]) end }, :pre_gen => proc { |record| if record[:options].include?(:absent) record[:options] = "" else record[:options] = record[:options].join(',') end } record_line :key_v1, :fields => %w{options bits exponent modulus name}, :optional => %w{options}, :rts => /^\s+/, :match => /^(?:(.+) )?(\d+) (\d+) (\d+)(?: (.+))?$/ def prefetch # This was done in the type class but path expansion was failing for # not yet existing users, the only workaround I found was to move that # in the provider. - if user = @resource.should(:user) - target = File.expand_path("~%s/.ssh/authorized_keys" % user) - @property_hash[:target] = target - @resource[:target] = target - end + @resource[:target] = target super end + def target + if user + File.expand_path("~%s/.ssh/authorized_keys" % user) + elsif target = @resource.should(:target) + target + end + end + + def user + @resource.should(:user) + end + + def dir_perm + # Determine correct permission for created directory and file + # we can afford more restrictive permissions when the user is known + if target + if user + 0700 + else + 0755 + end + end + end + + def file_perm + if target + if user + 0600 + else + 0644 + end + end + end + def flush # As path expansion had to be moved in the provider, we cannot generate new file # resources and thus have to chown and chmod here. It smells hackish. - + # Create target's parent directory if nonexistant - if target = @property_hash[:target] - dir = File.dirname(@property_hash[:target]) + if target + dir = File.dirname(target) if not File.exist? dir Puppet.debug("Creating directory %s which did not exist" % dir) - Dir.mkdir(dir, 0700) + Dir.mkdir(dir, dir_perm) end end # Generate the file super # Ensure correct permissions - if target and user = @property_hash[:user] - File.chown(Puppet::Util.uid(user), nil, dir) - File.chown(Puppet::Util.uid(user), nil, @property_hash[:target]) + if target and user + uid = Puppet::Util.uid(user) + + if uid + File.chown(uid, nil, dir) + File.chown(uid, nil, target) + else + raise Puppet::Error, "Specified user does not exist" + end + end + + if target + 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/spec/unit/provider/ssh_authorized_key/parsed.rb b/spec/unit/provider/ssh_authorized_key/parsed.rb index 21f30f97e..73d623573 100755 --- a/spec/unit/provider/ssh_authorized_key/parsed.rb +++ b/spec/unit/provider/ssh_authorized_key/parsed.rb @@ -1,102 +1,166 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppettest' require 'puppettest/support/utils' require 'puppettest/fileparsing' provider_class = Puppet::Type.type(:ssh_authorized_key).provider(:parsed) describe provider_class do include PuppetTest include PuppetTest::FileParsing before :each do @sshauthkey_class = Puppet.type(:ssh_authorized_key) @provider = @sshauthkey_class.provider(:parsed) end after :each do @provider.initvars end def mkkey(args) fakeresource = fakeresource(:ssh_authorized_key, args[:name]) key = @provider.new(fakeresource) args.each do |p,v| key.send(p.to_s + "=", v) end return key end def genkey(key) @provider.filetype = :ram file = @provider.default_target key.flush text = @provider.target_object(file).read return text end it "should be able to parse each example" do fakedata("data/providers/ssh_authorized_key/parsed").each { |file| puts "Parsing %s" % file fakedataparse(file) } end 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 a 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 prefetch ~user/.ssh/authorized_keys when user is given" do key = Puppet::Type.type(:ssh_authorized_key).create( :name => "Test", :key => "AA", :type => "rsa", :ensure => :present, :user => "root") prov = @provider.new key prov.prefetch prov.target.should == File.expand_path("~root/.ssh/authorized_keys") end it "should create destination dir" do # No idea how to test the flush method end it "should set correct default permissions" do # No idea how to test the flush method end it "'s parse_options method should be able to parse options containing commas" do options = %w{from="host1.reductlivelabs.com,host.reductivelabs.com" command="/usr/local/bin/run" ssh-pty} optionstr = options.join(", ") @provider.parse_options(optionstr).should == options end end + +describe provider_class do + before :each do + @resource = stub("resource", :name => "foo") + @resource.stubs(:[]).returns "foo" + @provider = provider_class.new(@resource) + 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 a user has been specified" do + before :each do + @resource.stubs(:should).with(:user).returns "nobody" + @resource.stubs(:should).with(:target).returns nil + end + + it "should create the directory" do + Dir.expects(:mkdir).with(File.expand_path("~nobody/.ssh"), 0700) + @provider.flush + end + + it "should chown the directory to the user" do + uid = Puppet::Util.uid("nobody") + File.expects(:chown).with(uid, nil, File.expand_path("~nobody/.ssh")) + @provider.flush + end + + it "should chown the key file to the user" do + uid = Puppet::Util.uid("nobody") + File.expects(:chown).with(uid, nil, File.expand_path("~nobody/.ssh/authorized_keys")) + @provider.flush + end + + it "should chmod the key file to 0600" do + File.chmod(0600, File.expand_path("~nobody/.ssh/authorized_keys")) + @provider.flush + end + end + + describe "and a target has been specified" do + before :each do + @resource.stubs(:should).with(:user).returns nil + @resource.stubs(:should).with(:target).returns "/tmp/.ssh/authorized_keys" + end + + it "should make the directory" do + Dir.expects(:mkdir).with("/tmp/.ssh", 0755) + @provider.flush + end + + it "should chmod the key file to 0644" do + File.expects(:chmod).with(0644, "/tmp/.ssh/authorized_keys") + @provider.flush + end + end + + end +end