summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.markdown24
-rw-r--r--lib/puppet/parser/functions/clamp.rb30
-rw-r--r--lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb47
-rwxr-xr-xspec/acceptance/clamp_spec.rb40
-rw-r--r--spec/functions/clamp_spec.rb16
-rwxr-xr-xspec/functions/validate_x509_rsa_key_pair_spec.rb180
6 files changed, 337 insertions, 0 deletions
diff --git a/README.markdown b/README.markdown
index a6ed1fb..559a6a0 100644
--- a/README.markdown
+++ b/README.markdown
@@ -193,6 +193,14 @@ Removes the record separator from the end of a string or an array of strings; fo
Returns a new string with the last character removed. If the string ends with '\r\n', both characters are removed. Applying `chop` to an empty string returns an empty string. If you want to merely remove record separators, then you should use the `chomp` function. Requires a string or an array of strings as input. *Type*: rvalue.
+#### `clamp`
+
+Keeps value within the range [Min, X, Max] by sort based on integer value (order of params doesn't matter). Takes strings, arrays or numerics. Strings are converted and compared numerically. Arrays of values are flattened into a list for further handling. For example:
+ * `clamp('24', [575, 187])` returns 187.
+ * `clamp(16, 88, 661)` returns 88.
+ * `clamp([4, 3, '99'])` returns 4.
+ *Type*: rvalue.
+
#### `concat`
Appends the contents of multiple arrays onto the first array given. For example:
@@ -1206,6 +1214,22 @@ Instead, use:
*Type*: statement.
+#### `validate_x509_rsa_key_pair`
+
+Validates a PEM-formatted X.509 certificate and private key using OpenSSL.
+Verifies that the certficate's signature was created from the supplied key.
+
+Fails catalog compilation if any value fails this check.
+
+Takes two arguments, the first argument must be a X.509 certificate and the
+second must be an RSA private key:
+
+ ~~~
+ validate_x509_rsa_key_pair($cert, $key)
+ ~~~
+
+*Type*: statement.
+
#### `values`
Returns the values of a given hash. For example, given `$hash = {'a'=1, 'b'=2, 'c'=3} values($hash)` returns [1,2,3].
diff --git a/lib/puppet/parser/functions/clamp.rb b/lib/puppet/parser/functions/clamp.rb
new file mode 100644
index 0000000..432c7c1
--- /dev/null
+++ b/lib/puppet/parser/functions/clamp.rb
@@ -0,0 +1,30 @@
+#
+# clamp.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:clamp, :type => :rvalue, :arity => -2, :doc => <<-EOS
+ Clamps value to a range.
+ EOS
+ ) do |args|
+
+ args.flatten!
+
+ raise(Puppet::ParseError, 'clamp(): Wrong number of arguments, ' +
+ 'need three to clamp') if args.size != 3
+
+ # check values out
+ args.each do |value|
+ case [value.class]
+ when [String]
+ raise(Puppet::ParseError, "clamp(): Required explicit numeric (#{value}:String)") unless value =~ /^\d+$/
+ when [Hash]
+ raise(Puppet::ParseError, "clamp(): The Hash type is not allowed (#{value})")
+ end
+ end
+
+ # convert to numeric each element
+ # then sort them and get a middle value
+ args.map{ |n| n.to_i }.sort[1]
+ end
+end
diff --git a/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb b/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb
new file mode 100644
index 0000000..fc9f23f
--- /dev/null
+++ b/lib/puppet/parser/functions/validate_x509_rsa_key_pair.rb
@@ -0,0 +1,47 @@
+module Puppet::Parser::Functions
+
+ newfunction(:validate_x509_rsa_key_pair, :doc => <<-ENDHEREDOC
+ Validates a PEM-formatted X.509 certificate and RSA private key using
+ OpenSSL. Verifies that the certficate's signature was created from the
+ supplied key.
+
+ Fail compilation if any value fails this check.
+
+ validate_x509_rsa_key_pair($cert, $key)
+
+ ENDHEREDOC
+ ) do |args|
+
+ require 'openssl'
+
+ NUM_ARGS = 2 unless defined? NUM_ARGS
+
+ unless args.length == NUM_ARGS then
+ raise Puppet::ParseError,
+ ("validate_x509_rsa_key_pair(): wrong number of arguments (#{args.length}; must be #{NUM_ARGS})")
+ end
+
+ args.each do |arg|
+ unless arg.is_a?(String)
+ raise Puppet::ParseError, "#{arg.inspect} is not a string."
+ end
+ end
+
+ begin
+ cert = OpenSSL::X509::Certificate.new(args[0])
+ rescue OpenSSL::X509::CertificateError => e
+ raise Puppet::ParseError, "Not a valid x509 certificate: #{e}"
+ end
+
+ begin
+ key = OpenSSL::PKey::RSA.new(args[1])
+ rescue OpenSSL::PKey::RSAError => e
+ raise Puppet::ParseError, "Not a valid RSA key: #{e}"
+ end
+
+ unless cert.verify(key)
+ raise Puppet::ParseError, "Certificate signature does not match supplied key"
+ end
+ end
+
+end
diff --git a/spec/acceptance/clamp_spec.rb b/spec/acceptance/clamp_spec.rb
new file mode 100755
index 0000000..0189258
--- /dev/null
+++ b/spec/acceptance/clamp_spec.rb
@@ -0,0 +1,40 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'clamp function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'clamps list of values' do
+ pp = <<-EOS
+ $x = 17
+ $y = 225
+ $z = 155
+ $o = clamp($x, $y, $z)
+ if $o == $z {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'clamps array of values' do
+ pp = <<-EOS
+ $a = [7, 19, 66]
+ $b = 19
+ $o = clamp($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles no arguments'
+ end
+end
diff --git a/spec/functions/clamp_spec.rb b/spec/functions/clamp_spec.rb
new file mode 100644
index 0000000..3e2fe7a
--- /dev/null
+++ b/spec/functions/clamp_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe 'clamp' do
+ it { is_expected.not_to eq(nil) }
+ it { is_expected.to run.with_params().and_raise_error(ArgumentError) }
+ it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) }
+ it { is_expected.to run.with_params(12, 88, 71, 190).and_raise_error(Puppet::ParseError, /Wrong number of arguments, need three to clamp/) }
+ it { is_expected.to run.with_params('12string', 88, 15).and_raise_error(Puppet::ParseError, /Required explicit numeric/) }
+ it { is_expected.to run.with_params(1, 2, {'a' => 55}).and_raise_error(Puppet::ParseError, /The Hash type is not allowed/) }
+ it { is_expected.to run.with_params('24', [575, 187]).and_return(187) }
+ it { is_expected.to run.with_params([4, 3, '99']).and_return(4) }
+ it { is_expected.to run.with_params(16, 750, 88).and_return(88) }
+ it { is_expected.to run.with_params([3, 873], 73).and_return(73) }
+ it { is_expected.to run.with_params([4], 8, 75).and_return(8) }
+ it { is_expected.to run.with_params([6], [31], 9911).and_return(31) }
+end
diff --git a/spec/functions/validate_x509_rsa_key_pair_spec.rb b/spec/functions/validate_x509_rsa_key_pair_spec.rb
new file mode 100755
index 0000000..eb63310
--- /dev/null
+++ b/spec/functions/validate_x509_rsa_key_pair_spec.rb
@@ -0,0 +1,180 @@
+require 'spec_helper'
+
+describe 'validate_x509_rsa_key_pair' do
+
+ let(:valid_cert) do
+ <<EOS
+-----BEGIN CERTIFICATE-----
+MIIC9jCCAeCgAwIBAgIRAK11n3X7aypJ7FPM8UFyAeowCwYJKoZIhvcNAQELMBIx
+EDAOBgNVBAoTB0FjbWUgQ28wHhcNMTUxMTIzMjIzOTU4WhcNMTYxMTIyMjIzOTU4
+WjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAz9bY/piKahD10AiJSfbI2A8NG5UwRz0r9T/WfvNVdhgrsGFgNQjvpUoZ
+nNJpQIHBbgMOiXqfATFjJl5FjEkSf7GUHohlGVls9MX2JmVvknzsiitd75H/EJd+
+N+k915lix8Vqmj8d1CTlbF/8tEjzANI67Vqw5QTuqebO7rkIUvRg6yiRfSo75FK1
+RinCJyl++kmleBwQZBInQyg95GvJ5JTqMzBs67DeeyzskDhTeTePRYVF2NwL8QzY
+htvLIBERTNsyU5i7nkxY5ptUwgFUwd93LH4Q19tPqL5C5RZqXxhE51thOOwafm+a
+W/cRkqYqV+tv+j1jJ3WICyF1JNW0BQIDAQABo0swSTAOBgNVHQ8BAf8EBAMCAKAw
+EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALggls
+b2NhbGhvc3QwCwYJKoZIhvcNAQELA4IBAQAzRo0hpVTrFQZLIXpwvKwZVGvJdCkV
+P95DTsSk/VTGV+/YtxrRqks++hJZnctm2PbnTsCAoIP3AMx+vicCKiKrxvpsLU8/
++6cowUbcuGMdSQktwDqbAgEhQlLsETll06w1D/KC+ejOc4+LRn3GQcEyGDtMk/EX
+IeAvBZHr4/kVXWnfo6kzCLcku1f8yE/yDEFClZe9XV1Lk/s+3YfXVtNnMJJ1giZI
+QVOe6CkmuQq+4AtIeW8aLkvlfp632jag1F77a1y+L268koKkj0hBMrtcErVQaxmq
+xym0+soR4Tk4pTIGckeFglrLxkP2JpM/yTwSEAVlmG9vgTliYKyR0uMl
+-----END CERTIFICATE-----
+EOS
+ end
+
+ let(:valid_key) do
+ <<EOS
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAz9bY/piKahD10AiJSfbI2A8NG5UwRz0r9T/WfvNVdhgrsGFg
+NQjvpUoZnNJpQIHBbgMOiXqfATFjJl5FjEkSf7GUHohlGVls9MX2JmVvknzsiitd
+75H/EJd+N+k915lix8Vqmj8d1CTlbF/8tEjzANI67Vqw5QTuqebO7rkIUvRg6yiR
+fSo75FK1RinCJyl++kmleBwQZBInQyg95GvJ5JTqMzBs67DeeyzskDhTeTePRYVF
+2NwL8QzYhtvLIBERTNsyU5i7nkxY5ptUwgFUwd93LH4Q19tPqL5C5RZqXxhE51th
+OOwafm+aW/cRkqYqV+tv+j1jJ3WICyF1JNW0BQIDAQABAoIBADAiZ/r+xP+vkd5u
+O61/lCBFzBlZQecdybJw6HJaVK6XBndA9hESUr4LHUdui6W+51ddKd65IV4bXAUk
+zCKjQb+FFvLDT/bA+TTvLATUdTSN7hJJ3OWBAHuNOlQklof6JCB0Hi4+89+P8/pX
+eKUgR/cmuTMDT/iaXdPHeqFbBQyA1ZpQFRjN5LyyJMS/9FkywuNc5wlpsArtc51T
+gIKENUZCuPhosR+kMFc2iuTNvqZWPhvouSrmhi2O6nSqV+oy0+irlqSpCF2GsCI8
+72TtLpq94Grrq0BEH5avouV+Lp4k83vO65OKCQKUFQlxz3Xkxm2U3J7KzxqnRtM3
+/b+cJ/kCgYEA6/yOnaEYhH/7ijhZbPn8RujXZ5VGJXKJqIuaPiHMmHVS5p1j6Bah
+2PcnqJA2IlLs3UloN+ziAxAIH6KCBiwlQ/uPBNMMaJsIjPNBEy8axjndKhKUpidg
+R0OJ7RQqMShOJ8akrSfWdPtXC/GBuwCYE//t77GgZaIMO3FcT9EKA48CgYEA4Xcx
+Fia0Jg9iyAhNmUOXI6hWcGENavMx01+x7XFhbnMjIKTZevFfTnTkrX6HyLXyGtMU
+gHOn+k4PE/purI4ARrKO8m5wYEKqSIt4dBMTkIXXirfQjXgfjR8E4T/aPe5fOFZo
+7OYuxLRtzmG1C2sW4txwKAKX1LaWcVx/RLSttSsCgYBbcj8Brk+F6OJcqYFdzXGJ
+OOlf5mSMVlopyg83THmwCqbZXtw8L6kAHqZrl5airmfDSJLuOQlMDoZXW+3u3mSC
+d5TwVahVUN57YDgzaumBLyMZDqIz0MZqVy23hTzkV64Rk9R0lR9xrYQJyMhw4sYL
+2f0mCTsSpzz+O+t9so+i2QKBgEC38gMlwPhb2kMI/x1LZYr6uzUu5qcYf+jowy4h
+KZKGwkKQj0zXFEB1FV8nvtpCP+irRmtIx6L13SYi8LnfWPzyLE4ynVdES5TfVAgd
+obQOdzx+XwL8xDHCAaiWp5K3ZeXKB/xYZnxYPlzLdyh76Ond1OPnOqX4c16+6llS
+c7pZAoGATd9NckT0XtXLEsF3IraDivq8dP6bccX2DNfS8UeEvRRrRwpFpSRrmuGb
+jbG4yzoIX4RjQfj/z48hwhJB+cKiN9WwcPsFXtHe7v3F6BRwK0JUfrCiXad8/SGZ
+KAf7Dfqi608zBdnPWHacre2Y35gPHB00nFQOLS6u46aBNSq07YA=
+-----END RSA PRIVATE KEY-----
+EOS
+ end
+
+ let(:another_valid_key) do
+ <<EOS
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAoISxYJBTPAeAzFnm+lE/ljLlmGal2Xr3vwZKkvJiuKA/m4QJ
+0ZNdtkBSDOVuG2dXVv6W4sChRtsCdvuVe7bjTYvlU8TWM3VEJDL9l9cRXScxxlKQ
+Xwb35y1yV35NJfaK/jzm9KcErtQQs1RxvGlWRaohmLM8uQcuhjZfMsSlQoHQD5LX
+sbPtk82RPyxYc1dj2vsaoi1VvuP2+jv4xLQOmNJY1bT5GTurqiltmxEtWhNNmGg0
+2wtK00ifqLVO5HNc3gXQCDM2M99Sbmn1YtbrgsU9xMYfcPmvQvb+YoKskyoqck+c
+HR//hi7vslbxABrny15LBkEfRc4TickphSGYXwIDAQABAoIBAATEzGw8/WwMIQRx
+K06GeWgh7PZBHm4+m/ud2TtSXiJ0CE+7dXs3cJJIiOd/LW08/bhE6gCkjmYHfaRB
+Ryicv1X/cPmzIFX5BuQ4a5ZGOmrVDkKBE27vSxAgJoR46RvWnjx9XLMp/xaekDxz
+psldK8X4DvV1ZbltgDFWji947hvyqUtHdKnkQnc5j7aCIFJf9GMfzaeeDPMaL8WF
+mVL4iy9EAOjNOHBshZj/OHyU5FbJ8ROwZQlCOiLCdFegftSIXt8EYDnjB3BdsALH
+N6hquqrD7xDKyRbTD0K7lqxUubuMwTQpi61jZD8TBTXEPyFVAnoMpXkc0Y+np40A
+YiIsR+kCgYEAyrc4Bh6fb9gt49IXGXOSRZ5i5+TmJho4kzIONrJ7Ndclwx9wzHfh
+eGBodWaw5CxxQGMf4vEiaZrpAiSFeDffBLR+Wa2TFE5aWkdYkR34maDjO00m4PE1
+S+YsZoGw7rGmmj+KS4qv2T26FEHtUI+F31RC1FPohLsQ22Jbn1ORipsCgYEAyrYB
+J2Ncf2DlX1C0GfxyUHQOTNl0V5gpGvpbZ0WmWksumYz2kSGOAJkxuDKd9mKVlAcz
+czmN+OOetuHTNqds2JJKKJy6hJbgCdd9aho3dId5Xs4oh4YwuFQiG8R/bJZfTlXo
+99Qr02L7MmDWYLmrR3BA/93UPeorHPtjqSaYU40CgYEAtmGfWwokIglaSDVVqQVs
+3YwBqmcrla5TpkMLvLRZ2/fktqfL4Xod9iKu+Klajv9ZKTfFkXWno2HHL7FSD/Yc
+hWwqnV5oDIXuDnlQOse/SeERb+IbD5iUfePpoJQgbrCQlwiB0TNGwOojR2SFMczf
+Ai4aLlQLx5dSND9K9Y7HS+8CgYEAixlHQ2r4LuQjoTs0ytwi6TgqE+vn3K+qDTwc
+eoods7oBWRaUn1RCKAD3UClToZ1WfMRQNtIYrOAsqdveXpOWqioAP0wE5TTOuZIo
+GiWxRgIsc7TNtOmNBv+chCdbNP0emxdyjJUIGb7DFnfCw47EjHnn8Guc13uXaATN
+B2ZXgoUCgYAGa13P0ggUf5BMJpBd8S08jKRyvZb1CDXcUCuGtk2yEx45ern9U5WY
+zJ13E5z9MKKO8nkGBqrRfjJa8Xhxk4HKNFuzHEet5lvNE7IKCF4YQRb0ZBhnb/78
++4ZKjFki1RrWRNSw9TdvrK6qaDKgTtCTtfRVXAYQXUgq7lSFOTtL3A==
+-----END RSA PRIVATE KEY-----
+EOS
+ end
+
+ let(:valid_cert_but_indented) do
+ valid_cert.gsub(/^/, ' ')
+ end
+
+ let(:valid_key_but_indented) do
+ valid_key.gsub(/^/, ' ')
+ end
+
+ let(:malformed_cert) do
+ truncate_middle(valid_cert)
+ end
+
+ let(:malformed_key) do
+ truncate_middle(valid_key)
+ end
+
+ let(:bad_cert) do
+ 'foo'
+ end
+
+ let(:bad_key) do
+ 'bar'
+ end
+
+ context 'function signature validation' do
+ it { is_expected.not_to eq(nil) }
+ it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
+ it { is_expected.to run.with_params(0, 1, 2, 3).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
+ end
+
+ context 'valid input' do
+ describe 'valid certificate and key' do
+ it { is_expected.to run.with_params(valid_cert, valid_key) }
+ end
+ end
+
+ context 'bad input' do
+ describe 'valid certificate, valid but indented key' do
+ it { is_expected.to run.with_params(valid_cert, valid_key_but_indented).and_raise_error(Puppet::ParseError, /Not a valid RSA key/) }
+ end
+
+ describe 'valid certificate, malformed key' do
+ it { is_expected.to run.with_params(valid_cert, malformed_key).and_raise_error(Puppet::ParseError, /Not a valid RSA key/) }
+ end
+
+ describe 'valid certificate, bad key' do
+ it { is_expected.to run.with_params(valid_cert, bad_key).and_raise_error(Puppet::ParseError, /Not a valid RSA key/) }
+ end
+
+ describe 'valid but indented certificate, valid key' do
+ it { is_expected.to run.with_params(valid_cert_but_indented, valid_key).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) }
+ end
+
+ describe 'malformed certificate, valid key' do
+ it { is_expected.to run.with_params(malformed_cert, valid_key).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) }
+ end
+
+ describe 'bad certificate, valid key' do
+ it { is_expected.to run.with_params(bad_cert, valid_key).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) }
+ end
+
+ describe 'validate certificate and key; certficate not signed by key' do
+ it { is_expected.to run.with_params(valid_cert, another_valid_key).and_raise_error(Puppet::ParseError, /Certificate signature does not match supplied key/) }
+ end
+
+ describe 'valid cert and key but arguments in wrong order' do
+ it { is_expected.to run.with_params(valid_key, valid_cert).and_raise_error(Puppet::ParseError, /Not a valid x509 certificate/) }
+ end
+
+ describe 'non-string arguments' do
+ it { is_expected.to run.with_params({}, {}).and_raise_error(Puppet::ParseError, /is not a string/) }
+ it { is_expected.to run.with_params(1, 1).and_raise_error(Puppet::ParseError, /is not a string/) }
+ it { is_expected.to run.with_params(true, true).and_raise_error(Puppet::ParseError, /is not a string/) }
+ it { is_expected.to run.with_params("foo", {}).and_raise_error(Puppet::ParseError, /is not a string/) }
+ it { is_expected.to run.with_params(1, "bar").and_raise_error(Puppet::ParseError, /is not a string/) }
+ it { is_expected.to run.with_params("baz", true).and_raise_error(Puppet::ParseError, /is not a string/) }
+ end
+ end
+
+ def truncate_middle(string)
+ chars_to_truncate = 48
+ middle = (string.length / 2).floor
+ start_pos = middle - (chars_to_truncate / 2)
+ end_pos = middle + (chars_to_truncate / 2)
+
+ string[start_pos...end_pos] = ''
+ return string
+ end
+end