summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn McCrae <jmccrae@chef.io>2021-04-12 13:24:01 -0700
committerJohn McCrae <jmccrae@chef.io>2021-04-12 13:24:01 -0700
commitee0114abeba034d57630f2ac555fd480b733a9e0 (patch)
tree9fe7389e2d4aa4bf010d8fdef8cbf865f64dd039
parentcff97d80bee0d1269eed28f4c128f56e69da4aad (diff)
downloadchef-ee0114abeba034d57630f2ac555fd480b733a9e0.tar.gz
uploading this for others to review
Signed-off-by: John McCrae <jmccrae@chef.io>
-rw-r--r--Gemfile.lock20
-rw-r--r--lib/chef/resource/windows_certificate.rb296
-rw-r--r--spec/functional/resource/windows_certificate2_spec.rb168
-rw-r--r--spec/functional/resource/windows_certificate_spec.rb137
4 files changed, 508 insertions, 113 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index 85ed5dd6fd..59dc19ea93 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,10 +1,10 @@
GIT
remote: https://github.com/chef/chefstyle.git
- revision: 8a1ad4ab1f4e210cc9440f64c9eb5d674ed10b38
+ revision: 6d136684438e8809e703feb84c46c69e90e042a7
branch: master
specs:
- chefstyle (1.7.2)
- rubocop (= 1.11.0)
+ chefstyle (1.7.4)
+ rubocop (= 1.12.0)
GIT
remote: https://github.com/chef/ohai.git
@@ -175,7 +175,7 @@ GEM
concurrent-ruby (1.1.8)
crack (0.4.5)
rexml
- debug_inspector (1.0.0)
+ debug_inspector (1.1.0)
diff-lcs (1.3)
ed25519 (1.2.4)
erubi (1.10.0)
@@ -208,7 +208,7 @@ GEM
highline (2.0.3)
httpclient (2.8.3)
iniparse (1.5.0)
- inspec-core (4.28.0)
+ inspec-core (4.29.3)
addressable (~> 2.4)
chef-telemetry (~> 1.0)
faraday (>= 0.9.0, < 1.4)
@@ -231,8 +231,8 @@ GEM
train-core (~> 3.0)
tty-prompt (~> 0.17)
tty-table (~> 0.10)
- inspec-core-bin (4.28.0)
- inspec-core (= 4.28.0)
+ inspec-core-bin (4.29.3)
+ inspec-core (= 4.29.3)
ipaddress (0.8.3)
iso8601 (0.13.0)
json (2.5.1)
@@ -316,7 +316,7 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-support (3.10.2)
- rubocop (1.11.0)
+ rubocop (1.12.0)
parallel (~> 1.10)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
@@ -344,7 +344,7 @@ GEM
syslog-logger (1.6.8)
thor (1.1.0)
tomlrb (1.3.0)
- train-core (3.5.4)
+ train-core (3.5.5)
addressable (~> 2.5)
ffi (!= 1.13.0)
json (>= 1.8, < 3.0)
@@ -376,7 +376,7 @@ GEM
unicode-display_width (2.0.0)
unicode_utils (1.4.0)
uuidtools (2.2.0)
- webmock (3.12.1)
+ webmock (3.12.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
diff --git a/lib/chef/resource/windows_certificate.rb b/lib/chef/resource/windows_certificate.rb
index 6d0e080b3f..d2c525e652 100644
--- a/lib/chef/resource/windows_certificate.rb
+++ b/lib/chef/resource/windows_certificate.rb
@@ -19,6 +19,7 @@
require_relative "../util/path_helper"
require_relative "../resource"
+require_relative "../exceptions"
module Win32
autoload :Certstore, "win32-certstore" if Chef::Platform.windows?
end
@@ -62,11 +63,11 @@ class Chef
DOC
property :source, String,
- description: "The source file (for create and acl_add), thumbprint (for delete and acl_add) or subject (for delete) if it differs from the resource block's name.",
+ description: "The source file (for create and acl_add), thumbprint (for delete, export and acl_add) or subject (for delete or export) if it differs from the resource block's name.",
name_property: true
property :pfx_password, String,
- description: "The password to access the source if it is a pfx file."
+ description: "The password to access the object with if it is a pfx file."
property :private_key_acl, Array,
description: "An array of 'domain\\account' entries to be granted read-only access to the certificate's private key. Not idempotent."
@@ -79,9 +80,6 @@ class Chef
description: "Use the `CurrentUser` store instead of the default `LocalMachine` store. Note: Prior to #{ChefUtils::Dist::Infra::CLIENT}. 16.10 this property was ignored.",
default: false
- property :cert_path, String,
- description: "The path to the certificate."
-
# lazy used to set default value of sensitive to true if password is set
property :sensitive, [TrueClass, FalseClass],
description: "Ensure that sensitive resource data is not logged by the #{ChefUtils::Dist::Infra::CLIENT}.",
@@ -92,16 +90,23 @@ class Chef
default: false,
introduced: "16.8"
- action :create, description: "Creates or updates a certificate" do
- # Extension of the certificate
- ext = ::File.extname(new_resource.source)
+ property :output_path, String,
+ description: "A path on the node where a certificate object (pfx, pem, cer, key, etc) can be exported to.",
+ introduced: "16.10"
+
+ action :create do
+ description "Creates or updates a certificate."
+
+ ext = get_file_extension(new_resource.source)
# PFX certificates contains private keys and we import them with some other approach
+
import_certificates(fetch_cert_object(ext), (ext == ".pfx"))
end
# acl_add is a modify-if-exists operation : not idempotent
- action :acl_add, description: "Adds read-only entries to a certificate's private key ACL" do
+ action :acl_add do
+ description "Adds read-only entries to a certificate's private key ACL."
if ::File.exist?(new_resource.source)
hash = "$cert.GetCertHashString()"
@@ -124,8 +129,11 @@ class Chef
end
end
- action :delete, description: "Deletes a certificate" do
+ action :delete do
+ description "Deletes a certificate."
+
cert_obj = fetch_cert
+
if cert_obj
converge_by("Deleting certificate #{new_resource.source} from Store #{new_resource.store_name}") do
delete_cert
@@ -135,16 +143,34 @@ class Chef
end
end
- action :fetch, description: "Fetches a certificate" do
- cert_obj = fetch_cert
+ action :fetch do
+ description "Fetches a certificate."
+
+ if !new_resource.output_path
+ raise Chef::Exceptions::ResourceNotFound, "You must include an output_path parameter when calling the fetch action"
+ end
+
+ puts "Output path is #{new_resource.output_path}"
+
+ if ::File.extname(new_resource.output_path) == ".pfx"
+ puts "Thumbprint is : #{resolve_thumbprint(new_resource.source)}"
+ powershell_exec!(pfx_ps_cmd(resolve_thumbprint(new_resource.source), store_location: ps_cert_location, store_name: new_resource.store_name, output_path: new_resource.output_path, password: new_resource.pfx_password ))
+ else
+ cert_obj = fetch_cert
+ end
+
if cert_obj
- show_or_store_cert(cert_obj)
+ converge_by("Fetching certificate #{new_resource.source} from Store \\#{ps_cert_location}\\#{new_resource.store_name}") do
+ export_cert(cert_obj, output_path: new_resource.output_path, store_name: new_resource.store_name , store_location: ps_cert_location, pfx_password: new_resource.pfx_password)
+ end
else
Chef::Log.debug("Certificate not found")
end
end
- action :verify, description: "Verifies a certificate and logs the result" do
+ action :verify do
+ description "Verifies a certificate and logs the result"
+
out = verify_cert
if !!out == out
out = out ? "Certificate is valid" : "Certificate not valid"
@@ -153,6 +179,7 @@ class Chef
end
action_class do
+ @local_pfx_path = ""
CERT_SYSTEM_STORE_LOCAL_MACHINE = 0x00020000
CERT_SYSTEM_STORE_CURRENT_USER = 0x00010000
@@ -162,10 +189,10 @@ class Chef
store.add(cert_obj)
end
- def add_pfx_cert
+ def add_pfx_cert(path) #expecting a path object here.
exportable = new_resource.exportable ? 1 : 0
store = ::Win32::Certstore.open(new_resource.store_name, store_location: native_cert_location)
- store.add_pfx(new_resource.source, new_resource.pfx_password, exportable)
+ store.add_pfx(path, new_resource.pfx_password, exportable)
end
def delete_cert
@@ -175,12 +202,79 @@ class Chef
def fetch_cert
store = ::Win32::Certstore.open(new_resource.store_name, store_location: native_cert_location)
- store.get(resolve_thumbprint(new_resource.source))
+ if new_resource.output_path && ::File.extname(new_resource.output_path) == ".key"
+ fetch_key
+ # store.get_key(resolve_thumbprint(new_resource.source), store_name: new_resource.store_name, store_location: ps_cert_location)
+ else
+ store.get(resolve_thumbprint(new_resource.source), store_name: new_resource.store_name, store_location: native_cert_location)
+ end
+ end
+
+ def fetch_key
+ require "openssl"
+ file_name = ::File.basename(new_resource.output_path, ::File.extname(new_resource.output_path))
+ directory = ::File.dirname(new_resource.output_path)
+ pfx_file = file_name + ".pfx"
+ new_pfx_output_path = ::File.join(Chef::FileCache.create_cache_path('pfx_files'), pfx_file)
+ puts "In Key Fetch"
+ # puts "Thumbprint is #{resolve_thumbprint(new_resource.source)}"
+ # puts "Store Location : #{ps_cert_location}"
+ # puts "Store Name : #{new_resource.store_name}"
+ # puts "Output Path : #{new_pfx_output_path}"
+ # puts "Password : #{new_resource.pfx_password}"
+ powershell_exec(pfx_ps_cmd(resolve_thumbprint(new_resource.source), store_location: ps_cert_location, store_name: new_resource.store_name, output_path: new_pfx_output_path, password: new_resource.pfx_password ))
+ pkcs12 = OpenSSL::PKCS12.new(::File.binread(new_pfx_output_path), new_resource.pfx_password)
+ f = ::File.open(new_resource.output_path, "w")
+ f.write(pkcs12.key.to_s)
+ f.flush
+ f.close
+ end
+
+ def get_file_extension(file_name)
+ type = url_or_file?(file_name)
+
+ if type == "file"
+ ::File.extname(file_name)
+ elsif type == "url"
+ require 'open-uri'
+ uri = URI.parse(file_name)
+ output_file = ::File.basename(uri.path)
+ ::File.extname(output_file)
+ end
+
+ end
+
+ def get_file_name(path_name)
+ type = url_or_file?(path_name)
+
+ if type == "file"
+ ::File.extname(path_name)
+ elsif type == "url"
+ require 'open-uri'
+ uri = URI.parse(path_name)
+ ::File.basename(uri.path)
+ end
+
+ end
+
+ # did I get passed a file, a url, or a mistake?
+ def url_or_file?(source)
+ require 'uri'
+
+ uri = URI.parse(source)
+
+ if source == uri.kind_of?(URI::HTTP) || uri.kind_of?(URI::HTTPS)
+ "url"
+ elsif ::File.file?(source)
+ "file"
+ else
+ raise Chef::Exceptions::FileNotFound, "Invalid Source File"
+ end
end
# Thumbprints should be exactly 40 Hex characters
def valid_thumbprint?(string)
- string.scan(/\H/).empty? && string.length == 40
+ string.match?(/[0-9A-Fa-f]/) && string.length == 40
end
def get_thumbprint(store_name, location, source)
@@ -192,7 +286,6 @@ class Chef
def resolve_thumbprint(thumbprint)
return thumbprint if valid_thumbprint?(thumbprint)
-
powershell_exec!(get_thumbprint(new_resource.store_name, ps_cert_location, new_resource.source)).result
end
@@ -205,45 +298,11 @@ class Chef
def verify_cert(thumbprint = new_resource.source)
store = ::Win32::Certstore.open(new_resource.store_name, store_location: native_cert_location)
- store.valid?(resolve_thumbprint(thumbprint))
- end
-
- def show_or_store_cert(cert_obj)
- if new_resource.cert_path
- export_cert(cert_obj, new_resource.cert_path)
- if ::File.size(new_resource.cert_path) > 0
- Chef::Log.info("Certificate export in #{new_resource.cert_path}")
- else
- ::File.delete(new_resource.cert_path)
- end
- else
- Chef::Log.info(cert_obj.display)
- end
- end
-
- def export_cert(cert_obj, cert_path)
- out_file = ::File.new(cert_path, "w+")
- case ::File.extname(cert_path)
- when ".pem"
- out_file.puts(cert_obj.to_pem)
- when ".der"
- out_file.puts(cert_obj.to_der)
- when ".cer"
- cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CER").stdout
- out_file.puts(cert_out)
- when ".crt"
- cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CRT").stdout
- out_file.puts(cert_out)
- when ".pfx"
- cert_out = shell_out("openssl pkcs12 -export -nokeys -in #{cert_obj.to_pem} -outform PFX").stdout
- out_file.puts(cert_out)
- when ".p7b"
- cert_out = shell_out("openssl pkcs7 -export -nokeys -in #{cert_obj.to_pem} -outform P7B").stdout
- out_file.puts(cert_out)
+ if new_resource.pfx_password.nil?
+ store.valid?(resolve_thumbprint(thumbprint), store_location: native_cert_location, store_name: new_resource.store_name )
else
- Chef::Log.info("Supported certificate format .pem, .der, .cer, .crt, .pfx and .p7b")
+ store.valid?(resolve_thumbprint(thumbprint), store_location: native_cert_location, store_name: new_resource.store_name)
end
- out_file.close
end
# this array structure is solving 2 problems. The first is that we need to have support for both the CurrentUser AND LocalMachine stores
@@ -252,6 +311,23 @@ class Chef
new_resource.user_store ? "CurrentUser" : "LocalMachine"
end
+ def pfx_ps_cmd(thumbprint, store_location: "LocalMachine", store_name: "My", output_path:, password: )
+ puts "My thumbprint is : #{thumbprint}"
+ puts "My store location is : #{store_location}"
+ puts "My store name is : #{store_name}"
+ puts "My output path is : #{output_path}"
+ puts "My password is : #{password}"
+ require "pry"
+ binding.pry
+ <<-CMD
+ $mypwd = ConvertTo-SecureString -String "#{password}" -Force -AsPlainText
+ Get-ChildItem -path cert:\\#{store_location}\\#{store_name} -Recurse
+ $cert = Get-ChildItem -path cert:\\#{store_location}\\#{store_name} -Recurse | Where { $_.Thumbprint -eq "#{thumbprint.upcase}" }
+ # | Export-PfxCertificate -FilePath #{output_path} -Password $mypwd
+ Export-PfxCertificate -Cert $cert -FilePath "#{output_path}" -Password $mypwd
+ CMD
+ end
+
def native_cert_location
new_resource.user_store ? CERT_SYSTEM_STORE_CURRENT_USER : CERT_SYSTEM_STORE_LOCAL_MACHINE
end
@@ -331,7 +407,41 @@ class Chef
# @raise [OpenSSL::PKCS12::PKCS12Error] When incorrect password is provided for PFX certificate
#
def fetch_cert_object(ext)
- contents = ::File.binread(new_resource.source)
+ type = url_or_file?(new_resource.source)
+
+ if type == "file"
+ begin
+ ::File.exist?(new_resource.source)
+ contents = ::File.binread(new_resource.source)
+ rescue => exception
+ message = "Unable to load the certificate object from the specified localpath : #{new_resource.source}\n"
+ message << exception.message
+ raise Chef::Exceptions::FileNotFound, message
+ end
+ elsif type == "url"
+ require 'uri'
+ uri = URI(new_resource.source)
+ state = uri.is_a?(URI::HTTP) && !uri.host.nil? ? true : false
+ if state
+ begin
+ output_file = get_file_name(new_resource.source)
+ local_path = ::File.join(Chef::Config[:file_cache_path], output_file)
+ @local_pfx_path = local_path
+ ::File.open(local_path, "wb") do |file|
+ file.write URI.open(new_resource.source).read
+ end
+ rescue => exception
+ message = "Not Able to Download Certificate Object at the URL specified : #{new_resource.source}\n"
+ message << exception.message
+ raise Chef::Exceptions::FileNotFound, message
+ end
+
+ contents = ::File.binread(local_path)
+
+ else
+ raise Chef::Exceptions::InvalidRemoteFileURI, "Not Able to Download Certificate Object at the URL specified : #{new_resource.source}"
+ end
+ end
case ext
when ".pfx"
@@ -348,24 +458,76 @@ class Chef
end
end
+ def export_cert(cert_obj, output_path:, store_name:, store_location:, pfx_password:)
+ # Delete the cert if it exists. This is non-destructive in that it only removes the file and not the entire path.
+ # We want to ensure we're not randomly loading an old stinky cert.
+ if ::File.exists?(output_path)
+ ::File.delete(output_path)
+ end
+
+ unless ::File.directory?(::File.dirname(output_path))
+ FileUtils.mkdir_p(::File.dirname(output_path))
+ end
+
+ out_file = ::File.new(output_path, "w+")
+
+ case ::File.extname(output_path)
+ when ".pem"
+ out_file.puts(cert_obj)
+ when ".der"
+ out_file.puts(cert_obj.to_der)
+ when ".cer"
+ cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CER").stdout
+ out_file.puts(cert_out)
+ when ".crt"
+ cert_out = shell_out("openssl x509 -text -inform DER -in #{cert_obj} -outform CRT").stdout
+ out_file.puts(cert_out)
+ when ".pfx"
+ pfx_ps_cmd(resolve_thumbprint(new_resource.source), store_location: store_location, store_name: store_name, output_path: output_path, password: pfx_password )
+ when ".p7b"
+ cert_out = shell_out("openssl pkcs7 -export -nokeys -in #{cert_obj.to_pem} -outform P7B").stdout
+ out_file.puts(cert_out)
+ when ".key"
+ out_file.puts(cert_obj)
+ else
+ Chef::Log.info("Supported certificate format .pem, .der, .cer, .crt, and .p7b")
+ end
+
+ out_file.close
+ end
+
# Imports the certificate object into cert store
#
# @param cert_objs [OpenSSL::X509::Certificate] Object containing certificate's attributes
#
# @param is_pfx [Boolean] true if we want to import a PFX certificate
#
- def import_certificates(cert_objs, is_pfx)
+ def import_certificates(cert_objs, is_pfx, store_name: new_resource.store_name, store_location: native_cert_location)
[cert_objs].flatten.each do |cert_obj|
- thumbprint = OpenSSL::Digest.new("SHA1", cert_obj.to_der).to_s # Fetch its thumbprint
- # Need to check if return value is Boolean:true
- # If not then the given certificate should be added in certstore
- if verify_cert(thumbprint) == true
- Chef::Log.debug("Certificate is already present")
+ # thumbprint = OpenSSL::Digest.new("SHA1", cert_obj.to_der).to_s
+ # pkcs = OpenSSL::PKCS12.new(cert_obj, new_resource.pfx_password)
+ # cert = OpenSSL::X509::Certificate.new(pkcs.certificate.to_pem)
+ thumbprint = OpenSSL::Digest.new("SHA1", cert_obj.to_der).to_s
+ if is_pfx
+ if verify_cert(thumbprint) == true
+ Chef::Log.debug("Certificate is already present")
+ else
+ type = url_or_file?(new_resource.source)
+ if type == "file"
+ converge_by("Creating a PFX #{new_resource.source} for Store #{new_resource.store_name}") do
+ add_pfx_cert(new_resource.source)
+ end
+ elsif type == "url"
+ converge_by("Creating a PFX #{@local_pfx_path} for Store #{new_resource.store_name}") do
+ add_pfx_cert(@local_pfx_path)
+ end
+ end
+ end
else
- converge_by("Adding certificate #{new_resource.source} into #{ps_cert_location} Store #{new_resource.store_name}") do
- if is_pfx
- add_pfx_cert
- else
+ if verify_cert(thumbprint) == true
+ Chef::Log.debug("Certificate is already present")
+ else
+ converge_by("Creating a certificate #{new_resource.source} for Store #{new_resource.store_name}") do
add_cert(cert_obj)
end
end
diff --git a/spec/functional/resource/windows_certificate2_spec.rb b/spec/functional/resource/windows_certificate2_spec.rb
new file mode 100644
index 0000000000..7852a88155
--- /dev/null
+++ b/spec/functional/resource/windows_certificate2_spec.rb
@@ -0,0 +1,168 @@
+# Author: Nimesh Patni (nimesh.patni@msystechnologies.com)
+# Copyright:: Copyright (c) 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 "spec_helper"
+require "chef/mixin/powershell_exec"
+require "chef/resource/windows_certificate"
+
+describe Chef::Resource::WindowsCertificate, :windows_only do
+ include Chef::Mixin::PowershellExec
+
+ def create_store(store_location: "LocalMachine", store_name: store)
+ powershell_exec <<~EOC
+ New-Item -Path Cert:\\#{store_location}\\#{store_name}
+ EOC
+ end
+
+ def delete_store(store_location: "LocalMachine", store_name: store)
+ powershell_exec <<~EOC
+ Remove-Item -Path Cert:\\#{store_location}\\#{store_name} -Recurse
+ EOC
+ end
+
+ def certificate_count(store_location: "LocalMachine", store_name: store)
+ powershell_exec(<<~EOC).result.to_i
+ (Get-ChildItem -Force -Path Cert:\\#{store_location}\\#{store_name} | measure).Count
+ EOC
+ end
+
+ let(:password) { "P@ssw0rd!" }
+ let(:store) { "Chef-Functional-Test" }
+ # let(:store_name) { "MY" }
+ let(:store_location) { "LocalMachine" }
+ let(:download_cert_url) { "https://testingchef.blob.core.windows.net/files/test.cer?sv=2020-02-10&ss=b&srt=sco&sp=rlax&se=2022-03-20T01:20:15Z&st=2021-03-19T17:20:15Z&spr=https&sig=nMmvTTXp%2Fn0%2FYizBV8BzhjRJ%2Bmk%2BxYZ9529yOfqDxjQ%3D" }
+ let(:cert_output_path) { ::File.join(Chef::Config[:file_cache_path], "output.cer") }
+ let(:pfx_output_path) { ::File.join(Chef::Config[:file_cache_path], "output.pfx") }
+ let(:key_output_path) { ::File.join(Chef::Config[:file_cache_path], "output.key") }
+ let(:cer_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.cer") }
+ let(:base64_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "base64_test.cer") }
+ let(:pem_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.pem") }
+ let(:p7b_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.p7b") }
+ let(:pfx_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.pfx") }
+ let(:tests_thumbprint) { "e45a4a7ff731e143cf20b8bfb9c7c4edd5238bb3" }
+ let(:other_cer_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "othertest.cer") }
+ let(:others_thumbprint) { "6eae1deefaf59daf1a97c9ceeff39c98b3da38cb" }
+ let(:p7b_thumbprint) { "f867e25b928061318ed2c36ca517681774b06260" }
+ let(:p7b_nested_thumbprint) { "dc395eae6be5b69951b8b6e1090cfc33df30d2cd" }
+
+ let(:resource) do
+ run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)
+ Chef::Resource::WindowsCertificate.new("ChefFunctionalTest", run_context).tap do |r|
+ r.store_name = store
+ end
+ end
+
+ before do
+ # Bypass validation of the store name so we can use a fake test store.
+ allow_any_instance_of(Chef::Mixin::ParamsValidate)
+ .to receive(:_pv_equal_to)
+ .with({ store_name: store }, :store_name, anything)
+ .and_return(true)
+
+ create_store
+
+ end
+
+ after { delete_store }
+
+ describe "action: verify" do
+ it "fails with no certificates in the store" do
+ # expect(Chef::Log).to receive(:info).with("Certificate not found")
+
+ # resource.source = tests_thumbprint
+ # resource.run_action(:verify)
+
+ # expect(resource).not_to be_updated_by_last_action
+ end
+ end
+
+ describe "action: fetch" do
+ # context "with no certificate in the store" do
+ # it "throws an error with no certificates in the store" do
+ # expect(Chef::Log).not_to receive(:info)
+ # resource.source = others_thumbprint
+ # resource.output_path = cert_output_path
+ # expect { resource.run_action :fetch }.to raise_error(ArgumentError)
+ # end
+ # end
+
+ # context "with a certificate in the store" do
+ # before do
+ # resource.source = cer_path
+ # resource.run_action(:create)
+ # end
+
+ # it "succeeds with a valid thumbprint" do
+ # resource.source = tests_thumbprint
+ # local_output_path = ::File.join(Chef::Config[:file_cache_path], "test.pem")
+ # resource.output_path = local_output_path
+ # resource.run_action(:fetch)
+ # expect(File.exist?(local_output_path)).to be_truthy
+ # end
+
+ # it "fails with an invalid thumbprint", :focus do
+ # expect(Chef::Log).not_to receive(:info)
+
+ # resource.source = others_thumbprint
+
+ # Dir.mktmpdir do |dir|
+ # path = File.join(dir, "test.pem")
+
+ # resource.output_path = path
+ # expect { resource.run_action :fetch }.to raise_error(ArgumentError)
+ # end
+ # end
+ # end
+
+ context "with a pfx/pkcs12 object in the store" do
+ before do
+ resource.source = pfx_path
+ resource.pfx_password = password
+ resource.exportable = true
+ resource.run_action(:create)
+ end
+
+ # it "verfies" do
+ # resource.source = tests_thumbprint
+ # resource.run_action(:verify)
+ # end
+
+ it "exports a PFX file with a valid thumbprint", :focus do
+ resource.source = tests_thumbprint
+ resource.pfx_password = password
+ resource.output_path = pfx_output_path
+ resource.run_action(:fetch)
+ expect(File.exist?(pfx_output_path)).to be_truthy
+ end
+
+ # it "exports a key file with a valid thumbprint" do
+ # resource.source = tests_thumbprint
+ # resource.pfx_password = password
+ # resource.output_path = key_output_path
+ # resource.run_action(:fetch)
+ # expect(File.exist?(key_output_path)).to be_truthy
+ # end
+
+ # it "throws an exception when output_path is not specified" do
+ # resource.source = tests_thumbprint
+ # resource.pfx_password = password
+ # expect { resource.run_action :fetch }.to raise_error(::Chef::Exceptions::ResourceNotFound)
+ # end
+ end
+ end
+
+end
diff --git a/spec/functional/resource/windows_certificate_spec.rb b/spec/functional/resource/windows_certificate_spec.rb
index b5d0484e0c..334671575e 100644
--- a/spec/functional/resource/windows_certificate_spec.rb
+++ b/spec/functional/resource/windows_certificate_spec.rb
@@ -22,26 +22,32 @@ require "chef/resource/windows_certificate"
describe Chef::Resource::WindowsCertificate, :windows_only do
include Chef::Mixin::PowershellExec
- def create_store
+ def create_store(store_location: "LocalMachine", store_name: store)
powershell_exec <<~EOC
- New-Item -Path Cert:\\LocalMachine\\#{store}
+ New-Item -Path Cert:\\#{store_location}\\#{store_name}
EOC
end
- def delete_store
+ def delete_store(store_location: "LocalMachine", store_name: store)
powershell_exec <<~EOC
- Remove-Item -Path Cert:\\LocalMachine\\#{store} -Recurse
+ Remove-Item -Path Cert:\\#{store_location}\\#{store_name} -Recurse
EOC
end
- def certificate_count
+ def certificate_count(store_location: "LocalMachine", store_name: store)
powershell_exec(<<~EOC).result.to_i
- (Get-ChildItem -Force -Path Cert:\\LocalMachine\\#{store} | measure).Count
+ (Get-ChildItem -Force -Path Cert:\\#{store_location}\\#{store_name} | measure).Count
EOC
end
let(:password) { "P@ssw0rd!" }
let(:store) { "Chef-Functional-Test" }
+ let(:store_name) { "MY" }
+ let(:store_location) { "LocalMachine" }
+ let(:download_cert_url) { "https://testingchef.blob.core.windows.net/files/test.cer?sv=2020-02-10&ss=b&srt=sco&sp=rlax&se=2022-03-20T01:20:15Z&st=2021-03-19T17:20:15Z&spr=https&sig=nMmvTTXp%2Fn0%2FYizBV8BzhjRJ%2Bmk%2BxYZ9529yOfqDxjQ%3D" }
+ let(:cert_output_path) { ::File.join(Chef::Config[:file_cache_path], "output.cer") }
+ let(:pfx_output_path) { ::File.join(Chef::Config[:file_cache_path], "output.pfx") }
+ let(:key_output_path) { ::File.join(Chef::Config[:file_cache_path], "output.key") }
let(:cer_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.cer") }
let(:base64_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "base64_test.cer") }
let(:pem_path) { File.join(CHEF_SPEC_DATA, "windows_certificates", "test.pem") }
@@ -103,6 +109,14 @@ describe Chef::Resource::WindowsCertificate, :windows_only do
expect(resource).to be_updated_by_last_action
end
+ it "can add a certificate from a valid url" do
+ resource.source = download_cert_url
+ resource.run_action(:create)
+
+ expect(certificate_count).to eq(1)
+ expect(resource).to be_updated_by_last_action
+ end
+
it "can add a base64 encoded certificate idempotently" do
resource.source = base64_path
resource.run_action(:create)
@@ -200,7 +214,7 @@ describe Chef::Resource::WindowsCertificate, :windows_only do
resource.run_action(:create)
end
- it "succeeds with the main certificate's thumbprint" do
+ it "succeeds with the main certificate's thumbprint", :focus do
expect(Chef::Log).to receive(:info).with("Certificate is valid")
resource.source = p7b_thumbprint
@@ -230,13 +244,13 @@ describe Chef::Resource::WindowsCertificate, :windows_only do
end
describe "action: fetch" do
- it "does nothing with no certificates in the store" do
- expect(Chef::Log).not_to receive(:info)
-
- resource.source = tests_thumbprint
- resource.run_action(:fetch)
-
- expect(resource).not_to be_updated_by_last_action
+ context "with no certificate in the store" do
+ it "throws an error with no certificates in the store" do
+ expect(Chef::Log).not_to receive(:info)
+ resource.source = others_thumbprint
+ resource.output_path = cert_output_path
+ expect { resource.run_action :fetch }.to raise_error(ArgumentError)
+ end
end
context "with a certificate in the store" do
@@ -247,18 +261,10 @@ describe Chef::Resource::WindowsCertificate, :windows_only do
it "succeeds with a valid thumbprint" do
resource.source = tests_thumbprint
-
- Dir.mktmpdir do |dir|
- path = File.join(dir, "test.pem")
- expect(Chef::Log).to receive(:info).with("Certificate export in #{path}")
-
- resource.cert_path = path
- resource.run_action(:fetch)
-
- expect(File.exist?(path)).to be_truthy
- end
-
- expect(resource).not_to be_updated_by_last_action
+ local_output_path = ::File.join(Chef::Config[:file_cache_path], "test.pem")
+ resource.output_path = local_output_path
+ resource.run_action(:fetch)
+ expect(File.exist?(local_output_path)).to be_truthy
end
it "fails with an invalid thumbprint" do
@@ -269,23 +275,82 @@ describe Chef::Resource::WindowsCertificate, :windows_only do
Dir.mktmpdir do |dir|
path = File.join(dir, "test.pem")
- resource.cert_path = path
- resource.run_action(:fetch)
-
- expect(File.exist?(path)).to be_falsy
+ resource.output_path = path
+ expect { resource.run_action :fetch }.to raise_error(ArgumentError)
end
- expect(resource).not_to be_updated_by_last_action
+ end
+ end
+
+ context "with a pfx/pkcs12 object in the store" do
+ before do
+ resource.source = pfx_path
+ resource.pfx_password = password
+ resource.exportable = true
+ resource.run_action(:create)
+ end
+
+ it "exports a PFX file with a valid thumbprint", :focus do
+ resource.source = tests_thumbprint
+ resource.pfx_password = password
+ resource.output_path = pfx_output_path
+ resource.run_action(:fetch)
+ expect(File.exist?(pfx_output_path)).to be_truthy
+ end
+
+ it "exports a key file with a valid thumbprint" do
+ resource.source = tests_thumbprint
+ resource.pfx_password = password
+ resource.output_path = key_output_path
+ resource.run_action(:fetch)
+ expect(File.exist?(key_output_path)).to be_truthy
+ end
+
+ it "throws an exception when output_path is not specified" do
+ resource.source = tests_thumbprint
+ resource.pfx_password = password
+ expect { resource.run_action :fetch }.to raise_error(::Chef::Exceptions::ResourceNotFound)
end
end
end
- describe "action: delete" do
- it "does nothing when attempting to delete a certificate that doesn't exist" do
- expect(Chef::Log).to receive(:debug).with("Certificate not found")
+ # describe "action: fetch pfx objects" do
+ # before do
+ # resource.source = pfx_path
+ # resource.pfx_password = password
+ # resource.exportable = true
+ # resource.run_action(:create)
+ # end
+
+ # context "with a pfx/pkcs12 object in the store" do
+ # it "exports a PFX file with a valid thumbprint" do
+ # resource.source = tests_thumbprint
+ # resource.pfx_password = password
+ # resource.output_path = pfx_output_path
+ # resource.run_action(:fetch)
+ # expect(File.exist?(pfx_output_path)).to be_truthy
+ # end
+
+ # it "exports a key file with a valid thumbprint" do
+ # resource.source = tests_thumbprint
+ # resource.pfx_password = password
+ # resource.output_path = key_output_path
+ # resource.run_action(:fetch)
+ # expect(File.exist?(key_output_path)).to be_truthy
+ # end
+
+ # it "throws an exception when output_path is not specified" do
+ # resource.source = tests_thumbprint
+ # resource.pfx_password = password
+ # expect { resource.run_action :fetch }.to raise_error(::Chef::Exceptions::ResourceNotFound)
+ # end
+ # end
+ # end
+ describe "action: delete" do
+ it "throws an argument error when attempting to delete a certificate that doesn't exist" do
resource.source = tests_thumbprint
- resource.run_action(:delete)
+ expect { resource.run_action :delete }.to raise_error(ArgumentError)
end
it "deletes an existing certificate while leaving other certificates alone" do
@@ -303,7 +368,7 @@ describe Chef::Resource::WindowsCertificate, :windows_only do
expect(certificate_count).to eq(1)
expect(resource).to be_updated_by_last_action
- resource.run_action(:delete)
+ expect { resource.run_action :delete }.to raise_error(ArgumentError)
expect(certificate_count).to eq(1)
expect(resource).not_to be_updated_by_last_action