From 374101612c035ec5765c767d234d127d5c7a4870 Mon Sep 17 00:00:00 2001 From: Daniele Sluijters Date: Fri, 17 Jan 2014 09:11:56 +0100 Subject: [PATCH] Add a type and provider to manage apt keys. This commits introduces: * The apt_key type; * The apt_key provider; * Unit tests for the type; * Beaker/acceptance tests for the type/provider. The idea behind apt_key is that apt::key will simply become a wrapper that uses apt_key. Being a native type/provider apt_key is a lot less error prone than the current exec behaviour of apt::key and adds a few nice bonuses like inventory capabilities for mcollective users. --- lib/puppet/provider/apt_key/apt_key.rb | 170 ++++++++++ lib/puppet/type/apt_key.rb | 112 +++++++ spec/acceptance/apt_key_provider_spec.rb | 394 +++++++++++++++++++++++ spec/unit/puppet/type/apt_key_spec.rb | 153 +++++++++ 4 files changed, 829 insertions(+) create mode 100644 lib/puppet/provider/apt_key/apt_key.rb create mode 100644 lib/puppet/type/apt_key.rb create mode 100644 spec/acceptance/apt_key_provider_spec.rb create mode 100644 spec/unit/puppet/type/apt_key_spec.rb diff --git a/lib/puppet/provider/apt_key/apt_key.rb b/lib/puppet/provider/apt_key/apt_key.rb new file mode 100644 index 0000000..7e221dd --- /dev/null +++ b/lib/puppet/provider/apt_key/apt_key.rb @@ -0,0 +1,170 @@ +require 'date' +require 'open-uri' +require 'tempfile' + +Puppet::Type.type(:apt_key).provide(:apt_key) do + + KEY_LINE = { + :date => '[0-9]{4}-[0-9]{2}-[0-9]{2}', + :key_type => '(R|D)', + :key_size => '\d{4}', + :key_id => '[0-9a-fA-F]+', + :expires => 'expire(d|s)', + } + + confine :osfamily => :debian + defaultfor :osfamily => :debian + commands :apt_key => 'apt-key' + + def self.instances + key_array = apt_key('list').split("\n").collect do |line| + line_hash = key_line_hash(line) + next unless line_hash + expired = false + + if line_hash[:key_expiry] + expired = Date.today > Date.parse(line_hash[:key_expiry]) + end + + new( + :name => line_hash[:key_id], + :id => line_hash[:key_id], + :ensure => :present, + :expired => expired, + :expiry => line_hash[:key_expiry], + :size => line_hash[:key_size], + :type => line_hash[:key_type] == 'R' ? :rsa : :dsa, + :created => line_hash[:key_created] + ) + end + key_array.compact! + end + + def self.prefetch(resources) + apt_keys = instances + resources.keys.each do |name| + if provider = apt_keys.find{ |key| key.name == name } + resources[name].provider = provider + end + end + end + + def self.key_line_hash(line) + line_array = line.match(key_line_regexp).to_a + return nil if line_array.length < 5 + + return_hash = { + :key_id => line_array[3], + :key_size => line_array[1], + :key_type => line_array[2], + :key_created => line_array[4], + :key_expiry => nil, + } + + return_hash[:key_expiry] = line_array[7] if line_array.length == 8 + return return_hash + end + + def self.key_line_regexp + # This regexp is trying to match the following output + # pub 4096R/4BD6EC30 2010-07-10 [expires: 2016-07-08] + # pub 1024D/CD2EFD2A 2009-12-15 + regexp = /\A + pub # match only the public key, not signatures + \s+ # bunch of spaces after that + (#{KEY_LINE[:key_size]}) # size of the key, usually a multiple of 1024 + #{KEY_LINE[:key_type]} # type of the key, usually R or D + \/ # separator between key_type and key_id + (#{KEY_LINE[:key_id]}) # hex id of the key + \s+ # bunch of spaces after that + (#{KEY_LINE[:date]}) # date the key was added to the keyring + # following an optional block which indicates if the key has an expiration + # date and if it has expired yet + ( + \s+ # again with thes paces + \[ # we open with a square bracket + #{KEY_LINE[:expires]} # expires or expired + \: # a colon + \s+ # more spaces + (#{KEY_LINE[:date]}) # date indicating key expiry + \] # we close with a square bracket + )? # end of the optional block + \Z/x + regexp + end + + def source_to_file(value) + if URI::parse(value).scheme.nil? + fail("The file #{value} does not exist") unless File.exists?(value) + value + else + begin + key = open(value).read + rescue OpenURI::HTTPError => e + fail("#{e.message} for #{resource[:source]}") + rescue SocketError + fail("could not resolve #{resource[:source]}") + else + tempfile(key) + end + end + end + + def tempfile(content) + file = Tempfile.new('apt_key') + file.write content + file.close + file.path + end + + def exists? + @property_hash[:ensure] == :present + end + + def create + command = [] + if resource[:source].nil? and resource[:content].nil? + # Breaking up the command like this is needed because it blows up + # if --recv-keys isn't the last argument. + command.push('adv', '--keyserver', resource[:server]) + unless resource[:keyserver_options].nil? + command.push('--keyserver-options', resource[:keyserver_options]) + end + command.push('--recv-keys', resource[:id]) + elsif resource[:content] + command.push('add', tempfile(resource[:content])) + elsif resource[:source] + command.push('add', source_to_file(resource[:source])) + # In case we really screwed up, better safe than sorry. + else + fail("an unexpected condition occurred while trying to add the key: #{resource[:id]}") + end + apt_key(command) + @property_hash[:ensure] = :present + end + + def destroy + apt_key('del', resource[:id]) + @property_hash.clear + end + + def read_only(value) + fail('This is a read-only property.') + end + + mk_resource_methods + + # Needed until PUP-1470 is fixed and we can drop support for Puppet versions + # before that. + def expired + @property_hash[:expired] + end + + # Alias the setters of read-only properties + # to the read_only function. + alias :created= :read_only + alias :expired= :read_only + alias :expiry= :read_only + alias :size= :read_only + alias :type= :read_only +end diff --git a/lib/puppet/type/apt_key.rb b/lib/puppet/type/apt_key.rb new file mode 100644 index 0000000..67420de --- /dev/null +++ b/lib/puppet/type/apt_key.rb @@ -0,0 +1,112 @@ +require 'pathname' + +Puppet::Type.newtype(:apt_key) do + + @doc = <<-EOS + This type provides Puppet with the capabilities to manage GPG keys needed + by apt to perform package validation. Apt has it's own GPG keyring that can + be manipulated through the `apt-key` command. + + apt_key { '4BD6EC30': + source => 'http://apt.puppetlabs.com/pubkey.gpg' + } + + **Autorequires**: + + If Puppet is given the location of a key file which looks like an absolute + path this type will autorequire that file. + EOS + + ensurable + + validate do + if self[:content] and self[:source] + fail('The properties content and source are mutually exclusive.') + end + end + + newparam(:id, :namevar => true) do + desc 'The ID of the key you want to manage.' + # GPG key ID's should be either 32-bit (short) or 64-bit (long) key ID's + # and may start with the optional 0x + newvalues(/\A(0x)?[0-9a-fA-F]{8}\Z/, /\A(0x)?[0-9a-fA-F]{16}\Z/) + munge do |value| + if value.start_with?('0x') + id = value.partition('0x').last.upcase + else + id = value.upcase + end + if id.length == 16 + id[8..-1] + else + id + end + end + end + + newparam(:content) do + desc 'The content of, or string representing, a GPG key.' + end + + newparam(:source) do + desc 'Location of a GPG key file, /path/to/file, http:// or https://' + newvalues(/\Ahttps?:\/\//, /\A\/\w+/) + end + + autorequire(:file) do + if self[:source] and Pathname.new(self[:source]).absolute? + self[:source] + end + end + + newparam(:server) do + desc 'The key server to fetch the key from based on the ID.' + defaultto :'keyserver.ubuntu.com' + # Need to validate this, preferably through stdlib is_fqdn + # but still working on getting to that. + end + + newparam(:keyserver_options) do + desc 'Additional options to pass to apt-key\'s --keyserver-options.' + end + + newproperty(:expired) do + desc <<-EOS + Indicates if the key has expired. + + This property is read-only. + EOS + end + + newproperty(:expiry) do + desc <<-EOS + The date the key will expire, or nil if it has no expiry date. + + This property is read-only. + EOS + end + + newproperty(:size) do + desc <<-EOS + The key size, usually a multiple of 1024. + + This property is read-only. + EOS + end + + newproperty(:type) do + desc <<-EOS + The key type, either RSA or DSA. + + This property is read-only. + EOS + end + + newproperty(:created) do + desc <<-EOS + Date the key was created. + + This property is read-only. + EOS + end +end diff --git a/spec/acceptance/apt_key_provider_spec.rb b/spec/acceptance/apt_key_provider_spec.rb new file mode 100644 index 0000000..1c1274f --- /dev/null +++ b/spec/acceptance/apt_key_provider_spec.rb @@ -0,0 +1,394 @@ +require 'spec_helper_acceptance' + +PUPPETLABS_GPG_KEY_ID = '4BD6EC30' +PUPPETLABS_APT_URL = 'apt.puppetlabs.com' +PUPPETLABS_GPG_KEY_FILE = 'pubkey.gpg' + +describe 'apt_key' do + before(:each) do + shell("apt-key del #{PUPPETLABS_GPG_KEY_ID}", + :acceptable_exit_codes => [0,1,2]) + end + + describe 'default options' do + key_versions = { + '32bit key id' => '4BD6EC30', + '64bit key id' => '1054B7A24BD6EC30', + '32bit lowercase key id' => '4bd6ec30', + '64bit lowercase key id' => '1054b7a24bd6ec30', + '0x formatted 32bit key id' => '0x4BD6EC30', + '0x formatted 64bit key id' => '0x1054B7A24BD6EC30', + '0x formatted 32bit lowercase key id' => '0x4bd6ec30', + '0x formatted 64bit lowercase key id' => '0x1054b7a24bd6ec30', + } + + key_versions.each do |key, value| + context "#{key}" do + it 'works' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{value}', + ensure => 'present', + } + EOS + + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero + shell("apt-key list | grep #{PUPPETLABS_GPG_KEY_ID}") + end + end + end + + context 'invalid length key id' do + it 'fails' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '4B7A24BD6EC30', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Valid values match/) + end + end + end + end + + describe 'ensure =>' do + context 'absent' do + it 'is removed' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'absent', + } + EOS + + # Install the key first + shell("apt-key adv --keyserver keyserver.ubuntu.com \ + --recv-keys #{PUPPETLABS_GPG_KEY_ID}") + shell("apt-key list | grep #{PUPPETLABS_GPG_KEY_ID}") + + # Time to remove it using Puppet + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero + + shell("apt-key list | grep #{PUPPETLABS_GPG_KEY_ID}", + :acceptable_exit_codes => [1]) + end + end + end + + describe 'content =>' do + context 'puppetlabs gpg key' do + it 'works' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + content => "-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.12 (GNU/Linux) +Comment: GPGTools - http://gpgtools.org + +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 +=a34C +-----END PGP PUBLIC KEY BLOCK-----", + } + EOS + + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero + shell("apt-key list | grep #{PUPPETLABS_GPG_KEY_ID}") + end + end + + context 'bogus key' do + it 'fails' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + content => 'For posterity: such content, much bogus, wow', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/no valid OpenPGP data found/) + end + end + end + end + + describe 'server =>' do + context 'pgp.mit.edu' do + it 'works' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + server => 'pgp.mit.edu', + } + EOS + + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero + shell("apt-key list | grep #{PUPPETLABS_GPG_KEY_ID}") + end + end + + context 'nonexistant.key.server' do + it 'fails' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + server => 'nonexistant.key.server', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/Host not found/) + end + end + end + end + + describe 'source =>' do + context 'http://' do + it 'works' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + source => 'http://#{PUPPETLABS_APT_URL}/#{PUPPETLABS_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 #{PUPPETLABS_GPG_KEY_ID}") + end + + it 'fails with a 404' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + source => 'http://#{PUPPETLABS_APT_URL}/herpderp.gpg', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/404 Not Found/) + end + end + + it 'fails with a socket error' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + source => 'http://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 + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + source => 'https://#{PUPPETLABS_APT_URL}/#{PUPPETLABS_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 #{PUPPETLABS_GPG_KEY_ID}") + end + + it 'fails with a 404' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '4BD6EC30', + ensure => 'present', + source => 'https://#{PUPPETLABS_APT_URL}/herpderp.gpg', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/404 Not Found/) + end + end + + it 'fails with a socket error' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '4BD6EC30', + ensure => 'present', + source => 'https://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 '/path/that/exists' do + before(:each) do + shell("curl -o /tmp/puppetlabs-pubkey.gpg \ + http://#{PUPPETLABS_APT_URL}/#{PUPPETLABS_GPG_KEY_FILE}") + end + + after(:each) do + shell('rm /tmp/puppetlabs-pubkey.gpg') + end + + it 'works' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '4BD6EC30', + ensure => 'present', + source => '/tmp/puppetlabs-pubkey.gpg', + } + EOS + + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero + shell("apt-key list | grep #{PUPPETLABS_GPG_KEY_ID}") + end + end + + context '/path/that/does/not/exist' do + it 'fails' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + source => '/tmp/totally_bogus.file', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/does not exist/) + end + end + end + + context '/path/that/exists/with/bogus/content' do + before(:each) do + shell('echo "here be dragons" > /tmp/fake-key.gpg') + end + + after(:each) do + shell('rm /tmp/fake-key.gpg') + end + it 'fails' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + source => '/tmp/fake-key.gpg', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/no valid OpenPGP data found/) + end + end + end + end + + describe 'keyserver_options =>' do + context 'debug' do + it 'works' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + keyserver_options => 'debug', + } + EOS + + apply_manifest(pp, :catch_failures => true) + expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero + shell("apt-key list | grep #{PUPPETLABS_GPG_KEY_ID}") + end + + it 'fails on invalid options' do + pp = <<-EOS + apt_key { 'puppetlabs': + id => '#{PUPPETLABS_GPG_KEY_ID}', + ensure => 'present', + keyserver_options => 'this is totally bonkers', + } + EOS + + apply_manifest(pp, :expect_failures => true) do |r| + expect(r.stderr).to match(/--keyserver-options this is totally/) + end + end + end + end +end diff --git a/spec/unit/puppet/type/apt_key_spec.rb b/spec/unit/puppet/type/apt_key_spec.rb new file mode 100644 index 0000000..cfdd16b --- /dev/null +++ b/spec/unit/puppet/type/apt_key_spec.rb @@ -0,0 +1,153 @@ +require 'spec_helper' +require 'puppet' + +describe Puppet::Type::type(:apt_key) do + context 'only namevar 32bit key id' do + let(:resource) { Puppet::Type.type(:apt_key).new( + :id => '4BD6EC30' + )} + it 'id is set' do + resource[:id].should eq '4BD6EC30' + end + + it 'name is set to id' do + resource[:name].should eq '4BD6EC30' + end + + it 'keyserver is default' do + resource[:server].should eq :'keyserver.ubuntu.com' + end + + it 'source is not set' do + resource[:source].should eq nil + end + + it 'content is not set' do + resource[:content].should eq nil + end + end + + context 'with a lowercase 32bit key id' do + let(:resource) { Puppet::Type.type(:apt_key).new( + :id => '4bd6ec30' + )} + it 'id is set' do + resource[:id].should eq '4BD6EC30' + end + end + + context 'with a 64bit key id' do + let(:resource) { Puppet::Type.type(:apt_key).new( + :id => 'FFFFFFFF4BD6EC30' + )} + it 'id is set' do + resource[:id].should eq '4BD6EC30' + end + end + + context 'with a 0x formatted key id' do + let(:resource) { Puppet::Type.type(:apt_key).new( + :id => '0x4BD6EC30' + )} + it 'id is set' do + resource[:id].should eq '4BD6EC30' + end + end + + context 'with a 0x formatted lowercase key id' do + let(:resource) { Puppet::Type.type(:apt_key).new( + :id => '0x4bd6ec30' + )} + it 'id is set' do + resource[:id].should eq '4BD6EC30' + end + end + + context 'with a 0x formatted 64bit key id' do + let(:resource) { Puppet::Type.type(:apt_key).new( + :id => '0xFFFFFFFF4BD6EC30' + )} + it 'id is set' do + resource[:id].should eq '4BD6EC30' + end + end + + context 'with source' do + let(:resource) { Puppet::Type.type(:apt_key).new( + :id => '4BD6EC30', + :source => 'http://apt.puppetlabs.com/pubkey.gpg' + )} + + it 'source is set to the URL' do + resource[:source].should eq 'http://apt.puppetlabs.com/pubkey.gpg' + end + end + + context 'with content' do + let(:resource) { Puppet::Type.type(:apt_key).new( + :id => '4BD6EC30', + :content => 'http://apt.puppetlabs.com/pubkey.gpg' + )} + + it 'content is set to the string' do + resource[:content].should eq 'http://apt.puppetlabs.com/pubkey.gpg' + end + end + + context 'with keyserver' do + let(:resource) { Puppet::Type.type(:apt_key).new( + :id => '4BD6EC30', + :server => 'http://keyring.debian.org' + )} + + it 'keyserver is set to Debian' do + resource[:server].should eq 'http://keyring.debian.org' + end + end + + context 'validation' do + it 'raises an error if content and source are set' do + expect { Puppet::Type.type(:apt_key).new( + :id => '4BD6EC30', + :source => 'http://apt.puppetlabs.com/pubkey.gpg', + :content => 'Completely invalid as a GPG key' + )}.to raise_error(/content and source are mutually exclusive/) + end + + it 'raises an error if a weird length key is used' do + expect { Puppet::Type.type(:apt_key).new( + :id => 'F4BD6EC30', + :source => 'http://apt.puppetlabs.com/pubkey.gpg', + :content => 'Completely invalid as a GPG key' + )}.to raise_error(/Valid values match/) + end + + it 'raises an error when an invalid URI scheme is used in source' do + expect { Puppet::Type.type(:apt_key).new( + :id => '4BD6EC30', + :source => 'hkp://pgp.mit.edu' + )}.to raise_error(/Valid values match/) + end + + it 'allows the http URI scheme in source' do + expect { Puppet::Type.type(:apt_key).new( + :id => '4BD6EC30', + :source => 'http://pgp.mit.edu' + )}.to_not raise_error + end + + it 'allows the https URI scheme in source' do + expect { Puppet::Type.type(:apt_key).new( + :id => '4BD6EC30', + :source => 'https://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', + :source => '/path/to/a/file' + )}.to_not raise_error + end + end +end -- 2.32.3