From ce4faae2d37f9a76afeb85e94161e4348d28876f Mon Sep 17 00:00:00 2001 From: Uwe Stuehler Date: Fri, 20 Sep 2013 12:25:09 +0200 Subject: [PATCH 1/4] Fix typo in gpgme package ensure --- manifests/init.pp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/init.pp b/manifests/init.pp index 4847fc2..44578a1 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -36,7 +36,7 @@ } package { 'gpgme': - ensure => 'instaled', + ensure => 'installed', provider => $gpgme_provider, require => Package['gnupg'] } From 5ba3a553e476d5eea233233f11c3364a38ff4447 Mon Sep 17 00:00:00 2001 From: Uwe Stuehler Date: Fri, 20 Sep 2013 12:27:33 +0200 Subject: [PATCH 2/4] Allow empty passphrase again --- lib/puppet/provider/gpgkey/gpgme.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/puppet/provider/gpgkey/gpgme.rb b/lib/puppet/provider/gpgkey/gpgme.rb index 4e50828..dfb238e 100644 --- a/lib/puppet/provider/gpgkey/gpgme.rb +++ b/lib/puppet/provider/gpgkey/gpgme.rb @@ -15,7 +15,11 @@ def create keydata += "Name-Comment: " +keyname()+"\n" keydata += "Name-Email: " +@resource.value(:email)+"\n" keydata += "Expire-Date: " +@resource.value(:expire)+"\n" - keydata += "Passphrase: " +@resource.value(:password)+"\n" + # This parameter requires a value when present. Default is to + # not use a passphrase. + unless @resource.value(:password).empty? + keydata += "Passphrase: " +@resource.value(:password)+"\n" + end keydata += "\n" ctx.genkey(keydata, nil, nil) From e89b31f444bdde2360085343104372c889f30036 Mon Sep 17 00:00:00 2001 From: Uwe Stuehler Date: Fri, 20 Sep 2013 12:33:49 +0200 Subject: [PATCH 3/4] Plug a GPGME::Ctx leak with gpgme gem >= 2.0.0 The latest version of gpgme on rubyforge is 2.0.2 and it supports passing a block to GPGME::Ctx.new. I believe that up to now, this provider has leaked Ctx instances because Ctx#release was never called. That's now ensured when Ctx.new is given a block. --- lib/puppet/provider/gpgkey/gpgme.rb | 37 ++++++++++++++++------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/puppet/provider/gpgkey/gpgme.rb b/lib/puppet/provider/gpgkey/gpgme.rb index dfb238e..9014b87 100644 --- a/lib/puppet/provider/gpgkey/gpgme.rb +++ b/lib/puppet/provider/gpgkey/gpgme.rb @@ -1,28 +1,31 @@ Puppet::Type.type(:gpgkey).provide(:gpgme) do + # This provider uses the new API of the gpgme-2.0.0 gem. require 'gpgme' + def exists? ! GPGME::Key.find(:secret, keyname()).empty? end def create - ctx = GPGME::Ctx.new - keydata = "\n" - keydata += "Key-Type: " +@resource.value(:keytype)+"\n" - keydata += "Key-Length: " +@resource.value(:keylength)+"\n" - keydata += "Subkey-Type: " +@resource.value(:subkeytype)+"\n" - keydata += "Subkey-Length: " +@resource.value(:subkeylength)+"\n" - keydata += "Name-Real: " +@resource.value(:name)+"\n" - keydata += "Name-Comment: " +keyname()+"\n" - keydata += "Name-Email: " +@resource.value(:email)+"\n" - keydata += "Expire-Date: " +@resource.value(:expire)+"\n" - # This parameter requires a value when present. Default is to - # not use a passphrase. - unless @resource.value(:password).empty? - keydata += "Passphrase: " +@resource.value(:password)+"\n" - end - keydata += "\n" + GPGME::Ctx.new do |ctx| + keydata = "\n" + keydata += "Key-Type: " +@resource.value(:keytype)+"\n" + keydata += "Key-Length: " +@resource.value(:keylength)+"\n" + keydata += "Subkey-Type: " +@resource.value(:subkeytype)+"\n" + keydata += "Subkey-Length: " +@resource.value(:subkeylength)+"\n" + keydata += "Name-Real: " +@resource.value(:name)+"\n" + keydata += "Name-Comment: " +keyname()+"\n" + keydata += "Name-Email: " +@resource.value(:email)+"\n" + keydata += "Expire-Date: " +@resource.value(:expire)+"\n" + # This parameter requires a value when present. Default is to + # not use a passphrase. + unless @resource.value(:password).empty? + keydata += "Passphrase: " +@resource.value(:password)+"\n" + end + keydata += "\n" - ctx.genkey(keydata, nil, nil) + ctx.genkey(keydata, nil, nil) + end end def destroy From a0d3e108f84e7fdbaae3969cb3032b028fabb078 Mon Sep 17 00:00:00 2001 From: Uwe Stuehler Date: Fri, 20 Sep 2013 18:41:52 +0200 Subject: [PATCH 4/4] Support `user' and `gnupghome' parameters Ther `user' parameter can be set to manage the keys of any existing user on the system. It defaults to the user running puppet. If `gnupghome` is set, the environment variable GNUPGHOME will be set during all calls to GnuPG and override the default directory ~/.gnupg. Implementation detail: We use Process.fork and SUIDManager.change_privileges instead of SUIDManager.asuser since GnuPG will abort when it detects that it is running with effective UID != real UID. --- lib/puppet/provider/gpgkey/gpgme.rb | 82 ++++++++++++++++++++++------- lib/puppet/type/gpgkey.rb | 19 +++++++ 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/lib/puppet/provider/gpgkey/gpgme.rb b/lib/puppet/provider/gpgkey/gpgme.rb index 9014b87..4b71da4 100644 --- a/lib/puppet/provider/gpgkey/gpgme.rb +++ b/lib/puppet/provider/gpgkey/gpgme.rb @@ -3,41 +3,83 @@ require 'gpgme' def exists? - ! GPGME::Key.find(:secret, keyname()).empty? + run_as_user do + Process.exit!(GPGME::Key.find(:secret, keyname()).empty? ? 1 : 0) + end == 0 end def create - GPGME::Ctx.new do |ctx| - keydata = "\n" - keydata += "Key-Type: " +@resource.value(:keytype)+"\n" - keydata += "Key-Length: " +@resource.value(:keylength)+"\n" - keydata += "Subkey-Type: " +@resource.value(:subkeytype)+"\n" - keydata += "Subkey-Length: " +@resource.value(:subkeylength)+"\n" - keydata += "Name-Real: " +@resource.value(:name)+"\n" - keydata += "Name-Comment: " +keyname()+"\n" - keydata += "Name-Email: " +@resource.value(:email)+"\n" - keydata += "Expire-Date: " +@resource.value(:expire)+"\n" - # This parameter requires a value when present. Default is to - # not use a passphrase. - unless @resource.value(:password).empty? - keydata += "Passphrase: " +@resource.value(:password)+"\n" - end - keydata += "\n" + run_as_user do + GPGME::Ctx.new do |ctx| + keydata = "\n" + keydata += "Key-Type: " +@resource.value(:keytype)+"\n" + keydata += "Key-Length: " +@resource.value(:keylength)+"\n" + keydata += "Subkey-Type: " +@resource.value(:subkeytype)+"\n" + keydata += "Subkey-Length: " +@resource.value(:subkeylength)+"\n" + keydata += "Name-Real: " +@resource.value(:name)+"\n" + keydata += "Name-Comment: " +keyname()+"\n" + keydata += "Name-Email: " +@resource.value(:email)+"\n" + keydata += "Expire-Date: " +@resource.value(:expire)+"\n" + # This parameter requires a value when present. Default is to + # not use a passphrase. + unless @resource.value(:password).empty? + keydata += "Passphrase: " +@resource.value(:password)+"\n" + end + keydata += "\n" - ctx.genkey(keydata, nil, nil) + ctx.genkey(keydata, nil, nil) + end end end def destroy - GPGME::Key.find(:secret, keyname()).each do |key| - key.delete!(true) + run_as_user do + GPGME::Key.find(:secret, keyname()).each do |key| + key.delete!(true) + end end end private + def keyname keyname = 'puppet#' + @resource.value(:name) + '#' return keyname end + def run_as_user(&block) + if (pid = Process.fork).nil? + Puppet::Util::SUIDManager.change_privileges(@resource.value(:user), nil, true) + with_env(&block) + Process.exit! + else + Process.waitpid(pid) + $?.exitstatus + end + end + + def with_env(&block) + env_vars = %w{HOME GNUPGHOME} + old_env = env_vars.map { |var| ENV[var] } + + begin + ENV['HOME'] = Etc.getpwuid(Process.uid).dir + + if @resource.value(:gnupghome) + ENV['GNUPGHOME'] = @resource.value(:gnupghome) + else + ENV.delete('GNUPGHOME') + end + + Dir.chdir(ENV['HOME']) do + yield + end + ensure + old_env.each_with_index { |val, i| + var = env_vars[i] + val.nil? ? ENV.delete(var) : (ENV[var] = val) + } + end + end + end diff --git a/lib/puppet/type/gpgkey.rb b/lib/puppet/type/gpgkey.rb index 85eafaf..64799fd 100644 --- a/lib/puppet/type/gpgkey.rb +++ b/lib/puppet/type/gpgkey.rb @@ -42,4 +42,23 @@ defaultto true end + newparam(:user) do + desc <<-EOT + The user account in which the key should be managed. + The resource will automatically depend on this user. + EOT + end + + newparam(:gnupghome) do + desc <<-EOT + The GnuPG data directory in which the key should be managed. + If this is not set, GnuPG selects the directory by itself. + EOT + end + + # Autorequire the owner of the ~/.gnupg directory. + autorequire(:user) do + self[:user] + end + end