From 5ef200f10de7bc184781c8ed34cfd03bb1b26606 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Thu, 11 Dec 2014 22:37:23 -0800 Subject: [PATCH 1/3] first pass at simple encryption This isn't intended to be a complete solution yet, but I wanted to give you a proof of concept I banged out. The encryption is completely transparent to the end user. To use it, simply pass the parameter of `encrypted => true` and it's encrypted & decrypted for you. Example usage: ``` Puppet datacat_fragment { 'encryption test': target => $target, data => { name => 'fred', }, encrypted => true, } ``` --- .../datacat_collector/datacat_collector.rb | 8 +++++ lib/puppet/type/datacat_fragment.rb | 15 ++++++++ lib/puppet_x/richardc/datacat.rb | 35 +++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/lib/puppet/provider/datacat_collector/datacat_collector.rb b/lib/puppet/provider/datacat_collector/datacat_collector.rb index c5cbf20..b1e3238 100644 --- a/lib/puppet/provider/datacat_collector/datacat_collector.rb +++ b/lib/puppet/provider/datacat_collector/datacat_collector.rb @@ -9,6 +9,14 @@ def exists? r.is_a?(Puppet::Type.type(:datacat_fragment)) && ((our_names & [ r[:target] ].flatten).size > 0) end + # decrypt any encrypted fragments + fragments.each do |fragment| + next unless fragment[:encrypted] == :true + fragment[:data].each do |key,value| + fragment[:data][key] = Puppet_X::Richardc::Datacat.decrypt(value) + end + end + # order fragments on their :order property fragments = fragments.sort { |a,b| a[:order] <=> b[:order] } diff --git a/lib/puppet/type/datacat_fragment.rb b/lib/puppet/type/datacat_fragment.rb index 2f3031c..df9341c 100644 --- a/lib/puppet/type/datacat_fragment.rb +++ b/lib/puppet/type/datacat_fragment.rb @@ -17,4 +17,19 @@ newparam(:data) do desc 'A hash of data to be merged for this resource.' end + + newparam(:encrypted) do + desc 'Whether the data values should be encrypted over the wire.' + newvalues(:true, :false) + defaultto :false + end + + validate do + if self[:encrypted] == :true + self[:data].each do |key,value| + # using the fqdn is a terrible idea, but I couldn't figure out how to get clientcert here + self[:data][key] = Puppet_X::Richardc::Datacat.encrypt(value, Facter.value(:fqdn)) + end + end + end end diff --git a/lib/puppet_x/richardc/datacat.rb b/lib/puppet_x/richardc/datacat.rb index f28fad6..7f216f3 100644 --- a/lib/puppet_x/richardc/datacat.rb +++ b/lib/puppet_x/richardc/datacat.rb @@ -10,6 +10,41 @@ def self.deep_merge newval end end + + def self.encrypt(data, destination) + raise Puppet::ArgumentError, 'Can only encrypt strings' unless data.class == String + raise Puppet::ArgumentError, 'Need a node name to encrypt for' unless destination.class == String + + ssldir = Puppet.settings[:ssldir] + cert = OpenSSL::X509::Certificate.new(File.read("#{ssldir}/ca/ca_crt.pem")) + key = OpenSSL::PKey::RSA.new(File.read("#{ssldir}/ca/ca_key.pem"), '') + target = OpenSSL::X509::Certificate.new(File.read("#{ssldir}/ca/signed/#{destination}.pem")) + + signed = OpenSSL::PKCS7::sign(cert, key, data, [], OpenSSL::PKCS7::BINARY) + cipher = OpenSSL::Cipher::new("AES-128-CFB") + + OpenSSL::PKCS7::encrypt([target], signed.to_der, cipher, OpenSSL::PKCS7::BINARY).to_s + end + + def self.decrypt(data) + raise Puppet::ArgumentError, 'Can only decrypt strings' unless data.class == String + + ssldir = Puppet.settings[:ssldir] + name = Puppet.settings[:certname] + cert = OpenSSL::X509::Certificate.new(File.read("#{ssldir}/certs/#{name}.pem")) + key = OpenSSL::PKey::RSA.new(File.read("#{ssldir}/private_keys/#{name}.pem"), '') + source = OpenSSL::X509::Certificate.new(File.read("#{ssldir}/certs/ca.pem")) + + store = OpenSSL::X509::Store.new + store.add_cert(source) + + blob = OpenSSL::PKCS7.new(data) + decrypted = blob.decrypt(key, cert) + verified = OpenSSL::PKCS7.new(decrypted) + + verified.verify(nil, store, nil, OpenSSL::PKCS7::NOVERIFY) + verified.data + end end # Our much simpler version of Puppet::Parser::TemplateWrapper From a9c074ad79670727bc22bfac0db299b81d60aaff Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Mon, 14 Dec 2015 08:45:23 -0800 Subject: [PATCH 2/3] Port encryption to use binford2k/node_encrypt This conditionally loads my node_encrypt library, and then allows users to transparently node_encrypt() any data items. --- .../datacat_collector/datacat_collector.rb | 10 +++-- lib/puppet/type/datacat_fragment.rb | 15 ------- lib/puppet_x/richardc/datacat.rb | 40 +++---------------- 3 files changed, 11 insertions(+), 54 deletions(-) diff --git a/lib/puppet/provider/datacat_collector/datacat_collector.rb b/lib/puppet/provider/datacat_collector/datacat_collector.rb index b1e3238..838c984 100644 --- a/lib/puppet/provider/datacat_collector/datacat_collector.rb +++ b/lib/puppet/provider/datacat_collector/datacat_collector.rb @@ -10,10 +10,12 @@ def exists? end # decrypt any encrypted fragments - fragments.each do |fragment| - next unless fragment[:encrypted] == :true - fragment[:data].each do |key,value| - fragment[:data][key] = Puppet_X::Richardc::Datacat.decrypt(value) + if defined?(Puppet_X::Binford2k::NodeEncrypt) + fragments.each do |fragment| + fragment[:data].each do |key,value| + next unless Puppet_X::Binford2k::NodeEncrypt.encrypted?(value) + fragment[:data][key] = Puppet_X::Binford2k::NodeEncrypt.decrypt(value) + end end end diff --git a/lib/puppet/type/datacat_fragment.rb b/lib/puppet/type/datacat_fragment.rb index df9341c..2f3031c 100644 --- a/lib/puppet/type/datacat_fragment.rb +++ b/lib/puppet/type/datacat_fragment.rb @@ -17,19 +17,4 @@ newparam(:data) do desc 'A hash of data to be merged for this resource.' end - - newparam(:encrypted) do - desc 'Whether the data values should be encrypted over the wire.' - newvalues(:true, :false) - defaultto :false - end - - validate do - if self[:encrypted] == :true - self[:data].each do |key,value| - # using the fqdn is a terrible idea, but I couldn't figure out how to get clientcert here - self[:data][key] = Puppet_X::Richardc::Datacat.encrypt(value, Facter.value(:fqdn)) - end - end - end end diff --git a/lib/puppet_x/richardc/datacat.rb b/lib/puppet_x/richardc/datacat.rb index 7f216f3..8dc8f6a 100644 --- a/lib/puppet_x/richardc/datacat.rb +++ b/lib/puppet_x/richardc/datacat.rb @@ -1,3 +1,8 @@ +begin + require 'puppet_x/binford2k/node_encrypt' +rescue LoadError +end + module Puppet_X module Richardc class Datacat @@ -10,41 +15,6 @@ def self.deep_merge newval end end - - def self.encrypt(data, destination) - raise Puppet::ArgumentError, 'Can only encrypt strings' unless data.class == String - raise Puppet::ArgumentError, 'Need a node name to encrypt for' unless destination.class == String - - ssldir = Puppet.settings[:ssldir] - cert = OpenSSL::X509::Certificate.new(File.read("#{ssldir}/ca/ca_crt.pem")) - key = OpenSSL::PKey::RSA.new(File.read("#{ssldir}/ca/ca_key.pem"), '') - target = OpenSSL::X509::Certificate.new(File.read("#{ssldir}/ca/signed/#{destination}.pem")) - - signed = OpenSSL::PKCS7::sign(cert, key, data, [], OpenSSL::PKCS7::BINARY) - cipher = OpenSSL::Cipher::new("AES-128-CFB") - - OpenSSL::PKCS7::encrypt([target], signed.to_der, cipher, OpenSSL::PKCS7::BINARY).to_s - end - - def self.decrypt(data) - raise Puppet::ArgumentError, 'Can only decrypt strings' unless data.class == String - - ssldir = Puppet.settings[:ssldir] - name = Puppet.settings[:certname] - cert = OpenSSL::X509::Certificate.new(File.read("#{ssldir}/certs/#{name}.pem")) - key = OpenSSL::PKey::RSA.new(File.read("#{ssldir}/private_keys/#{name}.pem"), '') - source = OpenSSL::X509::Certificate.new(File.read("#{ssldir}/certs/ca.pem")) - - store = OpenSSL::X509::Store.new - store.add_cert(source) - - blob = OpenSSL::PKCS7.new(data) - decrypted = blob.decrypt(key, cert) - verified = OpenSSL::PKCS7.new(decrypted) - - verified.verify(nil, store, nil, OpenSSL::PKCS7::NOVERIFY) - verified.data - end end # Our much simpler version of Puppet::Parser::TemplateWrapper From 398254b62eec059fc4e4ae4df31eb1107f1ba331 Mon Sep 17 00:00:00 2001 From: Ben Ford Date: Mon, 14 Dec 2015 08:55:28 -0800 Subject: [PATCH 3/3] document node_encrypt --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 7260703..aa767d5 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,26 @@ datacat_fragment { 'open ssh': } ``` +Optional: in-catalog encryption +--------------------- +If you have the [`binford2k/node_encrypt`](https://forge.puppetlabs.com/binford2k/node_encrypt) +module installed, then you can transparently encrypt any data element using the +`node_encrypt()` function. **Remember to set `show_diff => false` to keep the +secrets from appearing in your reports!** + +```Puppet +datacat { '/tmp/test': + template_body => "Decrypted value: <%= @data["value"] %>", + show_diff => false, +} +datacat_fragment { 'encryption test': + target => '/tmp/test', + data => { + value => node_encrypt('This string will not be included in the catalog.'), + }, +} +``` + Caveats -------