summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2018-01-05 14:27:44 -0800
committerTim Smith <tsmith@chef.io>2018-01-05 15:17:44 -0800
commit98223f0b0fdca5ae8d460738c8c01ca0e80ec2c8 (patch)
treef053a068962ba4dbed2338eb37292614b7536b5d /lib
parent21155f73467969da3b36a35b146100d1a04b0cdf (diff)
downloadchef-98223f0b0fdca5ae8d460738c8c01ca0e80ec2c8.tar.gz
Add dhparam, rsa_private_key and rsa_public_key resources
Ported from the openssl cookbook. I've done all the major refactoring there to shake the bugs out. This is just reformatted to use the mixin instead of a cookbook helper and to be library style so it works in core chef. Signed-off-by: Tim Smith <tsmith@chef.io>
Diffstat (limited to 'lib')
-rw-r--r--lib/chef/mixin/openssl.rb118
-rw-r--r--lib/chef/resource/openssl_dhparam.rb57
-rw-r--r--lib/chef/resource/openssl_rsa_private_key.rb67
-rw-r--r--lib/chef/resource/openssl_rsa_public_key.rb54
-rw-r--r--lib/chef/resources.rb3
5 files changed, 299 insertions, 0 deletions
diff --git a/lib/chef/mixin/openssl.rb b/lib/chef/mixin/openssl.rb
new file mode 100644
index 0000000000..923ca75377
--- /dev/null
+++ b/lib/chef/mixin/openssl.rb
@@ -0,0 +1,118 @@
+#
+# Copyright:: Copyright 2013-2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module OpenSSL
+ def self.included(_base)
+ require "openssl" unless defined?(OpenSSL)
+ end
+
+ # determine the key filename from the cert filename
+ # @param [String] cert_filename the path to the certfile
+ # @return [String] the path to the keyfile
+ def get_key_filename(cert_filename)
+ cert_file_path, cert_filename = ::File.split(cert_filename)
+ cert_filename = ::File.basename(cert_filename, ::File.extname(cert_filename))
+ cert_file_path + ::File::SEPARATOR + cert_filename + ".key"
+ end
+
+ # is the key length a valid key length
+ # @param [Integer] number
+ # @return [Boolean] is length valid
+ def key_length_valid?(number)
+ number >= 1024 && number & (number - 1) == 0
+ end
+
+ # validate a dhparam file from path
+ # @param [String] dhparam_pem_path the path to the pem file
+ # @return [Boolean] is the key valid
+ def dhparam_pem_valid?(dhparam_pem_path)
+ # Check if the dhparam.pem file exists
+ # Verify the dhparam.pem file contains a key
+ return false unless ::File.exist?(dhparam_pem_path)
+ dhparam = OpenSSL::PKey::DH.new File.read(dhparam_pem_path)
+ dhparam.params_ok?
+ end
+
+ # given either a key file path or key file content see if it's actually
+ # a private key
+ # @param [String] key_file the path to the keyfile or the key contents
+ # @param [String] key_password optional password to the keyfile
+ # @return [Boolean] is the key valid?
+ def priv_key_file_valid?(key_file, key_password = nil)
+ # if the file exists try to read the content
+ # if not assume we were passed the key and set the string to the content
+ key_content = ::File.exist?(key_file) ? File.read(key_file) : key_file
+
+ begin
+ key = OpenSSL::PKey::RSA.new key_content, key_password
+ rescue OpenSSL::PKey::RSAError
+ return false
+ end
+ key.private?
+ end
+
+ # generate a dhparam file
+ # @param [String] key_length the length of the key
+ # @param [Integer] generator the dhparam generator to use
+ # @return [OpenSSL::PKey::DH]
+ def gen_dhparam(key_length, generator)
+ raise ArgumentError, "Key length must be a power of 2 greater than or equal to 1024" unless key_length_valid?(key_length)
+ raise TypeError, "Generator must be an integer" unless generator.is_a?(Integer)
+
+ OpenSSL::PKey::DH.new(key_length, generator)
+ end
+
+ # generate an RSA private key given key length
+ # @param [Integer] key_length the key length of the private key
+ # @return [OpenSSL::PKey::DH]
+ def gen_rsa_priv_key(key_length)
+ raise ArgumentError, "Key length must be a power of 2 greater than or equal to 1024" unless key_length_valid?(key_length)
+
+ OpenSSL::PKey::RSA.new(key_length)
+ end
+
+ # generate pem format of the public key given a private key
+ # @param [String] priv_key either the contents of the private key or the path to the file
+ # @param [String] priv_key_password optional password for the private key
+ # @return [String] pem format of the public key
+ def gen_rsa_pub_key(priv_key, priv_key_password = nil)
+ # if the file exists try to read the content
+ # if not assume we were passed the key and set the string to the content
+ key_content = ::File.exist?(priv_key) ? File.read(priv_key) : priv_key
+ key = OpenSSL::PKey::RSA.new key_content, priv_key_password
+ key.public_key.to_pem
+ end
+
+ # generate a pem file given a cipher, key, an optional key_password
+ # @param [OpenSSL::PKey::RSA] rsa_key the private key object
+ # @param [String] key_password the password for the private key
+ # @param [String] key_cipher the cipher to use
+ # @return [String] pem contents
+ def encrypt_rsa_key(rsa_key, key_password, key_cipher)
+ raise TypeError, "rsa_key must be a Ruby OpenSSL::PKey::RSA object" unless rsa_key.is_a?(OpenSSL::PKey::RSA)
+ raise TypeError, "key_password must be a string" unless key_password.is_a?(String)
+ raise TypeError, "key_cipher must be a string" unless key_cipher.is_a?(String)
+ raise ArgumentError, "Specified key_cipher is not available on this system" unless OpenSSL::Cipher.ciphers.include?(key_cipher)
+
+ cipher = OpenSSL::Cipher.new(key_cipher)
+ rsa_key.to_pem(cipher, key_password)
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_dhparam.rb b/lib/chef/resource/openssl_dhparam.rb
new file mode 100644
index 0000000000..6c261a5a8f
--- /dev/null
+++ b/lib/chef/resource/openssl_dhparam.rb
@@ -0,0 +1,57 @@
+#
+# Copyright:: Copyright 2009-2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # a resource for generating dhparam.pem files.
+ # If a valid dhparam.pem file is found at the specified location, no new
+ # file will be created. If a file is found at the specified location but it
+ # is not a valid dhparam file, it will be overwritten.
+ class OpensslDhparam < Chef::Resource
+ require "chef/mixin/openssl"
+ include Chef::Mixin::OpenSSL
+
+ resource_name :openssl_dhparam
+
+ property :path, String, name_property: true
+ property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048
+ property :generator, equal_to: [2, 5], default: 2
+ property :owner, String, default: lazy { node["platform"] == "windows" ? "Adminstrator" : "root" }
+ property :group, String, default: lazy { node["root_group"] }
+ property :mode, [Integer, String], default: "0640"
+
+ action :create do
+ unless dhparam_pem_valid?(new_resource.path)
+ converge_by("Create a dhparam file #{new_resource.path}") do
+ dhparam_content = gen_dhparam(new_resource.key_length, new_resource.generator).to_pem
+
+ declare_resource(:file, new_resource.path) do
+ action :create
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode
+ sensitive true
+ content dhparam_content
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_rsa_private_key.rb b/lib/chef/resource/openssl_rsa_private_key.rb
new file mode 100644
index 0000000000..55512ee098
--- /dev/null
+++ b/lib/chef/resource/openssl_rsa_private_key.rb
@@ -0,0 +1,67 @@
+#
+# Copyright:: Copyright 2009-2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # A resource for generating rsa private key files.
+ # If a valid rsa key file can be opened at the specified location, no new file
+ # will be created. If the RSA key file cannot be opened, either because it
+ # does not exist or because the password to the RSA key file does not match
+ # the password in the recipe, it will be overwritten.
+ class OpensslRsaPrivateKey < Chef::Resource
+ require "chef/mixin/openssl"
+ include Chef::Mixin::OpenSSL
+
+ resource_name :openssl_rsa_private_key
+ provides :openssl_rsa_private_key
+ provides :openssl_rsa_key # legacy cookbook resource name
+
+ property :path, String, name_property: true
+ property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048
+ property :key_pass, String
+ property :key_cipher, String, default: "des3", equal_to: OpenSSL::Cipher.ciphers
+ property :owner, String, default: lazy { node["platform"] == "windows" ? "Adminstrator" : "root" }
+ property :group, String, default: lazy { node["root_group"] }
+ property :mode, [Integer, String], default: "0640"
+ property :force, [true, false], default: false
+
+ action :create do
+ return if new_resource.force || priv_key_file_valid?(new_resource.path, new_resource.key_pass)
+
+ converge_by("create #{new_resource.key_length} bit RSA key #{new_resource.path}") do
+ if new_resource.key_pass
+ unencrypted_rsa_key = gen_rsa_priv_key(new_resource.key_length)
+ rsa_key_content = encrypt_rsa_key(unencrypted_rsa_key, new_resource.key_pass, new_resource.key_cipher)
+ else
+ rsa_key_content = gen_rsa_priv_key(new_resource.key_length).to_pem
+ end
+
+ declare_resource(:file, new_resource.path) do
+ action :create
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode
+ sensitive true
+ content rsa_key_content
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_rsa_public_key.rb b/lib/chef/resource/openssl_rsa_public_key.rb
new file mode 100644
index 0000000000..4cffe53f6d
--- /dev/null
+++ b/lib/chef/resource/openssl_rsa_public_key.rb
@@ -0,0 +1,54 @@
+#
+# Copyright:: Copyright 2009-2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ # A resource for generating rsa public key files given a rsa private key.
+ class OpensslRsaPublicKey < Chef::Resource
+ require "chef/mixin/openssl"
+ include Chef::Mixin::OpenSSL
+
+ resource_name :openssl_rsa_public_key
+
+ property :path, String, name_property: true
+ property :private_key_path, String
+ property :private_key_content, String
+ property :private_key_pass, String
+ property :owner, String, default: lazy { node["platform"] == "windows" ? "Adminstrator" : "root" }
+ property :group, String, default: lazy { node["root_group"] }
+ property :mode, [Integer, String], default: "0640"
+
+ action :create do
+ raise ArgumentError, "You cannot specify both 'private_key_path' and 'private_key_content' properties at the same time." if new_resource.private_key_path && new_resource.private_key_content
+ raise ArgumentError, "You must specify the private key with either 'private_key_path' or 'private_key_content' properties." unless new_resource.private_key_path || new_resource.private_key_content
+ raise "#{new_resource.private_key_path} not a valid private RSA key or password is invalid" unless priv_key_file_valid?((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ rsa_key_content = gen_rsa_pub_key((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ declare_resource(:file, new_resource.path) do
+ action :create
+ owner new_resource.owner
+ group new_resource.group
+ mode new_resource.mode
+ content rsa_key_content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 54d21fd53c..31d4589ca3 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -56,6 +56,9 @@ require "chef/resource/mdadm"
require "chef/resource/mount"
require "chef/resource/ohai"
require "chef/resource/openbsd_package"
+require "chef/resource/openssl_dhparam"
+require "chef/resource/openssl_rsa_private_key"
+require "chef/resource/openssl_rsa_public_key"
require "chef/resource/package"
require "chef/resource/pacman_package"
require "chef/resource/paludis_package"