-
Notifications
You must be signed in to change notification settings - Fork 465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
apt_key type/provider #212
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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+/) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because ruby: http://stackoverflow.com/questions/577653/difference-between-a-z-and-in-ruby-regular-expressions Someone pointed me at that, apparently there's a "feature" or two in Ruby's regex engine and it isn't a 100% PCRE compatible either 😒. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bah. |
||
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' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Debian people might not be happy with such a default ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very true, however it's the current default on |
||
# 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. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This space left blank intentionally. |
||
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. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This space left blank intentionally. |
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about
0X
? is that entirely invalid?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0X
is entirely invalid,0x
is the hex denomination,0X
is just madness.