Commit aac78750 authored by Stefan Schulte's avatar Stefan Schulte
Browse files

Add rpm_key type

The new type can be used to describe an imported gpg key
parent ba5f26cb
......@@ -13,4 +13,27 @@ New functions
New custom types
----------------
(currently none)
### rpmkey
A package maintainer can sign his RPM packages with a gpg key. The signed RPM package can later be
verified by the rpm utility if the corresponding public key of the package maintainer is present.
RPM has its own keyring and commands to import and remove keys.
A key can be imported with `rpm --import` and will then present itself as an installed package of the form
`gpgkey-#{keyid}-#{signature_date}`. A key can be removed by removing the package with `rpm -e`.
The new puppet `rpmkey` type treats a single key as resource so you can e.g. specify
rpmkey { '0608B895':
ensure => present,
source => 'https://fedoraproject.org/static/0608B895.txt',
}
or - if you want to make sure a key is deleted - specify
rpmkey { '0608B895':
ensure => absent,
}
The `name` of the `rpmkey` resource has to be the keyID of the gpg key.
Puppet::Type.type(:rpmkey).provide(:rpm) do
commands :rpm => 'rpm'
def self.instances
keys = []
rpm('-q','gpg-pubkey').each_line do |line|
if match = /^gpg-pubkey-([0-9a-f]*)-[0-9a-f]*/.match(line)
keys << new(:name => match.captures[0].upcase, :ensure => :present)
else
warning "Unexpected rpm output #{line.expect}. Ignoring this line."
end
end
keys
end
def self.prefetch(resources)
instances.each do |prov|
if resource = resources[prov.name]
resource.provider = prov
end
end
end
def create
raise Puppet::Error, "Cannot add key without a source" unless @resource[:source]
rpm('--import', @resource[:source])
end
def exists?
get(:ensure) != :absent
end
def destroy
rpm('-e', '--allmatches', "gpg-pubkey-#{@resource[:name].downcase}")
end
end
Puppet::Type.newtype(:rpmkey) do
@doc = "Define public GPG keys that should be part of the rpm
keyring."
newparam(:name) do
desc "The name of the key. This is the keyID (in hex) in
uppercase."
isnamevar
validate do |value|
raise Puppet::Error, "Name must not be empty" if value.empty?
unless value =~ /^[0-9A-F]*$/
raise Puppet::Error, "Invalid key #{value}. The key has be a valid keyID (in hex) in uppercase."
end
end
end
ensurable
newparam(:source) do
desc "The source of the public key if the key is not already imported."
end
end
gpg-pubkey-db42a60e-37ea5438
gpg-pubkey-4f2a6fd2-3f9d9d3b
gpg-pubkey-23a254d4-41ddbc46
#!/usr/bin/env rspec
require 'spec_helper'
describe Puppet::Type.type(:rpmkey).provider(:rpm) do
before :each do
Puppet::Type.type(:rpmkey).stubs(:defaultprovider).returns described_class
FileTest.stubs(:file?).with('/usr/bin/rpm').returns true
FileTest.stubs(:executable?).with('/usr/bin/rpm').returns true
end
describe ".instances" do
it "should have an instances method" do
described_class.should respond_to :instances
end
it "should get installed rpm keys by running rpm -q" do
described_class.expects(:rpm).with('-q', 'gpg-pubkey').returns File.read(my_fixture('rpm_q'))
described_class.instances.map(&:name).should == [
'DB42A60E',
'4F2A6FD2',
'23A254D4'
]
end
end
describe "#exists?" do
it "should return true if the resource is present" do
provider = described_class.new(:name => 'DB42A60E', :ensure => :present)
provider.should be_exists
end
it "should return false if the resource is absent" do
provider = described_class.new(:name => 'DB42A60E', :ensure => :absent)
provider.should_not be_exists
end
end
describe "#create" do
it "should import the key" do
provider = described_class.new(Puppet::Type.type(:rpmkey).new(
:name => 'DB42A60E',
:source => 'http://example.com/foo',
:ensure => :present
))
provider.expects(:rpm).with('--import', 'http://example.com/foo')
provider.create
end
it "should raise an error if no source is specified" do
provider = described_class.new(Puppet::Type.type(:rpmkey).new(
:name => 'DB42A60E',
:ensure => :present
))
expect { provider.create }.to raise_error(Puppet::Error, /Cannot add key without a source/)
end
end
describe "#destroy" do
it "should remove the key" do
provider = described_class.new(Puppet::Type.type(:rpmkey).new(
:name => 'DB42A60E',
:source => 'http://example.com/foo',
:ensure => :absent
))
provider.expects(:rpm).with('-e', '--allmatches', 'gpg-pubkey-db42a60e')
provider.destroy
end
it "should not complain about a missing source" do
provider = described_class.new(Puppet::Type.type(:rpmkey).new(
:name => 'DB42A60E',
:ensure => :absent
))
provider.expects(:rpm).with('-e', '--allmatches', 'gpg-pubkey-db42a60e')
expect { provider.destroy }.to_not raise_error
end
end
end
#!/usr/bin/env rspec
require 'spec_helper'
describe Puppet::Type.type(:rpmkey) do
before :each do
@provider_class = described_class.provide(:fake) { mk_resource_methods }
@provider_class.stubs(:suitable?).returns true
described_class.stubs(:defaultprovider).returns @provider_class
end
it "should have :name as its keyattribute" do
described_class.key_attributes.should == [:name]
end
describe "when validating attributes" do
[:name, :source, :provider].each do |param|
it "should have a #{param} parameter" do
described_class.attrtype(param).should == :param
end
end
[:ensure].each do |property|
it "should have a #{property} property" do
described_class.attrtype(property).should == :property
end
end
end
describe "when validating value" do
describe "for ensure" do
it "should support present" do
expect { described_class.new(:name => 'DB42A60E', :ensure => :present) }.to_not raise_error
end
it "should support absent" do
expect { described_class.new(:name => 'DB42A60E', :ensure => :absent) }.to_not raise_error
end
it "should not support other values" do
expect { described_class.new(:name => 'DB42A60E', :ensure => :foo) }.to raise_error(Puppet::Error, /Invalid value/)
end
end
describe "for name" do
it "should support a valid name" do
expect { described_class.new(:name => '01230123', :ensure => :present) }.to_not raise_error
expect { described_class.new(:name => 'DB42A60E', :ensure => :present) }.to_not raise_error
expect { described_class.new(:name => 'ABCDEFAB', :ensure => :present) }.to_not raise_error
end
it "should not support an empty name" do
expect { described_class.new(:name => '', :ensure => :present) }.to raise_error(Puppet::Error, /Name.*empty/)
end
it "should not support invalid names" do
expect { described_class.new(:name => 'DB42A60G', :ensure => :present) }.to raise_error(Puppet:: Error, /Invalid key/)
expect { described_class.new(:name => 'GB42A60E', :ensure => :present) }.to raise_error(Puppet:: Error, /Invalid key/)
expect { described_class.new(:name => 'DB42A60E-DB42A60E', :ensure => :present) }.to raise_error(Puppet:: Error, /Invalid key/)
expect { described_class.new(:name => ' DB42A60E', :ensure => :present) }.to raise_error(Puppet:: Error, /Invalid key/)
expect { described_class.new(:name => 'DB42A60E ', :ensure => :present) }.to raise_error(Puppet:: Error, /Invalid key/)
expect { described_class.new(:name => 'DB42 A60E', :ensure => :present) }.to raise_error(Puppet:: Error, /Invalid key/)
expect { described_class.new(:name => 'DB42a60E', :ensure => :present) }.to raise_error(Puppet:: Error, /Invalid key/)
expect { described_class.new(:name => 'dB42A60E', :ensure => :present) }.to raise_error(Puppet:: Error, /Invalid key/)
expect { described_class.new(:name => 'DB42A60e', :ensure => :present) }.to raise_error(Puppet:: Error, /Invalid key/)
end
end
describe "for source" do
it "should support a local filename" do
expect { described_class.new(:name => 'DB42A60E', :source => '/tmp/foo', :ensure => :present) }.to_not raise_error
end
it "should support a http link" do
expect { described_class.new(:name => 'DB42A60E', :source => 'http://example.com/foo', :ensure => :present) }.to_not raise_error
end
end
end
end
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment