From c3b3f5bb42a754c5fdda42a9af74b1227ad2a9f5 Mon Sep 17 00:00:00 2001 From: Daniele Sluijters Date: Thu, 20 Feb 2014 10:34:22 +0100 Subject: [PATCH] apt_key: Support fetching keys over FTP. --- lib/puppet/provider/apt_key/apt_key.rb | 12 ++++- lib/puppet/type/apt_key.rb | 4 +- lib/puppet_x/apt_key/patch_openuri.rb | 63 ++++++++++++++++++++++++ spec/acceptance/apt_key_provider_spec.rb | 52 +++++++++++++++++++ spec/unit/puppet/type/apt_key_spec.rb | 7 +++ 5 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 lib/puppet_x/apt_key/patch_openuri.rb diff --git a/lib/puppet/provider/apt_key/apt_key.rb b/lib/puppet/provider/apt_key/apt_key.rb index 7e221dd..51791d0 100644 --- a/lib/puppet/provider/apt_key/apt_key.rb +++ b/lib/puppet/provider/apt_key/apt_key.rb @@ -1,7 +1,15 @@ require 'date' require 'open-uri' +require 'net/ftp' require 'tempfile' +if RUBY_VERSION == '1.8.7' + # Mothers cry, puppies die and Ruby 1.8.7's open-uri needs to be + # monkeypatched to support passing in :ftp_passive_mode. + require 'puppet_x/apt_key/patch_openuri' + OpenURI::Options.merge!({:ftp_active_mode => false,}) +end + Puppet::Type.type(:apt_key).provide(:apt_key) do KEY_LINE = { @@ -99,8 +107,8 @@ Puppet::Type.type(:apt_key).provide(:apt_key) do value else begin - key = open(value).read - rescue OpenURI::HTTPError => e + key = open(value, :ftp_active_mode => false).read + rescue OpenURI::HTTPError, Net::FTPPermError => e fail("#{e.message} for #{resource[:source]}") rescue SocketError fail("could not resolve #{resource[:source]}") diff --git a/lib/puppet/type/apt_key.rb b/lib/puppet/type/apt_key.rb index 67420de..fa7b0c6 100644 --- a/lib/puppet/type/apt_key.rb +++ b/lib/puppet/type/apt_key.rb @@ -49,8 +49,8 @@ Puppet::Type.newtype(:apt_key) do end newparam(:source) do - desc 'Location of a GPG key file, /path/to/file, http:// or https://' - newvalues(/\Ahttps?:\/\//, /\A\/\w+/) + desc 'Location of a GPG key file, /path/to/file, ftp://, http:// or https://' + newvalues(/\Ahttps?:\/\//, /\Aftp:\/\//, /\A\/\w+/) end autorequire(:file) do diff --git a/lib/puppet_x/apt_key/patch_openuri.rb b/lib/puppet_x/apt_key/patch_openuri.rb new file mode 100644 index 0000000..722c7bd --- /dev/null +++ b/lib/puppet_x/apt_key/patch_openuri.rb @@ -0,0 +1,63 @@ +require 'uri' +require 'stringio' +require 'time' + +module URI + class FTP + def buffer_open(buf, proxy, options) # :nodoc: + if proxy + OpenURI.open_http(buf, self, proxy, options) + return + end + require 'net/ftp' + + directories = self.path.split(%r{/}, -1) + directories.shift if directories[0] == '' # strip a field before leading slash + directories.each {|d| + d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") } + } + unless filename = directories.pop + raise ArgumentError, "no filename: #{self.inspect}" + end + directories.each {|d| + if /[\r\n]/ =~ d + raise ArgumentError, "invalid directory: #{d.inspect}" + end + } + if /[\r\n]/ =~ filename + raise ArgumentError, "invalid filename: #{filename.inspect}" + end + typecode = self.typecode + if typecode && /\A[aid]\z/ !~ typecode + raise ArgumentError, "invalid typecode: #{typecode.inspect}" + end + + # The access sequence is defined by RFC 1738 + ftp = Net::FTP.open(self.host) + ftp.passive = true if !options[:ftp_active_mode] + # todo: extract user/passwd from .netrc. + user = 'anonymous' + passwd = nil + user, passwd = self.userinfo.split(/:/) if self.userinfo + ftp.login(user, passwd) + directories.each {|cwd| + ftp.voidcmd("CWD #{cwd}") + } + if typecode + # xxx: typecode D is not handled. + ftp.voidcmd("TYPE #{typecode.upcase}") + end + if options[:content_length_proc] + options[:content_length_proc].call(ftp.size(filename)) + end + ftp.retrbinary("RETR #{filename}", 4096) { |str| + buf << str + options[:progress_proc].call(buf.size) if options[:progress_proc] + } + ftp.close + buf.io.rewind + end + + include OpenURI::OpenRead + end +end diff --git a/spec/acceptance/apt_key_provider_spec.rb b/spec/acceptance/apt_key_provider_spec.rb index 1c1274f..1d39a97 100644 --- a/spec/acceptance/apt_key_provider_spec.rb +++ b/spec/acceptance/apt_key_provider_spec.rb @@ -3,6 +3,9 @@ require 'spec_helper_acceptance' PUPPETLABS_GPG_KEY_ID = '4BD6EC30' PUPPETLABS_APT_URL = 'apt.puppetlabs.com' PUPPETLABS_GPG_KEY_FILE = 'pubkey.gpg' +CENTOS_GPG_KEY_ID = 'C105B9DE' +CENTOS_REPO_URL = 'ftp.cvut.cz/centos' +CENTOS_GPG_KEY_FILE = 'RPM-GPG-KEY-CentOS-6' describe 'apt_key' do before(:each) do @@ -251,6 +254,55 @@ ugVIB2pi+8u84f+an4Hml4xlyijgYu05pqNvnLRyJDLd61hviLC8GYU= end end + context 'ftp://' do + before(:each) do + shell("apt-key del #{CENTOS_GPG_KEY_ID}", + :acceptable_exit_codes => [0,1,2]) + end + + it 'works' do + pp = <<-EOS + apt_key { 'CentOS 6': + id => '#{CENTOS_GPG_KEY_ID}', + ensure => 'present', + source => 'ftp://#{CENTOS_REPO_URL}/#{CENTOS_GPG_KEY_FILE}', + } + EOS + + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero + shell("apt-key list | grep #{CENTOS_GPG_KEY_ID}") + end + + it 'fails with a 550' do + pp = <<-EOS + apt_key { 'CentOS 6': + id => '#{CENTOS_GPG_KEY_ID}', + ensure => 'present', + source => 'ftp://#{CENTOS_REPO_URL}/herpderp.gpg', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/550 Failed to open/) + end + end + + it 'fails with a socket error' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + source => 'ftp://apt.puppetlabss.com/herpderp.gpg', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/could not resolve/) + end + end + end + context 'https://' do it 'works' do pp = <<-EOS diff --git a/spec/unit/puppet/type/apt_key_spec.rb b/spec/unit/puppet/type/apt_key_spec.rb index cfdd16b..c29f82b 100644 --- a/spec/unit/puppet/type/apt_key_spec.rb +++ b/spec/unit/puppet/type/apt_key_spec.rb @@ -143,6 +143,13 @@ describe Puppet::Type::type(:apt_key) do )}.to_not raise_error end + it 'allows the ftp URI scheme in source' do + expect { Puppet::Type.type(:apt_key).new( + :id => '4BD6EC30', + :source => 'ftp://pgp.mit.edu' + )}.to_not raise_error + end + it 'allows an absolute path in source' do expect { Puppet::Type.type(:apt_key).new( :id => '4BD6EC30', -- 2.32.3