From 748bd870fc4518781be63577ec2ecdf348f0ea12 Mon Sep 17 00:00:00 2001 From: Derek Dunn Date: Thu, 10 Aug 2023 12:22:13 -0400 Subject: [PATCH 01/26] Create legacy IIS to IISU/WinCert upgrade scripts and documentation --- LegacyIISMigrationGuide.md | 162 +++++++ Migration-Scripts/CreateIISUCertStoreType.sql | 454 ++++++++++++++++++ Migration-Scripts/CreateWinCertStoreType.sql | 314 ++++++++++++ Migration-Scripts/DeleteIISStores.sql | 98 ++++ .../UpgradeIISPersonalToIISU.sql | 231 +++++++++ .../UpgradeIISPersonalToWinCert.sql | 224 +++++++++ .../UpgradeIISRevokedAndRootsToWinCert.sql | 196 ++++++++ 7 files changed, 1679 insertions(+) create mode 100644 LegacyIISMigrationGuide.md create mode 100644 Migration-Scripts/CreateIISUCertStoreType.sql create mode 100644 Migration-Scripts/CreateWinCertStoreType.sql create mode 100644 Migration-Scripts/DeleteIISStores.sql create mode 100644 Migration-Scripts/UpgradeIISPersonalToIISU.sql create mode 100644 Migration-Scripts/UpgradeIISPersonalToWinCert.sql create mode 100644 Migration-Scripts/UpgradeIISRevokedAndRootsToWinCert.sql diff --git a/LegacyIISMigrationGuide.md b/LegacyIISMigrationGuide.md new file mode 100644 index 0000000..84115a7 --- /dev/null +++ b/LegacyIISMigrationGuide.md @@ -0,0 +1,162 @@ +# Built-in IIS Certificate Store Type Migration Guide + +As of Keyfactor Command v11, the built-in IIS certificate store types (IIS Personal, IIS Roots, and IIS Revoked) have been deprecated. This guide will instruct you on how to migrate the built-in IIS certificate store types to the open source iis-orchestrator certificate store types (IISU and WinCert) that can be found on the [Keyfactor GitHub page](https://github.com/Keyfactor/iis-orchestrator). + +# Prerequisites + +- All orchestrators that are currently being used to orchestrate the built-in IIS store types must support the certificate store type that the built-in stores are being migrated to (IISU, WinCert, or both). +- Ensure that you have a restorable database backup created before you begin the upgrade. + +# Migration Scripts Usage + +There are six SQL scripts that are needed for this migration: + +
+ +Creation Scripts + +## CreateIISUCertStoreType + +This script creates the IISU certificate store type. + +## CreateWinCertStoreType + +This script creates the WinCert certificate store type. + +
+ +
+ +Upgrade Scripts + +## UpgradeIISRevokedAndRootsToWinCert + +This script creates a 'WinCert' certificate store copy for every 'IIS Revoked' and 'IIS Roots' certificate store. It will also create a 'WinCert' version of every 'IIS Revoked' and 'IIS Roots' certificate store container. + +**Notes** + +- This script does not delete the IIS certificate stores or containers. +- By default, the orchestrator will use its service account credentials to connect to the new certificate stores. If other credentials should be used instead, they should be configured for each store from the Command Certificate Stores page. + +This script accepts three parameters that allow configuration of WinRM: + +| Parameter | Type | Valid Values | Default Value| Description | +|----|----|----|----|----| +|@winrm_protocol|NVARCHAR(5)|'https' or 'http'|https|The protocol that WinRM will use for interacting with the certificate stores| +|@winrm_port|INT|1 - 65535|5986|The port that WinRM will use for interacting with the certificate stores| +|@spnwithport|NVARCHAR(5)|'true' or 'false'|false|If set to 'true,' the `-IncludePortInSPN` flag will be set when WinRM creates the remote PowerShell connection| + +## UpgradeIISPersonalToIISU + +This script creates an 'IISU' certificate store copy for a provided list of 'IIS Personal' certificate stores. It will also create an 'IISU' version of every 'IIS Personal' certificate store container. + +**Notes** + +- This script does not delete the IIS certificate stores or containers. +- By default, the orchestrator will use its service account credentials to connect to the new certificate stores. If other credentials should be used instead, they should be configured for each store from the Command Certificate Stores page. + + +This script accepts four parameters: + +| Parameter | Type | Valid Values | Default Value| Description | +|----|----|----|----|----| +|@comma_separated_store_ids|NVARCHAR(MAX)|* or a comma separated list of certificate store IDs. ex: 6A79C7A3-1A9B-413B-9C6E-571EA52B9E31,3929B040-299C-4EDF-98A4-EF0FFC21DAF9,C8A62749-10C3-4441-A0CC-AD83CA9051B5||A comma separated list of IDs of the IIS Personal certificate stores that you would like to migrate. If you would like to migrate all of the IIS Personal stores to this store type, you can provide '*' instead of a comma separated list.| +|@store_path|NVARCHAR(15)|'My' or 'WebHosting'|My|Used to select which Windows Certificate store that holds the IIS bound certificate. 'My' for the personal store, 'WebHosting' for the web hosting store| +|@winrm_protocol|NVARCHAR(5)|'https' or 'http'|https|The protocol that WinRM will use for interacting with the certificate stores| +|@winrm_port|INT|1 - 65535|5986|The port that WinRM will use for interacting with the certificate stores| +|@spnwithport|NVARCHAR(5)|'true' or 'false'|false|If set to 'true,' the `-IncludePortInSPN` flag will be set when WinRM creates the remote PowerShell connection| + +## UpgradeIISPersonalToWinCert + +This script creates a 'WinCert' certificate store copy for a provided list of 'IIS Personal' certificate stores. It will also create a 'WinCert' version of every 'IIS Personal' certificate store container. + +**Notes** + +- This script does not delete the IIS certificate stores or containers. +- By default, the orchestrator will use its service account credentials to connect to the new certificate stores. If other credentials should be used instead, they should be configured for each store from the Command Certificate Stores page. + +This script accepts four parameters: + +| Parameter | Type | Valid Values | Default Value| Description | +|----|----|----|----|----| +|@comma_separated_store_ids|NVARCHAR(MAX)|* or a comma separated list of certificate store IDs. ex: 6A79C7A3-1A9B-413B-9C6E-571EA52B9E31,3929B040-299C-4EDF-98A4-EF0FFC21DAF9,C8A62749-10C3-4441-A0CC-AD83CA9051B5||A comma separated list of IDs of the IIS Personal certificate stores that you would like to migrate. If you would like to migrate all of the IIS Personal stores to this store type, you can provide '*' instead of a comma separated list.| +|@winrm_protocol|NVARCHAR(5)|'https' or 'http'|https|The protocol that WinRM will use for interacting with the certificate stores| +|@winrm_port|INT|1 - 65535|5986|The port that WinRM will use for interacting with the certificate stores| +|@spnwithport|NVARCHAR(5)|'true' or 'false'|false|If set to 'true,' the `-IncludePortInSPN` flag will be set when WinRM creates the remote PowerShell connection| + +
+ +
+ +Deletion Scripts + +## DeleteIISStores + +This script will delete all IIS Personal, IIS Roots, and IIS Revoked certificate store types, certificate stores, and certificate store containers. + +
+ +# Migration Order + +In order for a successful migration, the guides contained in this document should be completed in the following order: + +1. If you have any IIS Roots or IIS Revoked stores, follow this guide: ['IIS Roots' and 'IIS Revoked' Migration Guide](#iis-roots-and-iis-revoked-migration-guide) +1. If you have any IIS Personal stores, follow this guide: [IIS Personal Migration Guide](#iis-personal-migration-guide) +1. Finally, Follow the [Migration Finalization Guide](#migration-finalization-guide) + +## IIS Roots and IIS Revoked Migration Guide + +
+ +Expand for steps + +The 'IIS Roots' and 'IIS Revoked' certificate store types can only be migrated to the WinCert certificate store type. + +1. Execute the `CreateWinCertStoreType` SQL script to define the WinCert certificate store type if you have not already created this type. +1. Execute the `UpgradeIISRevokedAndRootsToWinCert` script to create WinCert store copies of your 'IIS Roots' and 'IIS Revoked' stores. See [UpgradeIISRevokedAndRootsToWinCert section](#upgradeiisrevokedandrootstowincert) for usage information. + +
+ +## IIS Personal Migration Guide + +The IIS Personal stores can either be migrated to the WinCert store type, the IISU store type, or both, depending on your environment configuration. If you would like to manage all of the certificates in a server's 'Personal' certificate store, you should migrate the store to the WinCert type. If you would like to manage an IIS bound certificate on the server, you should migrate to the IISU store type. + +### IIS Personal to WinCert Migration Guide + +
+ +Expand for steps + +1. If you have not already defined the WinCert store type, execute the `CreateWinCertStoreType` SQL script. +1. If you do not wish to create a WinCert store copy of all of your IIS Personal stores or have unique WinRM configurations for each certificate store, you should collect a list of IDs of the IIS Personal certificate stores that you wish to migrate at this time. +1. Execute the `UpgradeIISPersonalToWinCert` script to create WinCert store copies of your 'IIS Personal' stores. See [UpgradeIISPersonalToWinCert section](#upgradeiispersonaltowincert) for usage information. + +
+ +### IIS Personal to IISU Migration Guide + +
+ +Expand for steps + +1. If you have not already defined the IISU store type, execute the `CreateIISUCertStoreType` SQL script. +1. If you do not wish to create aN IISU store copy of all of your IIS Personal stores or have unique configurations for each certificate store, you should collect a list of IDs of the IIS Personal certificate stores that you wish to migrate at this time. +1. Execute the `UpgradeIISPersonalToIISU` script to create WinCert store copies of your 'IIS Personal' stores. See [UpgradeIISPersonalToIISU section](#upgradeiispersonaltoiisu) for usage information. + +
+ +## Migration Finalization Guide + +At this point, you should have an IISU or WinCert store copy of every legacy IIS store type and store container. Follow these steps to complete the migration: + +
+ +Expand for steps + +1. Review the certificate stores that were created in the Command portal. Ensure that there is a copy of each legacy IIS store with the desired store type (either IISU or WinCert). If desired, wait for any scheduled inventory jobs to run on the new stores and ensure that they return the expected certificates. +1. Review the certificate store containers that were created in the Command portal. If you upgraded IIS Personal stores to both WinCert and IISU store types, all IIS Personal certificate store containers will have an IISU and a WinCert store type copy (they will have names that end in either ' - Upgraded WinCert' or ' - Upgraded IISU'). You will need to determine if you would like to keep the IISU or the WinCert copy of each container set then delete the one that you do not wish to keep. If you would like to keep both, you must rename one of the two. This is necessary, as the deletion step will remove the ' - Upgraded ...' text from the end of the store names; without updating the names, execution of the deletion script will result in invalid stores with the same name. +1. Execute the `DeleteIISStores` script to remove all legacy IIS stores, store types, and containers. This script will also remove the ' - Upgraded ...' text from the migrated container names. + +
+ +At this point, all of your legacy IIS stores should be upgraded to either the IISU store type or the WinCert store type, and all legacy IIS stores, store types, containers, and jobs should be removed from Command. \ No newline at end of file diff --git a/Migration-Scripts/CreateIISUCertStoreType.sql b/Migration-Scripts/CreateIISUCertStoreType.sql new file mode 100644 index 0000000..c48a372 --- /dev/null +++ b/Migration-Scripts/CreateIISUCertStoreType.sql @@ -0,0 +1,454 @@ +SET NOCOUNT ON + +BEGIN TRY + BEGIN TRANSACTION + + DECLARE @registration_index AS INT; + SELECT @registration_index = MAX([ServerRegistration]) FROM [cms_agents].[CertStoreTypes]; + SET @registration_index = COALESCE(@registration_index + 1, 0); + + DECLARE @current_storetype_id AS INT; + SELECT @current_storetype_id = IDENT_CURRENT('[cms_agents].[CertStoreTypes]') + 1; + + DECLARE @enrollment_job_id uniqueidentifier = NEWID(); + DECLARE @inventory_job_id uniqueidentifier = NEWID(); + DECLARE @management_job_id uniqueidentifier = NEWID(); + + -- create enrollment job type + INSERT INTO [cms_agents].[JobTypes] ( + [Id] + ,[ConfigurationEndpoint] + ,[CompletionEndpoint] + ,[SubmitEndpoint] + ,[Name] + ,[Description] + ,[Capability] + ) + VALUES + ( + @enrollment_job_id, -- Id + 'AnyReenrollment/Configure', -- ConfigurationEndpoint + 'AnyReenrollment/Complete', -- CompletionEndpoint + 'AnyReenrollment/Submit', -- SubmitEndpoint + 'IISUReenrollment', -- Name + 'IIS Bound Certificate Reenrollment', -- Description + 'CertStores.IISU.Reenrollment' -- Capability + ); + + -- create inventory job type + INSERT INTO [cms_agents].[JobTypes] ( + [Id] + ,[ConfigurationEndpoint] + ,[CompletionEndpoint] + ,[SubmitEndpoint] + ,[Name] + ,[Description] + ,[Capability] + ) + VALUES + ( + @inventory_job_id, -- Id + 'AnyInventory/Configure', -- ConfigurationEndpoint + 'AnyInventory/Complete', -- CompletionEndpoint + NULL, -- SubmitEndpoint + 'IISUInventory', -- Name + 'IIS Bound Certificate Inventory', -- Description + 'CertStores.IISU.Inventory' -- Capability + ); + + -- create management job type + INSERT INTO [cms_agents].[JobTypes] ( + [Id] + ,[ConfigurationEndpoint] + ,[CompletionEndpoint] + ,[SubmitEndpoint] + ,[Name] + ,[Description] + ,[Capability] + ) + VALUES + ( + @management_job_id, -- Id + 'AnyManagement/Configure', -- ConfigurationEndpoint + 'AnyManagement/Complete', -- CompletionEndpoint + NULL, -- SubmitEndpoint + 'IISUManagement', -- Name + 'IIS Bound Certificate Management', -- Description + 'CertStores.IISU.Management' -- Capability + ); + + -- create IISU certificate store type + INSERT INTO [cms_agents].[CertStoreTypes] ( + [Name] + ,[ShortName] + ,[LocalStore] + ,[ServerRegistration] + ,[ImportType] + ,[InventoryJobType] + ,[ManagementJobType] + ,[AddSupported] + ,[RemoveSupported] + ,[CreateSupported] + ,[DiscoveryJobType] + ,[EnrollmentJobType] + ,[InventoryEndpoint] + ,[EntryPasswordSupported] + ,[StorePasswordRequired] + ,[PrivateKeyAllowed] + ,[StorePathType] + ,[CustomAliasAllowed] + ,[PowerShell] + ,[PasswordStyle] + ,[BlueprintAllowed] + ) + VALUES + ( + 'IIS Bound Certificate', -- Name + 'IISU', -- ShortName + 0, -- LocalStore + @registration_index, -- ServerRegistration + @current_storetype_id, -- ImportType + @inventory_job_id, -- InventoryJobType + @management_job_id, -- ManagementJobType + 1, -- AddSupported + 1, -- RemoveSupported + 0, -- CreateSupported + NULL, -- DiscoveryJobType + @enrollment_job_id, -- EnrollmentJobType + '/AnyInventory/Update', -- InventoryEndpoint + 0, -- EntryPasswordSupported + 0, -- StorePasswordRequired + 2, -- PrivateKeyAllowed + '["My","WebHosting"]', -- StorePathType + 0, -- CustomAliasAllowed + 0, -- PowerShell + 0, -- PasswordStyle + 0 -- BlueprintAllowed + ); + + -- create WinRm protocol property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'WinRm Protocol', -- Name + 'WinRm Protocol', -- DisplayName + 2, -- Type + 1, -- Required + NULL, -- DependsOn + 'https,http' -- DefaultValue + ); + + -- create WinRm port property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'WinRm Port', -- Name + 'WinRm Port', -- DisplayName + 0, -- Type + 1, -- Required + NULL, -- DependsOn + '5986' -- DefaultValue + ); + + -- create SPN With Port protocol property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'spnwithport', -- Name + 'SPN With Port', -- DisplayName + 1, -- Type + 0, -- Required + NULL, -- DependsOn + 'false' -- DefaultValue + ); + + -- create Server Username property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'ServerUsername', -- Name + 'Server Username', -- DisplayName + 3, -- Type + 0, -- Required + NULL, -- DependsOn + NULL -- DefaultValue + ); + + -- create Server Password property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'ServerPassword', -- Name + 'Server Password', -- DisplayName + 3, -- Type + 0, -- Required + NULL, -- DependsOn + NULL -- DefaultValue + ); + + -- create Use SSL property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'ServerUseSsl', -- Name + 'Use SSL', -- DisplayName + 1, -- Type + 1, -- Required + NULL, -- DependsOn + 'true' -- DefaultValue + ); + + + -- create IIS Site Name entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'SiteName', -- Name + 'IIS Site Name', -- DisplayName + 0, -- Type + 14, -- RequiredWhen + NULL, -- DependsOn + 'Default Web Site', -- DefaultValue + NULL -- Options + ); + + -- create IP Address entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'IPAddress', -- Name + 'IP Address', -- DisplayName + 0, -- Type + 14, -- RequiredWhen + NULL, -- DependsOn + '*', -- DefaultValue + NULL -- Options + ); + + -- create Port entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'Port', -- Name + 'Port', -- DisplayName + 0, -- Type + 14, -- RequiredWhen + NULL, -- DependsOn + '443', -- DefaultValue + NULL -- Options + ); + + -- create Host Name entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'HostName', -- Name + 'Host Name', -- DisplayName + 0, -- Type + 0, -- RequiredWhen + NULL, -- DependsOn + NULL, -- DefaultValue + NULL -- Options + ); + + -- create SNI Support entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'SniFlag', -- Name + 'SNI Support', -- DisplayName + 2, -- Type + 14, -- RequiredWhen + NULL, -- DependsOn + '0 - No SNI', -- DefaultValue + '0 - No SNI,1 - SNI Enabled,2 - Non SNI Binding,3 - SNI Binding' -- Options + ); + + -- create Protocol entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'Protocol', -- Name + 'Protocol', -- DisplayName + 2, -- Type + 14, -- RequiredWhen + NULL, -- DependsOn + 'https', -- DefaultValue + 'https' -- Options + ); + + -- create Crypto Provider Name entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'ProviderName', -- Name + 'Crypto Provider Name', -- DisplayName + 0, -- Type + 0, -- RequiredWhen + NULL, -- DependsOn + NULL, -- DefaultValue + NULL -- Options + ); + + -- create SAN entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'SAN', -- Name + 'SAN', -- DisplayName + 0, -- Type + 8, -- RequiredWhen + NULL, -- DependsOn + NULL, -- DefaultValue + NULL -- Options + ); + + COMMIT TRANSACTION; +END TRY + +BEGIN CATCH + IF (@@TRANCOUNT > 0) + BEGIN + ROLLBACK TRANSACTION; + END + + SELECT + ERROR_MESSAGE() AS ErrorMessage, + ERROR_SEVERITY() AS Severity, + ERROR_STATE() AS ErrorState; +END CATCH + diff --git a/Migration-Scripts/CreateWinCertStoreType.sql b/Migration-Scripts/CreateWinCertStoreType.sql new file mode 100644 index 0000000..21c01a3 --- /dev/null +++ b/Migration-Scripts/CreateWinCertStoreType.sql @@ -0,0 +1,314 @@ +SET NOCOUNT ON + +BEGIN TRY + BEGIN TRANSACTION + + DECLARE @registration_index AS INT; + SELECT @registration_index = MAX([ServerRegistration]) FROM [cms_agents].[CertStoreTypes]; + SET @registration_index = COALESCE(@registration_index + 1, 0); + + DECLARE @current_storetype_id AS INT; + SELECT @current_storetype_id = IDENT_CURRENT('[cms_agents].[CertStoreTypes]') + 1; + + DECLARE @enrollment_job_id uniqueidentifier = NEWID(); + DECLARE @inventory_job_id uniqueidentifier = NEWID(); + DECLARE @management_job_id uniqueidentifier = NEWID(); + + -- create enrollment job type + INSERT INTO [cms_agents].[JobTypes] ( + [Id] + ,[ConfigurationEndpoint] + ,[CompletionEndpoint] + ,[SubmitEndpoint] + ,[Name] + ,[Description] + ,[Capability] + ) + VALUES + ( + @enrollment_job_id, -- Id + 'AnyReenrollment/Configure', -- ConfigurationEndpoint + 'AnyReenrollment/Complete', -- CompletionEndpoint + 'AnyReenrollment/Submit', -- SubmitEndpoint + 'WinCertReenrollment', -- Name + 'Windows Certificate Reenrollment', -- Description + 'CertStores.WinCert.Reenrollment' -- Capability + ); + + -- create inventory job type + INSERT INTO [cms_agents].[JobTypes] ( + [Id] + ,[ConfigurationEndpoint] + ,[CompletionEndpoint] + ,[SubmitEndpoint] + ,[Name] + ,[Description] + ,[Capability] + ) + VALUES + ( + @inventory_job_id, -- Id + 'AnyInventory/Configure', -- ConfigurationEndpoint + 'AnyInventory/Complete', -- CompletionEndpoint + NULL, -- SubmitEndpoint + 'WinCertInventory', -- Name + 'Windows Certificate Inventory', -- Description + 'CertStores.WinCert.Inventory' -- Capability + ); + + -- create management job type + INSERT INTO [cms_agents].[JobTypes] ( + [Id] + ,[ConfigurationEndpoint] + ,[CompletionEndpoint] + ,[SubmitEndpoint] + ,[Name] + ,[Description] + ,[Capability] + ) + VALUES + ( + @management_job_id, -- Id + 'AnyManagement/Configure', -- ConfigurationEndpoint + 'AnyManagement/Complete', -- CompletionEndpoint + NULL, -- SubmitEndpoint + 'WinCertManagement', -- Name + 'Windows Certificate Management', -- Description + 'CertStores.WinCert.Management' -- Capability + ); + + -- create windows certificate store type + INSERT INTO [cms_agents].[CertStoreTypes] ( + [Name] + ,[ShortName] + ,[LocalStore] + ,[ServerRegistration] + ,[ImportType] + ,[InventoryJobType] + ,[ManagementJobType] + ,[AddSupported] + ,[RemoveSupported] + ,[CreateSupported] + ,[DiscoveryJobType] + ,[EnrollmentJobType] + ,[InventoryEndpoint] + ,[EntryPasswordSupported] + ,[StorePasswordRequired] + ,[PrivateKeyAllowed] + ,[StorePathType] + ,[CustomAliasAllowed] + ,[PowerShell] + ,[PasswordStyle] + ,[BlueprintAllowed] + ) + VALUES + ( + 'Windows Certificate', -- Name + 'WinCert', -- ShortName + 0, -- LocalStore + @registration_index, -- ServerRegistration + @current_storetype_id, -- ImportType + @inventory_job_id, -- InventoryJobType + @management_job_id, -- ManagementJobType + 1, -- AddSupported + 1, -- RemoveSupported + 0, -- CreateSupported + NULL, -- DiscoveryJobType + @enrollment_job_id, -- EnrollmentJobType + '/AnyInventory/Update', -- InventoryEndpoint + 0, -- EntryPasswordSupported + 0, -- StorePasswordRequired + 2, -- PrivateKeyAllowed + NULL, -- StorePathType + 0, -- CustomAliasAllowed + 0, -- PowerShell + 0, -- PasswordStyle + 0 -- BlueprintAllowed + ); + + -- create WinRm protocol property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'WinRm Protocol', -- Name + 'WinRm Protocol', -- DisplayName + 2, -- Type + 1, -- Required + NULL, -- DependsOn + 'https,http' -- DefaultValue + ); + + -- create WinRm port property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'WinRm Port', -- Name + 'WinRm Port', -- DisplayName + 0, -- Type + 1, -- Required + NULL, -- DependsOn + '5986' -- DefaultValue + ); + + -- create SPN With Port protocol property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'spnwithport', -- Name + 'SPN With Port', -- DisplayName + 1, -- Type + 0, -- Required + NULL, -- DependsOn + 'false' -- DefaultValue + ); + + -- create Server Username property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'ServerUsername', -- Name + 'Server Username', -- DisplayName + 3, -- Type + 0, -- Required + NULL, -- DependsOn + NULL -- DefaultValue + ); + + -- create Server Password property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'ServerPassword', -- Name + 'Server Password', -- DisplayName + 3, -- Type + 0, -- Required + NULL, -- DependsOn + NULL -- DefaultValue + ); + + -- create Use SSL property + INSERT INTO [cms_agents].[CertStoreTypeProperties] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[Required] + ,[DependsOn] + ,[DefaultValue] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'ServerUseSsl', -- Name + 'Use SSL', -- DisplayName + 1, -- Type + 1, -- Required + NULL, -- DependsOn + 'true' -- DefaultValue + ); + + -- create Crypto Provider Name entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'ProviderName', -- Name + 'Crypto Provider Name', -- DisplayName + 0, -- Type + 0, -- RequiredWhen + NULL, -- DependsOn + NULL, -- DefaultValue + NULL -- Options + ); + + -- create SAN entry parameter + INSERT INTO [cms_agents].[CertStoreTypeEntryParameters] ( + [StoreTypeId] + ,[Name] + ,[DisplayName] + ,[Type] + ,[RequiredWhen] + ,[DependsOn] + ,[DefaultValue] + ,[Options] + ) + VALUES + ( + @current_storetype_id, -- StoreTypeId + 'SAN', -- Name + 'SAN', -- DisplayName + 0, -- Type + 8, -- RequiredWhen + NULL, -- DependsOn + NULL, -- DefaultValue + NULL -- Options + ); + + COMMIT TRANSACTION +END TRY + +BEGIN CATCH + IF (@@TRANCOUNT > 0) + BEGIN + ROLLBACK TRANSACTION; + END + + SELECT + ERROR_MESSAGE() AS ErrorMessage, + ERROR_SEVERITY() AS Severity, + ERROR_STATE() AS ErrorState; +END CATCH \ No newline at end of file diff --git a/Migration-Scripts/DeleteIISStores.sql b/Migration-Scripts/DeleteIISStores.sql new file mode 100644 index 0000000..f7a6438 --- /dev/null +++ b/Migration-Scripts/DeleteIISStores.sql @@ -0,0 +1,98 @@ +SET NOCOUNT ON + +BEGIN TRY + BEGIN TRANSACTION + + DECLARE @IISStoreTypeIds TABLE (Id int); + DECLARE @IISJobTypeIds TABLE (Id uniqueidentifier); + + INSERT INTO @IISStoreTypeIds + SELECT [StoreType] + FROM [cms_agents].[CertStoreTypes] + WHERE [ShortName] = 'IIS'; + + INSERT INTO @IISJobTypeIds + SELECT [Id] + FROM [cms_agents].[JobTypes] + WHERE [Capability] LIKE 'CertStores.IIS.%'; + + DELETE [csc] + FROM [cms_agents].[CertStoreCertificates] [csc] + INNER JOIN [cms_agents].[CertStoreInventoryItems] [csii] ON [csii].[Id] = [csc].[CertStoreInventoryItemId] + INNER JOIN [cms_agents].[CertStores] [cs] ON [csii].[CertStoreId] = [cs].[Id] + INNER JOIN @IISStoreTypeIds [sti] ON [sti].[Id] = [cs].[CertStoreType]; + + DELETE [csii] + FROM [cms_agents].[CertStoreInventoryItems] [csii] + INNER JOIN [cms_agents].[CertStores] [cs] ON [csii].[CertStoreId] = [cs].[Id] + INNER JOIN @IISStoreTypeIds [sti] ON [sti].[Id] = [cs].[CertStoreType]; + + DELETE [cs] + FROM [cms_agents].[CertStores] [cs] + INNER JOIN @IISStoreTypeIds [st] ON [cs].[CertStoreType] = [st].[Id]; + + DELETE [csc] + FROM [cms_agents].[CertStoreContainers] [csc] + INNER JOIN @IISStoreTypeIds [st] ON [csc].[CertStoreType] = [st].[Id]; + + DELETE [csij] + FROM [cms_agents].[CertStoreInventoryJobs] [csij] + INNER JOIN [cms_agents].[AgentSchedules] [as] ON [csij].[JobId] = [as].[JobId] + INNER JOIN @IISJobTypeIds [jti] ON [jti].[Id] = [as].[JobTypeId]; + + DELETE [csrj] + FROM [cms_agents].[CertStoreReenrollmentJobs] [csrj] + INNER JOIN [cms_agents].[AgentSchedules] [as] ON [csrj].[JobId] = [as].[JobId] + INNER JOIN @IISJobTypeIds [jti] ON [jti].[Id] = [as].[JobTypeId]; + + DELETE [csmj] + FROM [cms_agents].[CertStoreManagementJobs] [csmj] + INNER JOIN [cms_agents].[AgentSchedules] [as] ON [csmj].[JobId] = [as].[JobId] + INNER JOIN @IISJobTypeIds [jti] ON [jti].[Id] = [as].[JobTypeId]; + + DELETE [as] + FROM [cms_agents].[AgentSchedules] [as] + INNER JOIN @IISJobTypeIds [jt] ON [jt].[Id] = [as].[JobTypeId]; + + DELETE [cstp] + FROM [cms_agents].[CertStoreTypeProperties] [cstp] + INNER JOIN @IISStoreTypeIds [st] ON [cstp].[StoreTypeId] = [st].[id]; + + DELETE [cst] + FROM [cms_agents].[CertStoreTypes] [cst] + INNER JOIN @IISStoreTypeIds [st] ON [cst].[StoreType] = [st].[id]; + + DELETE [ars] + FROM [cms_agents].[AgentRegistrationSettings] [ars] + INNER JOIN @IISJobTypeIds [jt] ON [ars].[JobTypeId] = [jt].[Id]; + + DELETE [ac] + FROM [cms_agents].[AgentCapabilities] [ac] + INNER JOIN @IISJobTypeIds [jt] ON [ac].[JobTypeId] = [jt].[Id]; + + DELETE [jt] + FROM [cms_agents].[JobTypes] [jt] + INNER JOIN @IISJobTypeIds [jti] ON [jt].[Id] = [jti].[Id]; + + UPDATE [cms_agents].[CertStoreContainers] + SET [Name] = REPLACE([Name],' - Upgraded IISU','') + WHERE [Name] LIKE '% - Upgraded IISU'; + + UPDATE [cms_agents].[CertStoreContainers] + SET [Name] = REPLACE([Name],' - Upgraded WinCert','') + WHERE [Name] LIKE '% - Upgraded WinCert'; + + COMMIT TRANSACTION; +END TRY + +BEGIN CATCH + IF (@@TRANCOUNT > 0) + BEGIN + ROLLBACK TRANSACTION; + END + + SELECT + ERROR_MESSAGE() AS ErrorMessage, + ERROR_SEVERITY() AS Severity, + ERROR_STATE() AS ErrorState; +END CATCH \ No newline at end of file diff --git a/Migration-Scripts/UpgradeIISPersonalToIISU.sql b/Migration-Scripts/UpgradeIISPersonalToIISU.sql new file mode 100644 index 0000000..42679f9 --- /dev/null +++ b/Migration-Scripts/UpgradeIISPersonalToIISU.sql @@ -0,0 +1,231 @@ +-- REQUIREMENTS: +-- SQL Server 2016 or later + +-- PREREQUISITES: +-- 1) The IISU certificate store type must already be properly set up in Keyfactor Command. Run the CreateIISUCertStoreType script to do so. +-- 2) Make sure to back up the targeted Keyfactor Command database before running this + +-- *** BEGIN SET UP *** + +-- *** STEP 1 OF 5: Either enter '*' to migrate all IIS Personal stores, or enter a comma separated list of IDs of stores to convert. +-- Ex.: '25f80f46-fe0a-4ee6-b666-f601effd6847,22d0ca17-1739-4282-a85b-e59a3b431cdd' *** +DECLARE @comma_separated_store_ids NVARCHAR(MAX) = '*'; + +-- *** STEP 2 OF 5: select which Windows certificate store contains the IIS bound certificate. either 'My' for the personal store +-- or 'WebHosting' for the web hosting store. *** +DECLARE @store_path NVARCHAR(15) = 'My'; + +-- *** STEP 3 OF 5: select the WinRM protocol that the Orchestrator should use. either 'https' or 'http'. *** +DECLARE @winrm_protocol NVARCHAR(5) = 'https'; + +-- *** STEP 4 OF 5: select the port that WinRM should use. *** +DECLARE @winrm_port INT = 5986; + +-- *** STEP 5 OF 5: Select whether or not the -IncludePortInSPN flag will be set when WinRM creates the remote PowerShell connection. either 'true' or 'false' *** +DECLARE @spnwithport NVARCHAR(5) = 'false'; + +-- *** END SET UP *** + + +SET NOCOUNT ON + +BEGIN TRY + BEGIN TRANSACTION + + DECLARE @store_ids_to_convert TABLE ([Id] UNIQUEIDENTIFIER); + DECLARE @iis_store_type_id INT = 6; + + PRINT 'validating...'; + + SELECT @comma_separated_store_ids = REPLACE(@comma_separated_store_ids, ' ', ''); + + IF (@comma_separated_store_ids = '*') + BEGIN + INSERT INTO @store_ids_to_convert + SELECT [Id] FROM [cms_agents].[CertStores] [cs] + WHERE [cs].[CertStoreType] = @iis_store_type_id + END + ELSE + BEGIN + INSERT INTO @store_ids_to_convert + SELECT value FROM STRING_SPLIT(@comma_separated_store_ids, ','); + + DECLARE @invalid_ids TABLE ([InvalidId] UNIQUEIDENTIFIER); + + INSERT INTO @invalid_ids + SELECT [Id] FROM @store_ids_to_convert [si] + WHERE [si].[Id] NOT IN + ( + SELECT [Id] FROM [cms_agents].[CertStores] + ); + + IF ((SELECT COUNT(*) FROM @invalid_ids) > 0) + BEGIN + PRINT 'One or more invalid IIS Personal store ids provided. See output table for list of invalid identifiers.'; + SELECT * FROM @invalid_ids; + RETURN; + END + END + + IF (@store_path <> 'My' and @store_path <> 'WebHosting') + BEGIN + PRINT '@store_path must either be "My" or "WebHosting".'; + RETURN; + END + + IF (@winrm_protocol <> 'https' and @winrm_protocol <> 'http') + BEGIN + PRINT '@winrm_protocol must either be "https" or "http".'; + RETURN; + END + + IF (@spnwithport <> 'true' and @spnwithport <> 'false') + BEGIN + PRINT '@spnwithport must either be "true" or "false".'; + RETURN; + END + + PRINT 'Validation Complete'; + + DECLARE @iis_store_data TABLE ( + [IISInventoryJobId] UNIQUEIDENTIFIER, + [ContainerId] INT, + [ClientMachine] NVARCHAR(128), + [StoreApproved] BIT, + [AgentId] UNIQUEIDENTIFIER, + [InventorySchedule] NVARCHAR(512), + [InventoryEnabled] BIT, + [IISUInventoryJobId] UNIQUEIDENTIFIER, + [IISUStoreId] UNIQUEIDENTIFIER, + [IISUStoreContainerId] INT + ); + + -- aggregate necessary data for creating new certificate stores + INSERT INTO @iis_store_data SELECT + [cs].[CertStoreInventoryJobId] AS [IISInventoryJobId], + [cs].[ContainerId], + [cs].[ClientMachine], + [cs].[Approved] AS [StoreApproved], + [cs].[AgentId], + [as].[Schedule] AS [InventorySchedule], + [as].[Enabled] AS [InventoryEnabled], + CASE WHEN [cs].[CertStoreInventoryJobId] IS NOT NULL THEN NEWID() ELSE NULL END, + NEWID(), + NULL + FROM [cms_agents].[CertStores] [cs] + LEFT JOIN [cms_agents].[AgentSchedules] [as] ON [cs].[CertStoreInventoryJobId] = [as].[JobId] + INNER JOIN @store_ids_to_convert [si] ON [cs].[Id] = [si].[Id]; + + DECLARE @iisu_inventory_job_type_id UNIQUEIDENTIFIER; + SELECT @iisu_inventory_job_type_id = [Id] FROM [cms_agents].[JobTypes] WHERE [Name] = 'IISUInventory'; + + DECLARE @iisu_cert_store_type_id INT; + SELECT @iisu_cert_store_type_id = [StoreType] FROM [cms_agents].[CertStoreTypes] WHERE [ShortName] = 'IISU'; + + + -- create new certificate store containers if necessary + INSERT INTO [cms_agents].[CertStoreContainers] ( + [Name] + ,[Schedule] + ,[OverwriteSchedules] + ,[CertStoreType] + ) + SELECT + [csc].[Name] + ' - Upgraded IISU', + [csc].[Schedule], + [csc].[OverwriteSchedules], + @iisu_cert_store_type_id + FROM [cms_agents].[CertStoreContainers] [csc] + WHERE [csc].[CertStoreType] = @iis_store_type_id + AND [csc].[Id] NOT IN + ( + -- make sure we are only upgrading containers that haven't been upgraded yet + SELECT [csc].[Id] FROM [cms_agents].[CertStoreContainers] [csc2] + WHERE [csc].[Name] + ' - Upgraded IISU' = [csc2].[Name] + ); + + -- associate new certificate store containers with aggregated certificate store data + UPDATE @iis_store_data + SET [IISUStoreContainerId] = [wincsc].[Id] + FROM @iis_store_data [psd] + INNER JOIN [cms_agents].[CertStoreContainers] [csc] ON [psd].[ContainerId] = [csc].[Id] + INNER JOIN [cms_agents].[CertStoreContainers] [wincsc] ON [wincsc].[Name] = [csc].[Name] + ' - Upgraded IISU'; + + + -- create new agent schedules + INSERT INTO [cms_agents].[AgentSchedules] ( + [JobId] + ,[AgentId] + ,[JobTypeId] + ,[Schedule] + ,[Enabled] + ,[RequestTimestamp] + ,[Retries] + ) + SELECT + [psd].[IISUInventoryJobId], + [psd].[AgentId], + @iisu_inventory_job_type_id, + [psd].[InventorySchedule], + [psd].[InventoryEnabled], + NULL, + 0 + FROM @iis_store_data [psd] + WHERE [psd].[IISInventoryJobId] IS NOT NULL; + + + -- create new inventory jobs + INSERT INTO [cms_agents].[CertStoreInventoryJobs] ( + [JobId] + ,[InventoryEndpoint] + ,[RequestTimestamp] + ) + SELECT + [psd].[IISUInventoryJobId], + '/AnyInventory/Update', + CURRENT_TIMESTAMP + FROM @iis_store_data [psd] + WHERE [psd].[IISInventoryJobId] IS NOT NULL; + + + -- create new certificate stores + INSERT INTO [cms_agents].[CertStores] ( + [Id] + ,[CertStoreInventoryJobId] + ,[ContainerId] + ,[ClientMachine] + ,[StorePath] + ,[CertStoreType] + ,[Approved] + ,[CreateIfMissing] + ,[Properties] + ,[AgentId] + ) + SELECT + [psd].[IISUStoreId], + [psd].[IISUInventoryJobId], + [psd].[IISUStoreContainerId], + [psd].[ClientMachine], + @store_path, + @iisu_cert_store_type_id, + [psd].[StoreApproved], + 0, + '{"WinRm Protocol":"'+@winrm_protocol+'","WinRm Port":"'+CONVERT(NVARCHAR(10),@winrm_port)+'","spnwithport":"'+@spnwithport+'","ServerUsername":"'+CONVERT(NVARCHAR(36),NEWID())+'","ServerPassword":"'+CONVERT(NVARCHAR(36),NEWID())+'","ServerUseSsl":"true"}', + [psd].[AgentId] + FROM @iis_store_data [psd]; + + + COMMIT TRANSACTION; +END TRY + +BEGIN CATCH + IF (@@TRANCOUNT > 0) + BEGIN + ROLLBACK TRANSACTION; + END + + SELECT + ERROR_MESSAGE() AS ErrorMessage, + ERROR_SEVERITY() AS Severity, + ERROR_STATE() AS ErrorState; +END CATCH diff --git a/Migration-Scripts/UpgradeIISPersonalToWinCert.sql b/Migration-Scripts/UpgradeIISPersonalToWinCert.sql new file mode 100644 index 0000000..9ad194b --- /dev/null +++ b/Migration-Scripts/UpgradeIISPersonalToWinCert.sql @@ -0,0 +1,224 @@ +-- REQUIREMENTS: +-- SQL Server 2016 or later + +-- PREREQUISITES: +-- 1) The WinCert certificate store type must already be properly set up in Keyfactor Command. Run the CreateWinCertStoreType script to do so. +-- 2) Make sure to back up the targeted Keyfactor Command database before running this + +-- *** BEGIN SET UP *** + +-- *** STEP 1 OF 4: Either enter '*' to migrate all IIS Personal stores, or enter a comma separated list of IDs of stores to convert. +-- Ex.: '25f80f46-fe0a-4ee6-b666-f601effd6847,22d0ca17-1739-4282-a85b-e59a3b431cdd' *** +DECLARE @comma_separated_store_ids NVARCHAR(MAX) = '*'; + +-- *** STEP 2 OF 4: select the WinRM protocol that the Orchestrator should use. either 'https' or 'http'. *** +DECLARE @winrm_protocol NVARCHAR(5) = 'https'; + +-- *** STEP 3 OF 4: select the port that WinRM should use. *** +DECLARE @winrm_port INT = 5986; + +-- *** STEP 4 OF 4: Select whether or not the -IncludePortInSPN flag will be set when WinRM creates the remote PowerShell connection. either 'true' or 'false' *** +DECLARE @spnwithport NVARCHAR(5) = 'false'; + +-- *** END SET UP *** + + +SET NOCOUNT ON + +BEGIN TRY + BEGIN TRANSACTION + + DECLARE @store_ids_to_convert TABLE ([Id] UNIQUEIDENTIFIER); + DECLARE @iis_store_type_id INT = 6; + + PRINT 'validating parameters...'; + + SELECT @comma_separated_store_ids = REPLACE(@comma_separated_store_ids, ' ', ''); + + IF (@comma_separated_store_ids = '*') + BEGIN + INSERT INTO @store_ids_to_convert + SELECT [Id] FROM [cms_agents].[CertStores] [cs] + WHERE [cs].[CertStoreType] = @iis_store_type_id + END + ELSE + BEGIN + INSERT INTO @store_ids_to_convert + SELECT value FROM STRING_SPLIT(@comma_separated_store_ids, ','); + + DECLARE @invalid_ids TABLE ([InvalidId] UNIQUEIDENTIFIER); + + INSERT INTO @invalid_ids + SELECT [Id] FROM @store_ids_to_convert [si] + WHERE [si].[Id] NOT IN + ( + SELECT [Id] FROM [cms_agents].[CertStores] + ); + + IF ((SELECT COUNT(*) FROM @invalid_ids) > 0) + BEGIN + PRINT 'One or more invalid IIS Personal store ids provided. See output table for list of invalid identifiers.'; + SELECT * FROM @invalid_ids; + RETURN; + END + END + + IF (@winrm_protocol <> 'https' and @winrm_protocol <> 'http') + BEGIN + PRINT '@winrm_protocol must either be "https" or "http".'; + RETURN; + END + + IF (@spnwithport <> 'true' and @spnwithport <> 'false') + BEGIN + PRINT '@spnwithport must either be "true" or "false".'; + RETURN; + END + + PRINT 'Validation Complete'; + + DECLARE @store_path NVARCHAR(256) = 'My'; + + DECLARE @iis_store_data TABLE ( + [IISInventoryJobId] UNIQUEIDENTIFIER, + [ContainerId] INT, + [ClientMachine] NVARCHAR(128), + [StoreApproved] BIT, + [AgentId] UNIQUEIDENTIFIER, + [InventorySchedule] NVARCHAR(512), + [InventoryEnabled] BIT, + [WinInventoryJobId] UNIQUEIDENTIFIER, + [WinStoreId] UNIQUEIDENTIFIER, + [WinStoreContainerId] INT + ); + + -- aggregate necessary data for creating new certificate stores + INSERT INTO @iis_store_data SELECT + [cs].[CertStoreInventoryJobId] AS [IISInventoryJobId], + [cs].[ContainerId], + [cs].[ClientMachine], + [cs].[Approved] AS [StoreApproved], + [cs].[AgentId], + [as].[Schedule] AS [InventorySchedule], + [as].[Enabled] AS [InventoryEnabled], + CASE WHEN [cs].[CertStoreInventoryJobId] IS NOT NULL THEN NEWID() ELSE NULL END, + NEWID(), + NULL + FROM [cms_agents].[CertStores] [cs] + LEFT JOIN [cms_agents].[AgentSchedules] [as] ON [cs].[CertStoreInventoryJobId] = [as].[JobId] + INNER JOIN @store_ids_to_convert [si] ON [cs].[Id] = [si].[Id]; + + DECLARE @win_inventory_job_type_id UNIQUEIDENTIFIER; + SELECT @win_inventory_job_type_id = [Id] FROM [cms_agents].[JobTypes] WHERE [Name] = 'WinCertInventory'; + + DECLARE @win_cert_store_type_id INT; + SELECT @win_cert_store_type_id = [StoreType] FROM [cms_agents].[CertStoreTypes] WHERE [ShortName] = 'WinCert'; + + + -- create new certificate store containers if necessary + INSERT INTO [cms_agents].[CertStoreContainers] ( + [Name] + ,[Schedule] + ,[OverwriteSchedules] + ,[CertStoreType] + ) + SELECT + [csc].[Name] + ' - Upgraded WinCert', + [csc].[Schedule], + [csc].[OverwriteSchedules], + @win_cert_store_type_id + FROM [cms_agents].[CertStoreContainers] [csc] + WHERE [csc].[CertStoreType] = @iis_store_type_id + AND [csc].[Id] NOT IN + ( + -- make sure we are only upgrading containers that haven't been upgraded yet + SELECT [csc].[Id] FROM [cms_agents].[CertStoreContainers] [csc2] + WHERE [csc].[Name] + ' - Upgraded WinCert' = [csc2].[Name] + ); + + + + -- associate new certificate store containers with aggregated certificate store data + UPDATE @iis_store_data + SET [WinStoreContainerId] = [wincsc].[Id] + FROM @iis_store_data [psd] + INNER JOIN [cms_agents].[CertStoreContainers] [csc] ON [psd].[ContainerId] = [csc].[Id] + INNER JOIN [cms_agents].[CertStoreContainers] [wincsc] ON [wincsc].[Name] = [csc].[Name] + ' - Upgraded WinCert'; + + + -- create new agent schedules + INSERT INTO [cms_agents].[AgentSchedules] ( + [JobId] + ,[AgentId] + ,[JobTypeId] + ,[Schedule] + ,[Enabled] + ,[RequestTimestamp] + ,[Retries] + ) + SELECT + [psd].[WinInventoryJobId], + [psd].[AgentId], + @win_inventory_job_type_id, + [psd].[InventorySchedule], + [psd].[InventoryEnabled], + NULL, + 0 + FROM @iis_store_data [psd] + WHERE [psd].[IISInventoryJobId] IS NOT NULL; + + + -- create new inventory jobs + INSERT INTO [cms_agents].[CertStoreInventoryJobs] ( + [JobId] + ,[InventoryEndpoint] + ,[RequestTimestamp] + ) + SELECT + [psd].[WinInventoryJobId], + '/AnyInventory/Update', + CURRENT_TIMESTAMP + FROM @iis_store_data [psd] + WHERE [psd].[IISInventoryJobId] IS NOT NULL; + + + -- create new certificate stores + INSERT INTO [cms_agents].[CertStores] ( + [Id] + ,[CertStoreInventoryJobId] + ,[ContainerId] + ,[ClientMachine] + ,[StorePath] + ,[CertStoreType] + ,[Approved] + ,[CreateIfMissing] + ,[Properties] + ,[AgentId] + ) + SELECT + [psd].[WinStoreId], + [psd].[WinInventoryJobId], + [psd].[WinStoreContainerId], + [psd].[ClientMachine], + @store_path, + @win_cert_store_type_id, + [psd].[StoreApproved], + 0, + '{"WinRm Protocol":"'+@winrm_protocol+'","WinRm Port":"'+CONVERT(NVARCHAR(10),@winrm_port)+'","spnwithport":"'+@spnwithport+'","ServerUsername":"'+CONVERT(NVARCHAR(36),NEWID())+'","ServerPassword":"'+CONVERT(NVARCHAR(36),NEWID())+'","ServerUseSsl":"true"}', + [psd].[AgentId] + FROM @iis_store_data [psd]; + + COMMIT TRANSACTION; +END TRY + +BEGIN CATCH + IF (@@TRANCOUNT > 0) + BEGIN + ROLLBACK TRANSACTION; + END + + SELECT + ERROR_MESSAGE() AS ErrorMessage, + ERROR_SEVERITY() AS Severity, + ERROR_STATE() AS ErrorState; +END CATCH \ No newline at end of file diff --git a/Migration-Scripts/UpgradeIISRevokedAndRootsToWinCert.sql b/Migration-Scripts/UpgradeIISRevokedAndRootsToWinCert.sql new file mode 100644 index 0000000..af2f05d --- /dev/null +++ b/Migration-Scripts/UpgradeIISRevokedAndRootsToWinCert.sql @@ -0,0 +1,196 @@ +-- REQUIREMENTS: +-- SQL Server 2016 or later + +-- PREREQUISITES: +-- 1) The WinCert certificate store type must already be properly set up in Keyfactor Command. Run the CreateWinCertStoreType script to do so. +-- 2) Make sure to back up the targeted Keyfactor Command database before running this + +-- *** BEGIN SET UP *** + +-- *** STEP 1 OF 3: select the WinRM protocol that the Orchestrator should use. either 'https' or 'http'. *** +DECLARE @winrm_protocol NVARCHAR(5) = 'https'; + +-- *** STEP 2 OF 3: select the port that WinRM should use. *** +DECLARE @winrm_port INT = 5986; + +-- *** STEP 3 OF 3: Select whether or not the -IncludePortInSPN flag will be set when WinRM creates the remote PowerShell connection. either 'true' or 'false' *** +DECLARE @spnwithport NVARCHAR(5) = 'false'; + +-- *** END SET UP *** + + +SET NOCOUNT ON + +BEGIN TRY + BEGIN TRANSACTION + + PRINT 'validating parameters...'; + + IF (@winrm_protocol <> 'https' and @winrm_protocol <> 'http') + BEGIN + PRINT '@winrm_protocol must either be "https" or "http".'; + RETURN; + END + + IF (@spnwithport <> 'true' and @spnwithport <> 'false') + BEGIN + PRINT '@spnwithport must either be "true" or "false".'; + RETURN; + END + + PRINT 'Validation Complete'; + + DECLARE @upgrade_script NVARCHAR(MAX) = N' + + DECLARE @iis_store_data TABLE ( + [IISInventoryJobId] UNIQUEIDENTIFIER, + [ContainerId] INT, + [ClientMachine] NVARCHAR(128), + [StoreApproved] BIT, + [AgentId] UNIQUEIDENTIFIER, + [InventorySchedule] NVARCHAR(512), + [InventoryEnabled] BIT, + [WinInventoryJobId] UNIQUEIDENTIFIER, + [WinStoreId] UNIQUEIDENTIFIER, + [WinStoreContainerId] INT + ); + + -- aggregate necessary data for creating new certificate stores + INSERT INTO @iis_store_data SELECT + [cs].[CertStoreInventoryJobId] AS [IISInventoryJobId], + [cs].[ContainerId], + [cs].[ClientMachine], + [cs].[Approved] AS [StoreApproved], + [cs].[AgentId], + [as].[Schedule] AS [InventorySchedule], + [as].[Enabled] AS [InventoryEnabled], + CASE WHEN [cs].[CertStoreInventoryJobId] IS NOT NULL THEN NEWID() ELSE NULL END, + NEWID(), + NULL + FROM [cms_agents].[CertStores] [cs] + LEFT JOIN [cms_agents].[AgentSchedules] [as] + ON [cs].[CertStoreInventoryJobId] = [as].[JobId] + WHERE [cs].[CertStoreType] = @iis_store_type_id; + + DECLARE @win_inventory_job_type_id UNIQUEIDENTIFIER; + SELECT @win_inventory_job_type_id = [Id] FROM [cms_agents].[JobTypes] WHERE [Name] = ''WinCertInventory''; + + DECLARE @win_cert_store_type_id INT; + SELECT @win_cert_store_type_id = [StoreType] FROM [cms_agents].[CertStoreTypes] WHERE [ShortName] = ''WinCert''; + + + -- create new certificate store containers if necessary + INSERT INTO [cms_agents].[CertStoreContainers] ( + [Name] + ,[Schedule] + ,[OverwriteSchedules] + ,[CertStoreType] + ) + SELECT + [csc].[Name] + '' - Upgraded WinCert'', + [csc].[Schedule], + [csc].[OverwriteSchedules], + @win_cert_store_type_id + FROM [cms_agents].[CertStoreContainers] [csc] WHERE [csc].CertStoreType = @iis_store_type_id; + + -- associate new certificate store containers with aggregated certificate store data + UPDATE @iis_store_data + SET [WinStoreContainerId] = [wincsc].[Id] + FROM @iis_store_data [psd] + INNER JOIN [cms_agents].[CertStoreContainers] [csc] ON [psd].[ContainerId] = [csc].[Id] + INNER JOIN [cms_agents].[CertStoreContainers] [wincsc] ON [wincsc].[Name] = [csc].[Name] + '' - Upgraded WinCert''; + + + -- create new agent schedules + INSERT INTO [cms_agents].[AgentSchedules] ( + [JobId] + ,[AgentId] + ,[JobTypeId] + ,[Schedule] + ,[Enabled] + ,[RequestTimestamp] + ,[Retries] + ) + SELECT + [psd].[WinInventoryJobId], + [psd].[AgentId], + @win_inventory_job_type_id, + [psd].[InventorySchedule], + [psd].[InventoryEnabled], + NULL, + 0 + FROM @iis_store_data [psd] + WHERE [psd].[IISInventoryJobId] IS NOT NULL; + + + -- create new inventory jobs + INSERT INTO [cms_agents].[CertStoreInventoryJobs] ( + [JobId] + ,[InventoryEndpoint] + ,[RequestTimestamp] + ) + SELECT + [psd].[WinInventoryJobId], + ''/AnyInventory/Update'', + CURRENT_TIMESTAMP + FROM @iis_store_data [psd] + WHERE [psd].[IISInventoryJobId] IS NOT NULL; + + + -- create new certificate stores + INSERT INTO [cms_agents].[CertStores] ( + [Id] + ,[CertStoreInventoryJobId] + ,[ContainerId] + ,[ClientMachine] + ,[StorePath] + ,[CertStoreType] + ,[Approved] + ,[CreateIfMissing] + ,[Properties] + ,[AgentId] + ) + SELECT + [psd].[WinStoreId], + [psd].[WinInventoryJobId], + [psd].[WinStoreContainerId], + [psd].[ClientMachine], + @store_path, + @win_cert_store_type_id, + [psd].[StoreApproved], + 0, + ''{"WinRm Protocol":"''+@winrm_protocol+''","WinRm Port":"''+CONVERT(NVARCHAR(10),@winrm_port)+''","spnwithport":"''+@spnwithport+''","ServerUsername":"''+CONVERT(NVARCHAR(36),NEWID())+''","ServerPassword":"''+CONVERT(NVARCHAR(36),NEWID())+''","ServerUseSsl":"true"}'', + [psd].[AgentId] + FROM @iis_store_data [psd];' + + EXEC dbo.sp_executesql @upgrade_script, + N'@iis_store_type_id INT, + @store_path NVARCHAR(256), + @winrm_protocol NVARCHAR(5), + @winrm_port INT, + @spnwithport NVARCHAR(5)', + 4, 'Root', @winrm_protocol, @winrm_port, @spnwithport; + + EXEC dbo.sp_executesql @upgrade_script, + N'@iis_store_type_id INT, + @store_path NVARCHAR(256), + @winrm_protocol NVARCHAR(5), + @winrm_port INT, + @spnwithport NVARCHAR(5)', + 8, 'Disallowed', @winrm_protocol, @winrm_port, @spnwithport; + + + COMMIT TRANSACTION; +END TRY + +BEGIN CATCH + IF (@@TRANCOUNT > 0) + BEGIN + ROLLBACK TRANSACTION; + END + + SELECT + ERROR_MESSAGE() AS ErrorMessage, + ERROR_SEVERITY() AS Severity, + ERROR_STATE() AS ErrorState; +END CATCH From 4d8e575afc36530bfebce184266451428f292728 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Thu, 29 Feb 2024 02:53:37 +0000 Subject: [PATCH 02/26] Initial changes for creating and importing pfx files to accept specific csps. --- IISU/Certificate.cs | 2 + IISU/ClientPSCertStoreInventory.cs | 21 ++- IISU/ClientPSCertStoreManager.cs | 143 +++++++++++++++++- IISU/ImplementedStoreTypes/Win/Management.cs | 37 ++++- .../ImplementedStoreTypes/Win/WinInventory.cs | 9 +- IISU/PSHelper.cs | 51 ++++++- IISU/WindowsCertStore.csproj | 7 +- .../Properties/launchSettings.json | 8 + WinCertTestConsole/WinCertTestConsole.csproj | 1 + WindowsCertStore.sln | 9 ++ 10 files changed, 271 insertions(+), 17 deletions(-) create mode 100644 WinCertTestConsole/Properties/launchSettings.json diff --git a/IISU/Certificate.cs b/IISU/Certificate.cs index 88f3612..e0270be 100644 --- a/IISU/Certificate.cs +++ b/IISU/Certificate.cs @@ -22,5 +22,7 @@ public class Certificate public byte[] RawData { get; set; } public bool HasPrivateKey { get; set; } public string CertificateData => Convert.ToBase64String(RawData); + public string CryptoServiceProvider { get; set; } + public string SAN { get; set; } } } \ No newline at end of file diff --git a/IISU/ClientPSCertStoreInventory.cs b/IISU/ClientPSCertStoreInventory.cs index 25875ab..8b7bcaf 100644 --- a/IISU/ClientPSCertStoreInventory.cs +++ b/IISU/ClientPSCertStoreInventory.cs @@ -46,8 +46,19 @@ public List GetCertificatesFromStore(Runspace runSpace, string stor $certs = $certStore.Certificates $certStore.Close() $certStore.Dispose() - foreach ( $cert in $certs){{ - $cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey + $certs | ForEach-Object {{ + $certDetails = @{{ + Subject = $_.Subject + Thumbprint = $_.Thumbprint + HasPrivateKey = $_.HasPrivateKey + RawData = $_.RawData + }} + + if ($_.HasPrivateKey) {{ + $certDetails.CSP = $_.PrivateKey.CspKeyContainerInfo.ProviderName + }} + + New-Object PSObject -Property $certDetails }}"; ps.AddScript(certStoreScript); @@ -55,12 +66,16 @@ public List GetCertificatesFromStore(Runspace runSpace, string stor var certs = ps.Invoke(); foreach (var c in certs) + { myCertificates.Add(new Certificate { Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), - RawData = (byte[])c.Properties["RawData"]?.Value + RawData = (byte[])c.Properties["RawData"]?.Value, + CryptoServiceProvider = $"{c.Properties["CSP"]?.Value }", + SAN = $"{c.Properties["Subject"]?.Value}" }); + } return myCertificates; } diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index 35d0cb8..ac45044 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -16,6 +16,7 @@ using Keyfactor.Orchestrators.Extensions; using Microsoft.Extensions.Logging; using System; +using System.IO; using System.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; @@ -45,6 +46,145 @@ public ClientPSCertStoreManager(ILogger logger, Runspace runSpace, long jobNumbe _jobNumber = jobNumber; } + public string CreatePFXFile(string certificateContents, string privateKeyPassword) + { + try + { + // Create the x509 certificate + //x509Cert = new X509Certificate2 + // ( + // Convert.FromBase64String(certificateContents), + // privateKeyPassword, + // X509KeyStorageFlags.MachineKeySet | + // X509KeyStorageFlags.PersistKeySet | + // X509KeyStorageFlags.Exportable + // ); + + using (PowerShell ps = PowerShell.Create()) + { + ps.Runspace = _runspace; + + // Add script to write certificate contents to a temporary file + string script = @" + param($certificateContents) + $filePath = [System.IO.Path]::GetTempFileName() + '.pfx' + [System.IO.File]::WriteAllBytes($filePath, [System.Convert]::FromBase64String($certificateContents)) + $filePath + "; + + ps.AddScript(script); + ps.AddParameter("certificateContents", certificateContents); // Convert.ToBase64String(x509Cert.Export(X509ContentType.Pkcs12))); + + // Invoke the script on the remote computer + var results = ps.Invoke(); + + // Get the result (temporary file path) returned by the script + return results[0].ToString(); + } + } + catch (Exception) + { + throw new Exception("An error occurred while attempting to create and write the X509 contents."); + } + } + + public void DeletePFXFile(string filePath, string fileName) + { + using (PowerShell ps = PowerShell.Create()) + { + ps.Runspace = _runspace; + + // Add script to delete the temporary file + string deleteScript = @" + param($filePath) + Remove-Item -Path $filePath -Force + "; + + ps.AddScript(deleteScript); + ps.AddParameter("filePath", Path.Combine(filePath, fileName) + "*"); + + // Invoke the script to delete the file + var results = ps.Invoke(); + } + } + + public JobResult ImportPFXFile(string filePath, string privateKeyPassword, string cryptoProviderName) + { + try + { + using (PowerShell ps = PowerShell.Create()) + { + ps.Runspace = _runspace; + + if (cryptoProviderName == null) + { + string script = @" + param($pfxFilePath, $privateKeyPassword, $cspName) + $output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1 + $output + "; + + ps.AddScript(script); + ps.AddParameter("pfxFilePath", filePath); + ps.AddParameter("privateKeyPassword", privateKeyPassword); + } + else + { + string script = @" + param($pfxFilePath, $privateKeyPassword, $cspName) + $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $pfxFilePath 2>&1 + $output + "; + + ps.AddScript(script); + ps.AddParameter("pfxFilePath", filePath); + ps.AddParameter("privateKeyPassword", privateKeyPassword); + ps.AddParameter("cspName", cryptoProviderName); + } + + // Invoke the script + var results = ps.Invoke(); + + // Check for errors in the output + bool isError = false; + foreach (var result in results) + { + string outputLine = result.ToString(); + if (!string.IsNullOrEmpty(outputLine) && outputLine.Contains("Error")) + { + isError = true; + Console.WriteLine(outputLine); // Print the error message + } + } + + if (isError) + { + throw new Exception("Error occurred while attempting to import the pfx file."); + } + else + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobNumber, + FailureMessage = "" + }; + } + } + } + catch (Exception e) + { + _logger.LogError($"Error Occurred in ClientPSCertStoreManager.ImportPFXFile(): {e.Message}"); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobNumber, + FailureMessage = $"Error Occurred in ImportPFXFile {LogHandler.FlattenException(e)}" + }; + } + } + public JobResult AddCertificate(string certificateContents, string privateKeyPassword, string storePath) { try @@ -65,9 +205,6 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas X509KeyStorageFlags.Exportable ); - _logger.LogDebug($"X509 Cert Created With Subject: {x509Cert.SubjectName}"); - _logger.LogDebug($"Begin Add for Cert Store {$@"\\{_runspace.ConnectionInfo.ComputerName}\{storePath}"}"); - // Add Certificate var funcScript = @" $ErrorActionPreference = ""Stop"" diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index dbd6ed6..d765792 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -23,6 +23,7 @@ using System.Management.Automation; using System.Net; using Keyfactor.Logging; +using System.IO; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { @@ -114,26 +115,46 @@ private JobResult performAddition(ManagementJobConfiguration config) { try { +#nullable enable string certificateContents = config.JobCertificate.Contents; string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; string storePath = config.CertificateStoreDetails.StorePath; long jobNumber = config.JobHistoryId; + string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); +#nullable disable + + // If a crypto provider was provided, check to see if it exists + if (cryptoProvider != null) + { + _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); + if (!PsHelper.IsCSPFound(PsHelper.GetCSPList(myRunspace), cryptoProvider)) + { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the Crypto Provider provided."); } + } if (storePath != null) { _logger.LogInformation($"Attempting to add certificate to cert store: {storePath}"); - + ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - return manager.AddCertificate(certificateContents, privateKeyPassword, storePath); + + // Write the certificate contents to a temporary file on the remote computer, returning the filename. + string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); + _logger.LogTrace($"{filePath} was created."); + + // Using certutil on the remote computer, import the pfx file using a supplied csp if any. + JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider); + + // Delete the temporary file + manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); + + return result; + + // This method is being retired + // return manager.AddCertificate(certificateContents, privateKeyPassword, storePath); } else { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = "Store Path is empty or null." - }; + throw new Exception($"The store pathis empty or null."); } } catch (Exception e) diff --git a/IISU/ImplementedStoreTypes/Win/WinInventory.cs b/IISU/ImplementedStoreTypes/Win/WinInventory.cs index 0e4542c..33b8d65 100644 --- a/IISU/ImplementedStoreTypes/Win/WinInventory.cs +++ b/IISU/ImplementedStoreTypes/Win/WinInventory.cs @@ -33,6 +33,13 @@ public List GetInventoryItems(Runspace runSpace, string st foreach (Certificate cert in base.GetCertificatesFromStore(runSpace, storePath)) { + var entryParms = new Dictionary + { + { "ProviderName", cert.CryptoServiceProvider }, + { "SAN", cert.SAN } + }; + + inventoryItems.Add(new CurrentInventoryItem { Certificates = new[] { cert.CertificateData }, @@ -40,7 +47,7 @@ public List GetInventoryItems(Runspace runSpace, string st PrivateKeyEntry = cert.HasPrivateKey, UseChainLevel = false, ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Parameters = null + Parameters = entryParms }); } diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 7a8464a..202c38b 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -15,6 +15,8 @@ using Keyfactor.Logging; using Microsoft.Extensions.Logging; using System; +using System.Collections; +using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Net; @@ -54,8 +56,55 @@ public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMa PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(5, 1), null, null, false); Runspace rs = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Array.Empty()), instance); - + return rs; } + + public static IEnumerable GetCSPList(Runspace myRunspace) + { + _logger.LogTrace("Getting the list of Crypto Service Providers"); + + using var ps = PowerShell.Create(); + + ps.Runspace = myRunspace; + + var certStoreScript = $@" + $certUtilOutput = certutil -csplist + + $cspInfoList = @() + foreach ($line in $certUtilOutput) {{ + if ($line -match ""Provider Name:"") {{ + $cspName = ($line -split "":"")[1].Trim() + $cspInfoList += $cspName + }} + }} + + $cspInfoList"; + + ps.AddScript(certStoreScript); + + foreach (var result in ps.Invoke()) + { + var cspName = result?.BaseObject?.ToString(); + if (cspName != null) { yield return cspName; } + } + + _logger.LogInformation("No Crypto Service Providers were found"); + yield return null; + } + + public static bool IsCSPFound(IEnumerable cspList, string userCSP) + { + foreach (var csp in cspList) + { + if (string.Equals(csp, userCSP, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogTrace($"CSP found: {csp}"); + return true; + } + } + _logger.LogTrace($"CSP: {userCSP} was not found"); + return false; + } } } diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index fb4cca1..d6d1c51 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -12,6 +12,11 @@ false + + none + false + + @@ -26,7 +31,7 @@ - + diff --git a/WinCertTestConsole/Properties/launchSettings.json b/WinCertTestConsole/Properties/launchSettings.json new file mode 100644 index 0000000..33504c9 --- /dev/null +++ b/WinCertTestConsole/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "WSL": { + "commandName": "WSL2", + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/WinCertTestConsole/WinCertTestConsole.csproj b/WinCertTestConsole/WinCertTestConsole.csproj index 319c554..504e735 100644 --- a/WinCertTestConsole/WinCertTestConsole.csproj +++ b/WinCertTestConsole/WinCertTestConsole.csproj @@ -3,6 +3,7 @@ Exe net6.0 + AnyCPU diff --git a/WindowsCertStore.sln b/WindowsCertStore.sln index 28117ce..883ef0b 100644 --- a/WindowsCertStore.sln +++ b/WindowsCertStore.sln @@ -39,17 +39,26 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|x64.ActiveCfg = Debug|Any CPU + {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|x64.Build.0 = Debug|Any CPU {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.ActiveCfg = Release|Any CPU {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.Build.0 = Release|Any CPU + {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|x64.ActiveCfg = Release|x64 + {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|x64.Build.0 = Release|x64 {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Debug|x64.Build.0 = Debug|Any CPU {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Release|Any CPU.Build.0 = Release|Any CPU + {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Release|x64.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 9be2ad68fc92d2081aa65e38f508cbbe4e08d9bf Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Thu, 14 Mar 2024 01:05:17 +0000 Subject: [PATCH 03/26] Added ReEnrollment jobs to WinSQL --- CHANGELOG.md | 4 + IISU/Certificate.cs | 30 +++++++ IISU/ClientPSCertStoreInventory.cs | 3 +- IISU/ClientPSCertStoreManager.cs | 19 ++--- IISU/ClientPSCertStoreReEnrollment.cs | 46 +++++++---- IISU/ClientPsSqlManager.cs | 5 ++ IISU/ImplementedStoreTypes/Win/Management.cs | 6 +- .../ImplementedStoreTypes/Win/ReEnrollment.cs | 2 +- .../ImplementedStoreTypes/Win/WinInventory.cs | 1 - .../WinIIS/Management.cs | 73 ++++++++++++++--- .../WinIIS/ReEnrollment.cs | 2 +- .../WinIIS/WinIISInventory.cs | 4 +- .../WinSQL/Management.cs | 79 +++++++++++++++---- .../WinSQL/ReEnrollment.cs | 44 +++++++++++ .../WinSQL/SQLServerInventory.cs | 7 +- IISU/WinCertJobTypeBase.cs | 7 ++ readme_source.md | 3 +- 17 files changed, 269 insertions(+), 66 deletions(-) create mode 100644 IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed6e38..d789fe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +2.4.0 +* Changed the way certificates are added to cert stores. CertUtil is now used to import the PFX certificate into the associated store. The CSP is now considered when maintaining certificates, empty CSP values will result in using the machines default CSP. +* Added the Crypto Service Provider and SAN Entry Parameters to be used on Inventory queries, Adding and ReEnrollments for the WinCert and IISU extensions. The CSP was added for WinSWL for Management Add jobs only. + 2.3.1 * Added additional error trapping for WinRM connections to allow actual error on failure. diff --git a/IISU/Certificate.cs b/IISU/Certificate.cs index e0270be..81b89a4 100644 --- a/IISU/Certificate.cs +++ b/IISU/Certificate.cs @@ -13,6 +13,8 @@ // limitations under the License. using System; +using System.Linq; +using System.Text.RegularExpressions; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { @@ -24,5 +26,33 @@ public class Certificate public string CertificateData => Convert.ToBase64String(RawData); public string CryptoServiceProvider { get; set; } public string SAN { get; set; } + + public class Utilities + { + public static string FormatSAN(string san) + { + // Use regular expression to extract key-value pairs + var regex = new Regex(@"(?DNS Name|Email|IP Address)=(?[^=,\s]+)"); + var matches = regex.Matches(san); + + // Format matches into the desired format + string result = string.Join("&", matches.Cast() + .Select(m => $"{NormalizeKey(m.Groups["key"].Value)}={m.Groups["value"].Value}")); + + return result; + } + + private static string NormalizeKey(string key) + { + return key.ToLower() switch + { + "dns name" => "dns", + "email" => "email", + "ip address" => "ip", + _ => key.ToLower() // For other types, keep them as-is + }; + } + + } } } \ No newline at end of file diff --git a/IISU/ClientPSCertStoreInventory.cs b/IISU/ClientPSCertStoreInventory.cs index 8b7bcaf..82a6365 100644 --- a/IISU/ClientPSCertStoreInventory.cs +++ b/IISU/ClientPSCertStoreInventory.cs @@ -52,6 +52,7 @@ public List GetCertificatesFromStore(Runspace runSpace, string stor Thumbprint = $_.Thumbprint HasPrivateKey = $_.HasPrivateKey RawData = $_.RawData + san = $_.Extensions | Where-Object {{ $_.Oid.FriendlyName -eq ""Subject Alternative Name"" }} | ForEach-Object {{ $_.Format($false) }} }} if ($_.HasPrivateKey) {{ @@ -73,7 +74,7 @@ public List GetCertificatesFromStore(Runspace runSpace, string stor HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), RawData = (byte[])c.Properties["RawData"]?.Value, CryptoServiceProvider = $"{c.Properties["CSP"]?.Value }", - SAN = $"{c.Properties["Subject"]?.Value}" + SAN = Certificate.Utilities.FormatSAN($"{c.Properties["san"]?.Value}") }); } diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index ac45044..f65d60d 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -51,14 +51,14 @@ public string CreatePFXFile(string certificateContents, string privateKeyPasswor try { // Create the x509 certificate - //x509Cert = new X509Certificate2 - // ( - // Convert.FromBase64String(certificateContents), - // privateKeyPassword, - // X509KeyStorageFlags.MachineKeySet | - // X509KeyStorageFlags.PersistKeySet | - // X509KeyStorageFlags.Exportable - // ); + x509Cert = new X509Certificate2 + ( + Convert.FromBase64String(certificateContents), + privateKeyPassword, + X509KeyStorageFlags.MachineKeySet | + X509KeyStorageFlags.PersistKeySet | + X509KeyStorageFlags.Exportable + ); using (PowerShell ps = PowerShell.Create()) { @@ -185,6 +185,7 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin } } + [Obsolete("This method is no longer used. Certificates are added by creating and importing PFX files.", false)] public JobResult AddCertificate(string certificateContents, string privateKeyPassword, string storePath) { try @@ -211,7 +212,7 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas function InstallPfxToMachineStore([byte[]]$bytes, [string]$password, [string]$storeName) { $certStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $storeName, ""LocalMachine"" - $certStore.Open(5) + $certStore.Open('MaxAllowed') $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $bytes, $password, 18 <# Persist, Machine #> $certStore.Add($cert) diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index d971c55..85ce1ad 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -44,7 +44,7 @@ public ClientPSCertStoreReEnrollment(ILogger logger, IPAMSecretResolver resolver _resolver = resolver; } - public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment, bool bindCertificate) + public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment, CertStoreBindingTypeENUM bindingType) { bool hasError = false; @@ -218,21 +218,37 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit ps.Commands.Clear(); runSpace.Close(); - JobResult result; + // Default results + JobResult result = new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; + + // Do specific bindings + switch (bindingType) + { + case CertStoreBindingTypeENUM.WinIIS: + // Bind the certificate to IIS + ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUserName, serverPassword); + result = iisManager.BindCertificate(myCert); + // Provide logging information + if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the IIS Server."); } + else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the IIS Server. Check the logs for more information."); } + break; + + case CertStoreBindingTypeENUM.WinSQL: + + // Bind to SQL Server + ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUserName, serverPassword); + result = sqlManager.BindCertificates("", myCert); + + // Provide logging information + if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the SQL Server."); } + else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the SQL Server. Check the logs for more information."); } + break; - if (bindCertificate) - { - // Bind the certificate to IIS - ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUserName, serverPassword); - result = iisManager.BindCertificate(myCert); - }else - { - result = new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, - FailureMessage = "" - }; } ps.Commands.Clear(); diff --git a/IISU/ClientPsSqlManager.cs b/IISU/ClientPsSqlManager.cs index 9132ef5..45a485a 100644 --- a/IISU/ClientPsSqlManager.cs +++ b/IISU/ClientPsSqlManager.cs @@ -95,6 +95,11 @@ public ClientPsSqlManager(InventoryJobConfiguration config,Runspace runSpace) } } + public ClientPsSqlManager(ReenrollmentJobConfiguration config, string serverUsername, string serverPassword) + { + _logger = LogHandler.GetClassLogger(); + } + public JobResult UnBindCertificate() { try diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index d765792..5d00dc7 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -128,12 +128,12 @@ private JobResult performAddition(ManagementJobConfiguration config) { _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); if (!PsHelper.IsCSPFound(PsHelper.GetCSPList(myRunspace), cryptoProvider)) - { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the Crypto Provider provided."); } + { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } } if (storePath != null) { - _logger.LogInformation($"Attempting to add certificate to cert store: {storePath}"); + _logger.LogInformation($"Attempting to add WinCert certificate to cert store: {storePath}"); ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); @@ -149,7 +149,7 @@ private JobResult performAddition(ManagementJobConfiguration config) return result; - // This method is being retired + // This method is retired // return manager.AddCertificate(certificateContents, privateKeyPassword, storePath); } else diff --git a/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs b/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs index a261438..cf9abc6 100644 --- a/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs @@ -34,7 +34,7 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm _logger = LogHandler.GetClassLogger(typeof(ReEnrollment)); ClientPSCertStoreReEnrollment myReEnrollment = new ClientPSCertStoreReEnrollment(_logger, _resolver); - return myReEnrollment.PerformReEnrollment(config, submitReenrollmentUpdate, false); + return myReEnrollment.PerformReEnrollment(config, submitReenrollmentUpdate, CertStoreBindingTypeENUM.None); } } diff --git a/IISU/ImplementedStoreTypes/Win/WinInventory.cs b/IISU/ImplementedStoreTypes/Win/WinInventory.cs index 33b8d65..6332e1a 100644 --- a/IISU/ImplementedStoreTypes/Win/WinInventory.cs +++ b/IISU/ImplementedStoreTypes/Win/WinInventory.cs @@ -39,7 +39,6 @@ public List GetInventoryItems(Runspace runSpace, string st { "SAN", cert.SAN } }; - inventoryItems.Add(new CurrentInventoryItem { Certificates = new[] { cert.CertificateData }, diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index e9bd38d..b4a3c33 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using System.IO; using System.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; @@ -114,23 +115,69 @@ public JobResult ProcessJob(ManagementJobConfiguration config) private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) { - _logger.LogTrace("Before PerformAddition..."); + try + { +#nullable enable + string certificateContents = config.JobCertificate.Contents; + string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; + string storePath = config.CertificateStoreDetails.StorePath; + long jobNumber = config.JobHistoryId; + string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); +#nullable disable - string certificateContents = config.JobCertificate.Contents; - string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; - string storePath = config.CertificateStoreDetails.StorePath; - long jobNumber = config.JobHistoryId; + // If a crypto provider was provided, check to see if it exists + if (cryptoProvider != null) + { + _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); + if (!PsHelper.IsCSPFound(PsHelper.GetCSPList(myRunspace), cryptoProvider)) + { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } + } - ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); + if (storePath != null) + { + _logger.LogInformation($"Attempting to add IISU certificate to cert store: {storePath}"); + } - if (result.Result == OrchestratorJobStatusJobResult.Success) + ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); + + // This method is retired + //JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); + + // Write the certificate contents to a temporary file on the remote computer, returning the filename. + string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); + _logger.LogTrace($"{filePath} was created."); + + // Using certutil on the remote computer, import the pfx file using a supplied csp if any. + JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider); + + // Delete the temporary file + manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); + + if (result.Result == OrchestratorJobStatusJobResult.Success) + { + // Bind to IIS + _logger.LogInformation("Attempting to bind certificate to website."); + ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); + result = iisManager.BindCertificate(manager.X509Cert); + + // Provide logging information + if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the website."); } + else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the website. Check the logs for more information."); } + + return result; + } + else return result; + } + catch (Exception e) { - // Bind to IIS - ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); - result = iisManager.BindCertificate(manager.X509Cert); - return result; - } else return result; + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = + $"Management/Add {e.Message}" + }; + } } private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) diff --git a/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs index 4307725..8c07b0b 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs @@ -37,7 +37,7 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm _logger = LogHandler.GetClassLogger(typeof(ReEnrollment)); ClientPSCertStoreReEnrollment myReEnrollment = new ClientPSCertStoreReEnrollment(_logger, _resolver); - return myReEnrollment.PerformReEnrollment(config, submitReEnrollmentUpdate, true); + return myReEnrollment.PerformReEnrollment(config, submitReEnrollmentUpdate, CertStoreBindingTypeENUM.WinIIS); } } diff --git a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs index 457cdcf..f88f046 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs @@ -104,7 +104,9 @@ public List GetInventoryItems(Runspace runSpace, string st { "IPAddress", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0] }, { "HostName", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2] }, { "SniFlag", sniValue }, - { "Protocol", binding.Properties["Protocol"]?.Value } + { "Protocol", binding.Properties["Protocol"]?.Value }, + { "ProviderName", foundCert.CryptoServiceProvider }, + { "SAN", foundCert.SAN } }; myBoundCerts.Add( diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index aaa5d8b..028c6a6 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -13,6 +13,7 @@ // limitations under the License. using System; +using System.IO; using System.Management.Automation.Runspaces; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; @@ -111,31 +112,75 @@ private JobResult PerformAddCertificate(ManagementJobConfiguration config, strin { _logger.LogTrace("Before PerformAddition..."); - string certificateContents = config.JobCertificate.Contents; - string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; - string storePath = config.CertificateStoreDetails.StorePath; - long jobNumber = config.JobHistoryId; - - ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); - - if (result.Result == OrchestratorJobStatusJobResult.Success) + try { +#nullable enable + string certificateContents = config.JobCertificate.Contents; + string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; + string storePath = config.CertificateStoreDetails.StorePath; + long jobNumber = config.JobHistoryId; + string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); +#nullable disable - if (config.JobProperties.ContainsKey("RenewalThumbprint")) + // If a crypto provider was provided, check to see if it exists + if (cryptoProvider != null) { - RenewalThumbprint = config.JobProperties["RenewalThumbprint"].ToString(); - _logger.LogTrace($"Found Thumbprint Will Renew all Certs with this thumbprint: {RenewalThumbprint}"); + _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); + if (!PsHelper.IsCSPFound(PsHelper.GetCSPList(myRunspace), cryptoProvider)) + { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } } - // Bind to SQL Server - ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUsername, serverPassword); - result = sqlManager.BindCertificates(RenewalThumbprint,manager.X509Cert); - return result; + if (storePath != null) + { + _logger.LogInformation($"Attempting to add WinSql certificate to cert store: {storePath}"); + } + + ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); + + // This method is retired + //JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); + + // Write the certificate contents to a temporary file on the remote computer, returning the filename. + string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); + _logger.LogTrace($"{filePath} was created."); + // Using certutil on the remote computer, import the pfx file using a supplied csp if any. + JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider); + // Delete the temporary file + manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); - } else return result; + if (result.Result == OrchestratorJobStatusJobResult.Success) + { + + if (config.JobProperties.ContainsKey("RenewalThumbprint")) + { + RenewalThumbprint = config.JobProperties["RenewalThumbprint"].ToString(); + _logger.LogTrace($"Found Thumbprint Will Renew all Certs with this thumbprint: {RenewalThumbprint}"); + } + + // Bind to SQL Server + ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUsername, serverPassword); + result = sqlManager.BindCertificates(RenewalThumbprint, manager.X509Cert); + + // Provide logging information + if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the SQL Server."); } + else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the SQL Server. Check the logs for more information."); } + + return result; + } + else return result; + } + catch (Exception e) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = + $"Management/Add {e.Message}" + }; + } } private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) diff --git a/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs new file mode 100644 index 0000000..38540c1 --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs @@ -0,0 +1,44 @@ +// Copyright 2022 Keyfactor +// +// 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. + +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Extensions; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using Microsoft.Extensions.Logging; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSQL +{ + public class ReEnrollment : WinCertJobTypeBase, IReenrollmentJobExtension + { + private ILogger _logger; + private string RenewalThumbprint; + + public string ExtensionName => string.Empty; + + public ReEnrollment(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + + public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReEnrollmentUpdate) + { + _logger = LogHandler.GetClassLogger(typeof(ReEnrollment)); + + ClientPSCertStoreReEnrollment myReEnrollment = new ClientPSCertStoreReEnrollment(_logger, _resolver); + + // SQL ReEnrollment performs a different type of binding. Set the bindcertificate to false and call SQL Binding + return myReEnrollment.PerformReEnrollment(config, submitReEnrollmentUpdate, CertStoreBindingTypeENUM.WinSQL); + } + } +} diff --git a/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs b/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs index 49b2cb3..dc290fa 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs @@ -86,9 +86,10 @@ public List GetInventoryItems(Runspace runSpace, Inventory if (foundCert == null) continue; var sqlSettingsDict = new Dictionary - { - { "InstanceName", kp.Value.ToString() } - }; + { + { "InstanceName", kp.Value.ToString() }, + { "ProviderName", foundCert.CryptoServiceProvider } + }; myBoundCerts.Add( new CurrentInventoryItem diff --git a/IISU/WinCertJobTypeBase.cs b/IISU/WinCertJobTypeBase.cs index d6bcd17..0baf794 100644 --- a/IISU/WinCertJobTypeBase.cs +++ b/IISU/WinCertJobTypeBase.cs @@ -20,4 +20,11 @@ public abstract class WinCertJobTypeBase { public IPAMSecretResolver _resolver; } + + public enum CertStoreBindingTypeENUM + { + None, + WinIIS, + WinSQL + } } diff --git a/readme_source.md b/readme_source.md index 50c296f..039221d 100644 --- a/readme_source.md +++ b/readme_source.md @@ -136,7 +136,7 @@ Short Name| WinSql | Short display name for the store type Custom Capability | Leave Unchecked | Store type name orchestrator will register with. Check the box to allow entry of value Supported Job Types | Inventory, Add, Remove | Job types the extension supports Needs Server | Checked | Determines if a target server name is required when creating store -Blueprint Allowed | Unchecked | Determines if store type may be included in an Orchestrator blueprint +Blueprint Allowed | Checked | Determines if store type may be included in an Orchestrator blueprint Uses PowerShell | Unchecked | Determines if underlying implementation is PowerShell Requires Store Password | Unchecked | Determines if a store password is required when configuring an individual store. Supports Entry Password | Unchecked | Determines if an individual entry within a store can have a password. @@ -184,6 +184,7 @@ They are typically used to support binding of a certificate to a resource. Name|Display Name| Type|Default Value|Required When|Description ---|---|---|---|---|--- InstanceName | Instance Name|String||Not required | When enrolling leave blank or use MSSQLServer for the Default Instance, Instance Name for an Instance or MSSQLServer,Instance Name if enrolling to multiple instances plus the default instance. +ProviderName | Crypto Provider Name | String ||| Name of the Windows cryptographic provider to use during reenrollment jobs when generating and storing the private keys. If not specified, defaults to 'Microsoft Strong Cryptographic Provider'. This value would typically be specified when leveraging a Hardware Security Module (HSM). The specified cryptographic provider must be available on the target server being managed. The list of installed cryptographic providers can be obtained by running 'certutil -csplist' on the target Server. ![](images/SQLServerEntryParams.png) From fc7d525f227d05e5e1045dbfcd8247434bac058f Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Mon, 18 Mar 2024 22:55:12 +0000 Subject: [PATCH 04/26] Update generated README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 11df329..a448e3f 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,7 @@ Short Name| WinSql | Short display name for the store type Custom Capability | Leave Unchecked | Store type name orchestrator will register with. Check the box to allow entry of value Supported Job Types | Inventory, Add, Remove | Job types the extension supports Needs Server | Checked | Determines if a target server name is required when creating store -Blueprint Allowed | Unchecked | Determines if store type may be included in an Orchestrator blueprint +Blueprint Allowed | Checked | Determines if store type may be included in an Orchestrator blueprint Uses PowerShell | Unchecked | Determines if underlying implementation is PowerShell Requires Store Password | Unchecked | Determines if a store password is required when configuring an individual store. Supports Entry Password | Unchecked | Determines if an individual entry within a store can have a password. @@ -284,6 +284,7 @@ They are typically used to support binding of a certificate to a resource. Name|Display Name| Type|Default Value|Required When|Description ---|---|---|---|---|--- InstanceName | Instance Name|String||Not required | When enrolling leave blank or use MSSQLServer for the Default Instance, Instance Name for an Instance or MSSQLServer,Instance Name if enrolling to multiple instances plus the default instance. +ProviderName | Crypto Provider Name | String ||| Name of the Windows cryptographic provider to use during reenrollment jobs when generating and storing the private keys. If not specified, defaults to 'Microsoft Strong Cryptographic Provider'. This value would typically be specified when leveraging a Hardware Security Module (HSM). The specified cryptographic provider must be available on the target server being managed. The list of installed cryptographic providers can be obtained by running 'certutil -csplist' on the target Server. ![](images/SQLServerEntryParams.png) From 5e8ec1ac5396fd2aaed2b8e57bd85c674c7be461 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 26 Mar 2024 14:00:40 -0500 Subject: [PATCH 05/26] Updated some documentation. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d789fe0..4f27cfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ * Changed the way certificates are added to cert stores. CertUtil is now used to import the PFX certificate into the associated store. The CSP is now considered when maintaining certificates, empty CSP values will result in using the machines default CSP. * Added the Crypto Service Provider and SAN Entry Parameters to be used on Inventory queries, Adding and ReEnrollments for the WinCert and IISU extensions. The CSP was added for WinSWL for Management Add jobs only. +2.3.2 +* Changed the Open Cert Store access level from a '5' to 'MaxAllowed' + 2.3.1 + * Added additional error trapping for WinRM connections to allow actual error on failure. 2.3.0 From 56396f112268138bb8582ce7fa83a703785c236e Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 26 Mar 2024 14:01:28 -0500 Subject: [PATCH 06/26] Added additional exception handleing for SQL Management jobs. Updated the manifest for SQL ReEnrollment. --- CHANGELOG.md | 6 ++- IISU/ClientPsSqlManager.cs | 4 ++ .../WinSQL/ReEnrollment.cs | 1 - IISU/PSHelper.cs | 41 +++++++++++++------ IISU/manifest.json | 4 ++ 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f27cfd..ea7fd92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,14 @@ 2.4.0 * Changed the way certificates are added to cert stores. CertUtil is now used to import the PFX certificate into the associated store. The CSP is now considered when maintaining certificates, empty CSP values will result in using the machines default CSP. -* Added the Crypto Service Provider and SAN Entry Parameters to be used on Inventory queries, Adding and ReEnrollments for the WinCert and IISU extensions. The CSP was added for WinSWL for Management Add jobs only. +* Added the Crypto Service Provider and SAN Entry Parameters to be used on Inventory queries, Adding and ReEnrollments for the WinCert, WinSQL and IISU extensions. +* Changed how Client Machine Names are handled when a 'localhost' connection is desiered. The new naming convention is: {machineName}|localmachine. This will eliminate the issue of unqiue naming conflicts. +* Updated the manifest.json to now include WinSQL ReEnrollment. +* Updated the integration-manifest.json file for new fields in cert store types. 2.3.2 * Changed the Open Cert Store access level from a '5' to 'MaxAllowed' 2.3.1 - * Added additional error trapping for WinRM connections to allow actual error on failure. 2.3.0 diff --git a/IISU/ClientPsSqlManager.cs b/IISU/ClientPsSqlManager.cs index 45a485a..627116f 100644 --- a/IISU/ClientPsSqlManager.cs +++ b/IISU/ClientPsSqlManager.cs @@ -187,6 +187,10 @@ public string GetSqlInstanceValue(string instanceName,PowerShell ps) } return null; } + catch (ArgumentOutOfRangeException ex) + { + throw new Exception($"There were no SQL instances with the name: {instanceName}. Please check the spelling of the SQL instance."); + } catch (Exception e) { throw new Exception($"Error when initiating getting instance name from registry: {e.Message}", e.InnerException); diff --git a/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs index 38540c1..fc2e623 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs @@ -22,7 +22,6 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSQL public class ReEnrollment : WinCertJobTypeBase, IReenrollmentJobExtension { private ILogger _logger; - private string RenewalThumbprint; public string ExtensionName => string.Empty; diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 202c38b..cc51b29 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -32,8 +32,35 @@ public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMa _logger = LogHandler.GetClassLogger(); _logger.MethodEntry(); - if (clientMachineName.ToLower() != "localhost") - + // 2.4 - Client Machine Name now follows the naming conventions of {clientMachineName}|{localMachine} + // If the clientMachineName is just 'localhost', it will maintain that as locally only (as previosuly) + // If there is no 2nd part to the clientMachineName, a remote PowerShell session will be created + + // Break the clientMachineName into parts + string[] parts = clientMachineName.Split('|'); + + // Extract the client machine name and arguments based upon the number of parts + string machineName = parts.Length > 1 ? parts[0] : clientMachineName; + string argument = parts.Length > 1 ? parts[1] : null; + + // Determine if this is truely a local connection + bool isLocal = (machineName.ToLower() == "localhost") || (argument != null && argument.ToLower() == "localmachine"); + + _logger.LogInformation($"Full clientMachineName={clientMachineName} | machineName={machineName} | argument={argument} | isLocal={isLocal}"); + + if (isLocal) + { + // Create an out of process PowerShell runspace and explictly use version 5.1 + // This is needed when running as a service, which is how the orchestrator extension operates + // Interestingly this is not needd when running as a console application + // TODO: Consider refactoring this so that we properly dispose of these objects instead of waiting on the GC + + PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(5, 1), null, null, false); + Runspace rs = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Array.Empty()), instance); + + return rs; + } + else { var connInfo = new WSManConnectionInfo(new Uri($"{winRmProtocol}://{clientMachineName}:{winRmPort}/wsman")); connInfo.IncludePortInSPN = includePortInSpn; @@ -48,16 +75,6 @@ public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMa } return RunspaceFactory.CreateRunspace(connInfo); } - - // Create an out of process PowerShell runspace and explictly use version 5.1 - // This is needed when running as a service, which is how the orchestrator extension operates - // Interestingly this is not needd when running as a console application - // TODO: Consider refactoring this so that we properly dispose of these objects instead of waiting on the GC - - PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(5, 1), null, null, false); - Runspace rs = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Array.Empty()), instance); - - return rs; } public static IEnumerable GetCSPList(Runspace myRunspace) diff --git a/IISU/manifest.json b/IISU/manifest.json index d7c7c64..c13cfe7 100644 --- a/IISU/manifest.json +++ b/IISU/manifest.json @@ -32,6 +32,10 @@ "CertStores.WinSql.Management": { "assemblypath": "WindowsCertStore.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql.Management" + }, + "CertStores.WinSql.ReEnrollment": { + "assemblypath": "WindowsCertStore.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql.ReEnrollment" } } } From 316e4ec71c6140a413b58c1a69f7d853b89cb1eb Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 27 Mar 2024 16:26:04 -0500 Subject: [PATCH 07/26] Changed 'MaxAllowed' back to '5' and documented what that value represents. Cleaned up some old code. --- IISU/CertificateStore.cs | 4 +- IISU/ClientPSCertStoreManager.cs | 99 +------------------ IISU/ClientPsSqlManager.cs | 31 +++++- .../WinSQL/ReEnrollment.cs | 2 +- integration-manifest.json | 30 +++++- 5 files changed, 65 insertions(+), 101 deletions(-) diff --git a/IISU/CertificateStore.cs b/IISU/CertificateStore.cs index 25c284a..0ea35e6 100644 --- a/IISU/CertificateStore.cs +++ b/IISU/CertificateStore.cs @@ -41,10 +41,12 @@ public void RemoveCertificate(string thumbprint) { using var ps = PowerShell.Create(); ps.Runspace = RunSpace; + + // Open with value of 5 means: Open existing only (4) + Open ReadWrite (1) var removeScript = $@" $ErrorActionPreference = 'Stop' $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{StorePath}','LocalMachine') - $certStore.Open('MaxAllowed') + $certStore.Open(5) $certToRemove = $certStore.Certificates.Find(0,'{thumbprint}',$false) if($certToRemove.Count -gt 0) {{ $certStore.Remove($certToRemove[0]) diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index f65d60d..6809857 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -17,7 +17,6 @@ using Microsoft.Extensions.Logging; using System; using System.IO; -using System.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Security.Cryptography.X509Certificates; @@ -185,101 +184,6 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin } } - [Obsolete("This method is no longer used. Certificates are added by creating and importing PFX files.", false)] - public JobResult AddCertificate(string certificateContents, string privateKeyPassword, string storePath) - { - try - { - using var ps = PowerShell.Create(); - - _logger.MethodEntry(); - - ps.Runspace = _runspace; - - _logger.LogTrace($"Creating X509 Cert from: {certificateContents}"); - x509Cert = new X509Certificate2 - ( - Convert.FromBase64String(certificateContents), - privateKeyPassword, - X509KeyStorageFlags.MachineKeySet | - X509KeyStorageFlags.PersistKeySet | - X509KeyStorageFlags.Exportable - ); - - // Add Certificate - var funcScript = @" - $ErrorActionPreference = ""Stop"" - - function InstallPfxToMachineStore([byte[]]$bytes, [string]$password, [string]$storeName) { - $certStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $storeName, ""LocalMachine"" - $certStore.Open('MaxAllowed') - $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $bytes, $password, 18 <# Persist, Machine #> - $certStore.Add($cert) - - $certStore.Close(); - }"; - - ps.AddScript(funcScript).AddStatement(); - _logger.LogDebug("InstallPfxToMachineStore Statement Added..."); - - ps.AddCommand("InstallPfxToMachineStore") - .AddParameter("bytes", Convert.FromBase64String(certificateContents)) - .AddParameter("password", privateKeyPassword) - .AddParameter("storeName", $@"\\{_runspace.ConnectionInfo.ComputerName}\{storePath}"); - - _logger.LogTrace("InstallPfxToMachineStore Command Added..."); - - foreach (var cmd in ps.Commands.Commands) - { - _logger.LogTrace("Logging PowerShell Command"); - _logger.LogTrace(cmd.CommandText); - } - - _logger.LogTrace("Invoking ps..."); - ps.Invoke(); - _logger.LogTrace("ps Invoked..."); - - if (ps.HadErrors) - { - _logger.LogTrace("ps Has Errors"); - var psError = ps.Streams.Error.ReadAll() - .Aggregate(string.Empty, (current, error) => current + error?.ErrorDetails.Message); - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = _jobNumber, - FailureMessage = - $"Site {storePath} on server {_runspace.ConnectionInfo.ComputerName}: {psError}" - }; - } - } - - _logger.LogTrace("Clearing Commands..."); - ps.Commands.Clear(); - _logger.LogTrace("Commands Cleared.."); - _logger.LogInformation($"Certificate was successfully added to cert store: {storePath}"); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = _jobNumber, - FailureMessage = "" - }; - } - catch (Exception e) - { - _logger.LogError($"Error Occurred in ClientPSCertStoreManager.AddCertificate(): {e.Message}"); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = _jobNumber, - FailureMessage = $"Error Occurred in InstallCertificate {LogHandler.FlattenException(e)}" - }; - } - } - public void RemoveCertificate(string thumbprint, string storePath) { using var ps = PowerShell.Create(); @@ -288,10 +192,11 @@ public void RemoveCertificate(string thumbprint, string storePath) ps.Runspace = _runspace; + // Open with value of 5 means: Open existing only (4) + Open ReadWrite (1) var removeScript = $@" $ErrorActionPreference = 'Stop' $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{storePath}','LocalMachine') - $certStore.Open('MaxAllowed') + $certStore.Open(5) $certToRemove = $certStore.Certificates.Find(0,'{thumbprint}',$false) if($certToRemove.Count -gt 0) {{ $certStore.Remove($certToRemove[0]) diff --git a/IISU/ClientPsSqlManager.cs b/IISU/ClientPsSqlManager.cs index 627116f..b36084f 100644 --- a/IISU/ClientPsSqlManager.cs +++ b/IISU/ClientPsSqlManager.cs @@ -22,7 +22,9 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Net; using System.Security.Cryptography.X509Certificates; +using System.Web.Services.Description; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { @@ -35,6 +37,7 @@ internal class ClientPsSqlManager private string RenewalThumbprint { get; set; } = ""; private string ClientMachineName { get; set; } private long JobHistoryID { get; set; } + private readonly ILogger _logger; private readonly Runspace _runSpace; @@ -91,13 +94,39 @@ public ClientPsSqlManager(InventoryJobConfiguration config,Runspace runSpace) } catch (Exception e) { - throw new Exception($"Error when initiating a SQL Management Job: {e.Message}", e.InnerException); + throw new Exception($"Error when initiating a SQL Inventory Job: {e.Message}", e.InnerException); } } public ClientPsSqlManager(ReenrollmentJobConfiguration config, string serverUsername, string serverPassword) { _logger = LogHandler.GetClassLogger(); + + try + { + ClientMachineName = config.CertificateStoreDetails.ClientMachine; + JobHistoryID = config.JobHistoryId; + + if (config.JobProperties.ContainsKey("InstanceName")) + { + var instanceRef = config.JobProperties["InstanceName"]?.ToString(); + SqlInstanceName = string.IsNullOrEmpty(instanceRef) ? "MSSQLSERVER" : instanceRef; + } + + // Establish PowerShell Runspace + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + string winRmProtocol = jobProperties.WinRmProtocol; + string winRmPort = jobProperties.WinRmPort; + bool includePortInSPN = jobProperties.SpnPortFlag; + RestartService = jobProperties.RestartService; + + _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); + _runSpace = PsHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); + } + catch (Exception e) + { + throw new Exception($"Error when initiating a SQL ReEnrollment Job: {e.Message}", e.InnerException); + } } public JobResult UnBindCertificate() diff --git a/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs index fc2e623..c4e178f 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs @@ -17,7 +17,7 @@ using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSQL +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql { public class ReEnrollment : WinCertJobTypeBase, IReenrollmentJobExtension { diff --git a/integration-manifest.json b/integration-manifest.json index 0745fd6..87833f4 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -305,7 +305,7 @@ "HasPrivateKey": false, "OnAdd": false, "OnRemove": false, - "OnReenrollment": false + "OnReenrollment": true }, "DependsOn": "", "DefaultValue": "", @@ -397,6 +397,34 @@ "OnRemove": false, "OnReenrollment": false } + }, + { + "Name": "ProviderName", + "DisplayName": "Crypto Provider Name", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": false + }, + "DependsOn": "", + "DefaultValue": "", + "Options": "" + }, + { + "Name": "SAN", + "DisplayName": "SAN", + "Type": "String", + "RequiredWhen": { + "HasPrivateKey": false, + "OnAdd": false, + "OnRemove": false, + "OnReenrollment": true + }, + "DependsOn": "", + "DefaultValue": "", + "Options": "" } ], "PasswordOptions": { From 5384f6d66cd942a7adcefe25af48efd567bfe962 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 27 Mar 2024 17:35:50 -0500 Subject: [PATCH 08/26] Updated the readme-source documentation --- readme_source.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/readme_source.md b/readme_source.md index 039221d..46da11f 100644 --- a/readme_source.md +++ b/readme_source.md @@ -45,7 +45,7 @@ For customers wishing to use something other than the local administrator accoun - Read and Write values in the registry (HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server) when performing SQL Server certificate binding. ## Creating New Certificate Store Types -Currently this orchestrator handles two extensions: IISU for IIS servers with bound certificates and WinCert for general Windows Certificates. +Currently this orchestrator handles three types of extensions: IISU for IIS servers with bound certificates, WinCert for general Windows Certificates and WinSql for managing certificates for SQL Server. Below describes how each of these certificate store types are created and configured.
IISU Extension @@ -134,7 +134,7 @@ CONFIG ELEMENT | VALUE | DESCRIPTION Name | Windows SQL Server Certificate| Display name for the store type (may be customized) Short Name| WinSql | Short display name for the store type Custom Capability | Leave Unchecked | Store type name orchestrator will register with. Check the box to allow entry of value -Supported Job Types | Inventory, Add, Remove | Job types the extension supports +Supported Job Types | Inventory, Add, Remove, Reenrollment | Job types the extension supports Needs Server | Checked | Determines if a target server name is required when creating store Blueprint Allowed | Checked | Determines if store type may be included in an Orchestrator blueprint Uses PowerShell | Unchecked | Determines if underlying implementation is PowerShell @@ -185,6 +185,7 @@ Name|Display Name| Type|Default Value|Required When|Description ---|---|---|---|---|--- InstanceName | Instance Name|String||Not required | When enrolling leave blank or use MSSQLServer for the Default Instance, Instance Name for an Instance or MSSQLServer,Instance Name if enrolling to multiple instances plus the default instance. ProviderName | Crypto Provider Name | String ||| Name of the Windows cryptographic provider to use during reenrollment jobs when generating and storing the private keys. If not specified, defaults to 'Microsoft Strong Cryptographic Provider'. This value would typically be specified when leveraging a Hardware Security Module (HSM). The specified cryptographic provider must be available on the target server being managed. The list of installed cryptographic providers can be obtained by running 'certutil -csplist' on the target Server. +SAN | SAN | String || Reenrolling | Specifies Subject Alternative Name (SAN) to be used when performing reenrollment jobs. Certificate templates generally require a SAN that matches the subject of the certificate (per RFC 2818). Format is a list of = entries separated by ampersands. Examples: 'dns=www.mysite.com' for a single SAN or 'dns=www.mysite.com&dns=www.mysite2.com' for multiple SANs. Can be made optional if RFC 2818 is disabled on the CA. ![](images/SQLServerEntryParams.png) @@ -262,6 +263,8 @@ Click Save to save the Certificate Store Type. ## Creating New Certificate Stores Once the Certificate Store Types have been created, you need to create the Certificate Stores prior to using the extension. + +**Note:** A new naming convention for the Client Machine allows for multiple stores on the same server with different cert store path and cert store types. This convention is \{MachineName\}\|\{[optional]localmachine\}. If the optional value is 'localmachine' (legacy 'localhost' is still supported) is supplied, a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store. Here are the settings required for each Store Type previously configured.
@@ -274,7 +277,7 @@ CONFIG ELEMENT |DESCRIPTION ----------------|--------------- Category | Select IIS Bound Certificate or the customized certificate store display name from above. Container | Optional container to associate certificate store with. -Client Machine | Hostname of the Windows Server containing the certificate store to be managed. If this value is 'localhost', a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store and perform IIS binding operations. If this value is a hostname, a WinRM session will be established using the credentials specified in the Server Username and Server Password fields. +Client Machine | Contains the Hostname of the Windows Server containing the certificate store to be managed. If this value is a hostname, a WinRM session will be established using the credentials specified in the Server Username and Server Password fields. Store Path | Windows certificate store to manage. Choose "My" for the Personal Store or "WebHosting" for the Web Hosting Store. Orchestrator | Select an approved orchestrator capable of managing IIS Bound Certificates (one that has declared the IISU capability) WinRm Protocol | Protocol to use when establishing the WinRM session. (Listener on Client Machine must be configured for selected protocol.) @@ -326,7 +329,7 @@ CONFIG ELEMENT |DESCRIPTION ----------------|--------------- Category | Select Windows Certificate or the customized certificate store display name from above. Container | Optional container to associate certificate store with. -Client Machine | Hostname of the Windows Server containing the certificate store to be managed. If this value is 'localhost', a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store. If this value is a hostname, a WinRM session will be established using the credentials specified in the Server Username and Server Password fields. +Client Machine | Hostname of the Windows Server containing the certificate store to be managed. If this value is a hostname, a WinRM session will be established using the credentials specified in the Server Username and Server Password fields. Store Path | Windows certificate store to manage. Store must exist in the Local Machine store on the target server. Orchestrator | Select an approved orchestrator capable of managing Windows Certificates (one that has declared the WinCert capability) WinRm Protocol | Protocol to use when establishing the WinRM session. (Listener on Client Machine must be configured for selected protocol.) From 8fbc6c7cf7c87d03a022906ebabe903a9c07d2f3 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Wed, 27 Mar 2024 22:36:20 +0000 Subject: [PATCH 09/26] Update generated README --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a448e3f..2da5062 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ For customers wishing to use something other than the local administrator accoun - Read and Write values in the registry (HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server) when performing SQL Server certificate binding. ## Creating New Certificate Store Types -Currently this orchestrator handles two extensions: IISU for IIS servers with bound certificates and WinCert for general Windows Certificates. +Currently this orchestrator handles three types of extensions: IISU for IIS servers with bound certificates, WinCert for general Windows Certificates and WinSql for managing certificates for SQL Server. Below describes how each of these certificate store types are created and configured.
IISU Extension @@ -234,7 +234,7 @@ CONFIG ELEMENT | VALUE | DESCRIPTION Name | Windows SQL Server Certificate| Display name for the store type (may be customized) Short Name| WinSql | Short display name for the store type Custom Capability | Leave Unchecked | Store type name orchestrator will register with. Check the box to allow entry of value -Supported Job Types | Inventory, Add, Remove | Job types the extension supports +Supported Job Types | Inventory, Add, Remove, Reenrollment | Job types the extension supports Needs Server | Checked | Determines if a target server name is required when creating store Blueprint Allowed | Checked | Determines if store type may be included in an Orchestrator blueprint Uses PowerShell | Unchecked | Determines if underlying implementation is PowerShell @@ -285,6 +285,7 @@ Name|Display Name| Type|Default Value|Required When|Description ---|---|---|---|---|--- InstanceName | Instance Name|String||Not required | When enrolling leave blank or use MSSQLServer for the Default Instance, Instance Name for an Instance or MSSQLServer,Instance Name if enrolling to multiple instances plus the default instance. ProviderName | Crypto Provider Name | String ||| Name of the Windows cryptographic provider to use during reenrollment jobs when generating and storing the private keys. If not specified, defaults to 'Microsoft Strong Cryptographic Provider'. This value would typically be specified when leveraging a Hardware Security Module (HSM). The specified cryptographic provider must be available on the target server being managed. The list of installed cryptographic providers can be obtained by running 'certutil -csplist' on the target Server. +SAN | SAN | String || Reenrolling | Specifies Subject Alternative Name (SAN) to be used when performing reenrollment jobs. Certificate templates generally require a SAN that matches the subject of the certificate (per RFC 2818). Format is a list of = entries separated by ampersands. Examples: 'dns=www.mysite.com' for a single SAN or 'dns=www.mysite.com&dns=www.mysite2.com' for multiple SANs. Can be made optional if RFC 2818 is disabled on the CA. ![](images/SQLServerEntryParams.png) @@ -362,6 +363,8 @@ Click Save to save the Certificate Store Type. ## Creating New Certificate Stores Once the Certificate Store Types have been created, you need to create the Certificate Stores prior to using the extension. + +**Note:** A new naming convention for the Client Machine allows for multiple stores on the same server with different cert store path and cert store types. This convention is \{MachineName\}\|\{[optional]localmachine\}. If the optional value is 'localmachine' (legacy 'localhost' is still supported) is supplied, a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store. Here are the settings required for each Store Type previously configured.
@@ -374,7 +377,7 @@ CONFIG ELEMENT |DESCRIPTION ----------------|--------------- Category | Select IIS Bound Certificate or the customized certificate store display name from above. Container | Optional container to associate certificate store with. -Client Machine | Hostname of the Windows Server containing the certificate store to be managed. If this value is 'localhost', a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store and perform IIS binding operations. If this value is a hostname, a WinRM session will be established using the credentials specified in the Server Username and Server Password fields. +Client Machine | Contains the Hostname of the Windows Server containing the certificate store to be managed. If this value is a hostname, a WinRM session will be established using the credentials specified in the Server Username and Server Password fields. Store Path | Windows certificate store to manage. Choose "My" for the Personal Store or "WebHosting" for the Web Hosting Store. Orchestrator | Select an approved orchestrator capable of managing IIS Bound Certificates (one that has declared the IISU capability) WinRm Protocol | Protocol to use when establishing the WinRM session. (Listener on Client Machine must be configured for selected protocol.) @@ -426,7 +429,7 @@ CONFIG ELEMENT |DESCRIPTION ----------------|--------------- Category | Select Windows Certificate or the customized certificate store display name from above. Container | Optional container to associate certificate store with. -Client Machine | Hostname of the Windows Server containing the certificate store to be managed. If this value is 'localhost', a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store. If this value is a hostname, a WinRM session will be established using the credentials specified in the Server Username and Server Password fields. +Client Machine | Hostname of the Windows Server containing the certificate store to be managed. If this value is a hostname, a WinRM session will be established using the credentials specified in the Server Username and Server Password fields. Store Path | Windows certificate store to manage. Store must exist in the Local Machine store on the target server. Orchestrator | Select an approved orchestrator capable of managing Windows Certificates (one that has declared the WinCert capability) WinRm Protocol | Protocol to use when establishing the WinRM session. (Listener on Client Machine must be configured for selected protocol.) From d9d4417dae1aebded81d4f54de769c95b6752bc6 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 16 Apr 2024 12:20:39 -0700 Subject: [PATCH 10/26] Modified how runspaces are created for local machines access . Added code to get the last exit code from certutil to accurrately report errors --- IISU/ClientPSCertStoreManager.cs | 36 ++++++++++++++++++++++++++------ IISU/PSHelper.cs | 10 +-------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index 6809857..f462a25 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -14,6 +14,7 @@ using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; +using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; using System; using System.IO; @@ -120,6 +121,7 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin string script = @" param($pfxFilePath, $privateKeyPassword, $cspName) $output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1 + $c = $LASTEXITCODE $output "; @@ -132,6 +134,7 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin string script = @" param($pfxFilePath, $privateKeyPassword, $cspName) $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $pfxFilePath 2>&1 + $c = $LASTEXITCODE $output "; @@ -144,15 +147,36 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin // Invoke the script var results = ps.Invoke(); - // Check for errors in the output + //Get the last exist code returned from the script + int lastExitCode = (int)(ps.Runspace.SessionStateProxy.PSVariable.GetValue("c")); + bool isError = false; - foreach (var result in results) + if (lastExitCode != 0) + { + isError = true; + string outputMsg = ""; + + foreach (var result in results) + { + string outputLine = result.ToString(); + if (!string.IsNullOrEmpty(outputLine)) + { + outputMsg += "\n" + outputLine; + } + } + _logger.LogError(outputMsg); + } + else { - string outputLine = result.ToString(); - if (!string.IsNullOrEmpty(outputLine) && outputLine.Contains("Error")) + // Check for errors in the output + foreach (var result in results) { - isError = true; - Console.WriteLine(outputLine); // Print the error message + string outputLine = result.ToString(); + if (!string.IsNullOrEmpty(outputLine) && outputLine.Contains("Error")) + { + isError = true; + _logger.LogError(outputLine); + } } } diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index cc51b29..7d2990e 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -50,15 +50,7 @@ public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMa if (isLocal) { - // Create an out of process PowerShell runspace and explictly use version 5.1 - // This is needed when running as a service, which is how the orchestrator extension operates - // Interestingly this is not needd when running as a console application - // TODO: Consider refactoring this so that we properly dispose of these objects instead of waiting on the GC - - PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(5, 1), null, null, false); - Runspace rs = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Array.Empty()), instance); - - return rs; + return RunspaceFactory.CreateRunspace(); } else { From 4b7b26e09234665b337fde503eb20e5fc9571dd2 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 17 Apr 2024 06:18:17 -0700 Subject: [PATCH 11/26] Improved some trace log information. --- IISU/ImplementedStoreTypes/Win/Management.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index 5d00dc7..ba624ed 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License.using Keyfactor.Logging; +// Ignore Spelling: Keyfactor + using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; @@ -128,7 +130,7 @@ private JobResult performAddition(ManagementJobConfiguration config) { _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); if (!PsHelper.IsCSPFound(PsHelper.GetCSPList(myRunspace), cryptoProvider)) - { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } + { throw new Exception($"The Crypto Provider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } } if (storePath != null) @@ -138,23 +140,22 @@ private JobResult performAddition(ManagementJobConfiguration config) ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); // Write the certificate contents to a temporary file on the remote computer, returning the filename. + _logger.LogTrace($"Creating temporary pfx file."); string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); - _logger.LogTrace($"{filePath} was created."); // Using certutil on the remote computer, import the pfx file using a supplied csp if any. + _logger.LogTrace($"Importing temporary PFX File: {filePath}."); JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider); // Delete the temporary file + _logger.LogTrace($"Deleting temporary PFX File: {filePath}."); manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); return result; - - // This method is retired - // return manager.AddCertificate(certificateContents, privateKeyPassword, storePath); } else { - throw new Exception($"The store pathis empty or null."); + throw new Exception($"The store path is empty or null."); } } catch (Exception e) From 3eb72af2091d0b84864aec721e12698571e3dfdf Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 17 Apr 2024 18:20:46 -0500 Subject: [PATCH 12/26] Added error trapping when attepting to get the last exit code from a remote machine. Microsoft does not allow you to retrieve information from remote computers due to security and architecture. --- IISU/ClientPSCertStoreManager.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index f462a25..1a37864 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -147,8 +147,18 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin // Invoke the script var results = ps.Invoke(); - //Get the last exist code returned from the script - int lastExitCode = (int)(ps.Runspace.SessionStateProxy.PSVariable.GetValue("c")); + // Get the last exist code returned from the script + // This statement is in a try/catch block because PSVariable.GetValue() is not a valid method on a remote PS Session and throws an exception. + // Due to security reasons and Windows architecture, retreiving values from a remote system is not supported. + int lastExitCode = 0; + try + { + lastExitCode = (int)ps.Runspace.SessionStateProxy.PSVariable.GetValue("c"); + } + catch (Exception) + { + } + bool isError = false; if (lastExitCode != 0) From e6f2f1fbd4262f64c7fa975f865e8cf0fd86f468 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Mon, 22 Apr 2024 13:20:31 -0500 Subject: [PATCH 13/26] Corrected some misspelled words in the ReadMe. --- readme_source.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/readme_source.md b/readme_source.md index 46da11f..c5c4a2e 100644 --- a/readme_source.md +++ b/readme_source.md @@ -9,7 +9,7 @@ The returned list will contain the actual certificate store name to be used when By default, most certificates are stored in the “Personal” (My) and “Web Hosting” (WebHosting) stores. -This extension implements four job types: Inventory, Management Add/Remove, and ReEnrollment. +This extension implements four job types: Inventory, Management Add/Remove, and Reenrollment. WinRM is used to remotely manage the certificate stores and IIS bindings. WinRM must be properly configured to allow the orchestrator on the server to manage the certificates. Setting up WinRM is not in the scope of this document. @@ -21,7 +21,7 @@ In version 2.0 of the IIS Orchestrator, the certificate store type has been rena **Note: There is an additional (and deprecated) certificate store type of “IIS” that ships with the Keyfactor platform. Migration of certificate stores from the “IIS” type to either the “IISBin” or “IISU” types is not currently supported.** -**Note: If Looking to use GMSA Accounts to run the Service Kefyactor Command 10.2 or greater is required for No Value checkbox to work** +**Note: If Looking to use GMSA Accounts to run the Service Keyfactor Command 10.2 or greater is required for No Value checkbox to work** ## Security and Permission Considerations From an official support point of view, Local Administrator permissions are required on the target server. Some customers have been successful with using other accounts and granting rights to the underlying certificate and private key stores. Due to complexities with the interactions between Group Policy, WinRM, User Account Control, and other unpredictable customer environmental factors, Keyfactor cannot provide assistance with using accounts other than the local administrator account. @@ -34,7 +34,7 @@ For customers wishing to use something other than the local administrator accoun * WinRM needs to be properly set up between the server hosting the UO and the target server. This means that a WinRM client running on the UO server when running in the context of the UO service account needs to be able to create a session on the target server using the configured credentials of the target server and any PowerShell commands running on the remote session need to have appropriate permissions. -* Even though a given account may be in the administrators group or have administrative privledges on the target system and may be able to execute certificate and binding operations when running locally, the same account may not work when being used via WinRM. User Account Control (UAC) can get in the way and filter out administrative privledges. UAC / WinRM configuration has a LocalAccountTokenFilterPolicy setting that can be adjusted to not filter out administrative privledges for remote users, but enabling this may have other security ramifications. +* Even though a given account may be in the administrators group or have administrative privileges on the target system and may be able to execute certificate and binding operations when running locally, the same account may not work when being used via WinRM. User Account Control (UAC) can get in the way and filter out administrative privledges. UAC / WinRM configuration has a LocalAccountTokenFilterPolicy setting that can be adjusted to not filter out administrative privledges for remote users, but enabling this may have other security ramifications. * The following list may not be exhaustive, but in general the account (when running under a remote WinRM session) needs permissions to: - Instantiate and open a .NET X509Certificates.X509Store object for the target certificate store and be able to read and write both the certificates and related private keys. Note that ACL permissions on the stores and private keys are separate. @@ -374,12 +374,12 @@ Case Number|Case Name|Enrollment Params|Expected Results|Passed|Screenshot Case Number|Case Name|Enrollment Params|Expected Results|Passed|Screenshot ----|------------------------|------------------------------------|--------------|----------------|------------------------- -1 |New Cert Enrollment To Default Instance Leave Blank|**Intance Name:** |Cert will be Installed to default Instance, Service will be restarted for default instance|True|![](images/SQLTestCase1.gif) -2 |New Cert Enrollment To Default Instance MSSQLServer|**Intance Name:** MSSQLServer|Cert will be Installed to default Instance, Service will be restarted for default instance|True|![](images/SQLTestCase2.gif) -3 |New Cert Enrollment To Instance1|**Intance Name:** Instance1|Cert will be Installed to Instance1, Service will be restarted for Instance1|True|![](images/SQLTestCase3.gif) -4 |New Cert Enrollment To Instance1 and Default Instance|**Intance Name:** MSSQLServer,Instance1|Cert will be Installed to Default Instance and Instance1, Service will be restarted for Default Instance and Instance1|True|![](images/SQLTestCase4.gif) +1 |New Cert Enrollment To Default Instance Leave Blank|**Instance Name:** |Cert will be Installed to default Instance, Service will be restarted for default instance|True|![](images/SQLTestCase1.gif) +2 |New Cert Enrollment To Default Instance MSSQLServer|**Instance Name:** MSSQLServer|Cert will be Installed to default Instance, Service will be restarted for default instance|True|![](images/SQLTestCase2.gif) +3 |New Cert Enrollment To Instance1|**Instance Name:** Instance1|Cert will be Installed to Instance1, Service will be restarted for Instance1|True|![](images/SQLTestCase3.gif) +4 |New Cert Enrollment To Instance1 and Default Instance|**Instance Name:** MSSQLServer,Instance1|Cert will be Installed to Default Instance and Instance1, Service will be restarted for Default Instance and Instance1|True|![](images/SQLTestCase4.gif) 5 |One Click Renew Cert Enrollment To Instance1 and Default Instance|N/A|Cert will be Renewed/Installed to Default Instance and Instance1, Service will be restarted for Default Instance and Instance1|True|![](images/SQLTestCase5.gif) -6 |Remove Cert From Instance1 and Default Instance|**Intance Name:** |Cert from TC5 will be Removed From Default Instance and Instance1|True|![](images/SQLTestCase6.gif) +6 |Remove Cert From Instance1 and Default Instance|**Instance Name:** |Cert from TC5 will be Removed From Default Instance and Instance1|True|![](images/SQLTestCase6.gif) 7 |Inventory Different Certs Different Instance|N/A|2 Certs will be inventoried and each tied to its Instance|True|![](images/SQLTestCase7.gif) 8 |Inventory Same Cert Different Instance|N/A|2 Certs will be inventoried the cert will have a comma separated list of Instances|True|![](images/SQLTestCase8.gif) 9 |Inventory Against Machine Without SQL Server|N/A|Will fail with error saying it can't find SQL Server|True|![](images/SQLTestCase9.gif) From 67aff5a79ec73c636696a6963d19336399d1f81a Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Mon, 22 Apr 2024 18:21:02 +0000 Subject: [PATCH 14/26] Update generated README --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2da5062..ff4ac71 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ The returned list will contain the actual certificate store name to be used when By default, most certificates are stored in the “Personal” (My) and “Web Hosting” (WebHosting) stores. -This extension implements four job types: Inventory, Management Add/Remove, and ReEnrollment. +This extension implements four job types: Inventory, Management Add/Remove, and Reenrollment. WinRM is used to remotely manage the certificate stores and IIS bindings. WinRM must be properly configured to allow the orchestrator on the server to manage the certificates. Setting up WinRM is not in the scope of this document. @@ -121,7 +121,7 @@ In version 2.0 of the IIS Orchestrator, the certificate store type has been rena **Note: There is an additional (and deprecated) certificate store type of “IIS” that ships with the Keyfactor platform. Migration of certificate stores from the “IIS” type to either the “IISBin” or “IISU” types is not currently supported.** -**Note: If Looking to use GMSA Accounts to run the Service Kefyactor Command 10.2 or greater is required for No Value checkbox to work** +**Note: If Looking to use GMSA Accounts to run the Service Keyfactor Command 10.2 or greater is required for No Value checkbox to work** ## Security and Permission Considerations From an official support point of view, Local Administrator permissions are required on the target server. Some customers have been successful with using other accounts and granting rights to the underlying certificate and private key stores. Due to complexities with the interactions between Group Policy, WinRM, User Account Control, and other unpredictable customer environmental factors, Keyfactor cannot provide assistance with using accounts other than the local administrator account. @@ -134,7 +134,7 @@ For customers wishing to use something other than the local administrator accoun * WinRM needs to be properly set up between the server hosting the UO and the target server. This means that a WinRM client running on the UO server when running in the context of the UO service account needs to be able to create a session on the target server using the configured credentials of the target server and any PowerShell commands running on the remote session need to have appropriate permissions. -* Even though a given account may be in the administrators group or have administrative privledges on the target system and may be able to execute certificate and binding operations when running locally, the same account may not work when being used via WinRM. User Account Control (UAC) can get in the way and filter out administrative privledges. UAC / WinRM configuration has a LocalAccountTokenFilterPolicy setting that can be adjusted to not filter out administrative privledges for remote users, but enabling this may have other security ramifications. +* Even though a given account may be in the administrators group or have administrative privileges on the target system and may be able to execute certificate and binding operations when running locally, the same account may not work when being used via WinRM. User Account Control (UAC) can get in the way and filter out administrative privledges. UAC / WinRM configuration has a LocalAccountTokenFilterPolicy setting that can be adjusted to not filter out administrative privledges for remote users, but enabling this may have other security ramifications. * The following list may not be exhaustive, but in general the account (when running under a remote WinRM session) needs permissions to: - Instantiate and open a .NET X509Certificates.X509Store object for the target certificate store and be able to read and write both the certificates and related private keys. Note that ACL permissions on the stores and private keys are separate. @@ -474,12 +474,12 @@ Case Number|Case Name|Enrollment Params|Expected Results|Passed|Screenshot Case Number|Case Name|Enrollment Params|Expected Results|Passed|Screenshot ----|------------------------|------------------------------------|--------------|----------------|------------------------- -1 |New Cert Enrollment To Default Instance Leave Blank|**Intance Name:** |Cert will be Installed to default Instance, Service will be restarted for default instance|True|![](images/SQLTestCase1.gif) -2 |New Cert Enrollment To Default Instance MSSQLServer|**Intance Name:** MSSQLServer|Cert will be Installed to default Instance, Service will be restarted for default instance|True|![](images/SQLTestCase2.gif) -3 |New Cert Enrollment To Instance1|**Intance Name:** Instance1|Cert will be Installed to Instance1, Service will be restarted for Instance1|True|![](images/SQLTestCase3.gif) -4 |New Cert Enrollment To Instance1 and Default Instance|**Intance Name:** MSSQLServer,Instance1|Cert will be Installed to Default Instance and Instance1, Service will be restarted for Default Instance and Instance1|True|![](images/SQLTestCase4.gif) +1 |New Cert Enrollment To Default Instance Leave Blank|**Instance Name:** |Cert will be Installed to default Instance, Service will be restarted for default instance|True|![](images/SQLTestCase1.gif) +2 |New Cert Enrollment To Default Instance MSSQLServer|**Instance Name:** MSSQLServer|Cert will be Installed to default Instance, Service will be restarted for default instance|True|![](images/SQLTestCase2.gif) +3 |New Cert Enrollment To Instance1|**Instance Name:** Instance1|Cert will be Installed to Instance1, Service will be restarted for Instance1|True|![](images/SQLTestCase3.gif) +4 |New Cert Enrollment To Instance1 and Default Instance|**Instance Name:** MSSQLServer,Instance1|Cert will be Installed to Default Instance and Instance1, Service will be restarted for Default Instance and Instance1|True|![](images/SQLTestCase4.gif) 5 |One Click Renew Cert Enrollment To Instance1 and Default Instance|N/A|Cert will be Renewed/Installed to Default Instance and Instance1, Service will be restarted for Default Instance and Instance1|True|![](images/SQLTestCase5.gif) -6 |Remove Cert From Instance1 and Default Instance|**Intance Name:** |Cert from TC5 will be Removed From Default Instance and Instance1|True|![](images/SQLTestCase6.gif) +6 |Remove Cert From Instance1 and Default Instance|**Instance Name:** |Cert from TC5 will be Removed From Default Instance and Instance1|True|![](images/SQLTestCase6.gif) 7 |Inventory Different Certs Different Instance|N/A|2 Certs will be inventoried and each tied to its Instance|True|![](images/SQLTestCase7.gif) 8 |Inventory Same Cert Different Instance|N/A|2 Certs will be inventoried the cert will have a comma separated list of Instances|True|![](images/SQLTestCase8.gif) 9 |Inventory Against Machine Without SQL Server|N/A|Will fail with error saying it can't find SQL Server|True|![](images/SQLTestCase9.gif) From 268c18bc447621416935f99cbebf7ea83f60f575 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Thu, 25 Apr 2024 23:38:42 +0000 Subject: [PATCH 15/26] Update generated README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ff4ac71..b2cf34b 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The Keyfactor Universal Orchestrator may be installed on either Windows or Linux |Supports Management Remove|✓ | | |Supports Create Store| | | |Supports Discovery| | | -|Supports Renrollment|✓ | | +|Supports Reenrollment|✓ | | |Supports Inventory|✓ | | @@ -486,3 +486,6 @@ Case Number|Case Name|Enrollment Params|Expected Results|Passed|Screenshot
+When creating cert store type manually, that store property names and entry parameter names are case sensitive + + From 13e853340ac8fc45a784b1af21dbc3b54e1ef018 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 28 May 2024 19:07:46 -0500 Subject: [PATCH 16/26] POC for Macys - do not use for production. --- IISU/ClientPSCertStoreManager.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index 1a37864..02f1b74 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -112,6 +112,8 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { try { + _logger.LogTrace("Entering ImportPFX"); + using (PowerShell ps = PowerShell.Create()) { ps.Runspace = _runspace; @@ -119,7 +121,7 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin if (cryptoProviderName == null) { string script = @" - param($pfxFilePath, $privateKeyPassword, $cspName) + param($pfxFilePath, $privateKeyPassword) $output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1 $c = $LASTEXITCODE $output @@ -154,9 +156,11 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin try { lastExitCode = (int)ps.Runspace.SessionStateProxy.PSVariable.GetValue("c"); + _logger.LogTrace($"Last exit code: {lastExitCode}"); } catch (Exception) { + _logger.LogTrace("Unable to get the last exit code."); } @@ -182,7 +186,10 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin foreach (var result in results) { string outputLine = result.ToString(); - if (!string.IsNullOrEmpty(outputLine) && outputLine.Contains("Error")) + + _logger.LogTrace(outputLine); + + if (!string.IsNullOrEmpty(outputLine) && outputLine.Contains("Error") || outputLine.Contains("permissions are needed")) { isError = true; _logger.LogError(outputLine); From 3ce27b4f378196b382d7cdd0c8d08fc66c0b95a0 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 29 May 2024 11:51:30 -0500 Subject: [PATCH 17/26] Minor changes --- IISU/ClientPSCertStoreManager.cs | 71 ++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index 02f1b74..56dd5d5 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -18,6 +18,7 @@ using Microsoft.Extensions.Logging; using System; using System.IO; +using System.Linq.Expressions; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Security.Cryptography.X509Certificates; @@ -48,6 +49,10 @@ public ClientPSCertStoreManager(ILogger logger, Runspace runSpace, long jobNumbe public string CreatePFXFile(string certificateContents, string privateKeyPassword) { + _logger.LogTrace("Entering CreatePFXFile"); + if (!string.IsNullOrEmpty(privateKeyPassword)) { _logger.LogTrace("privateKeyPassword was present"); } + else _logger.LogTrace("No privateKeyPassword Presented"); + try { // Create the x509 certificate @@ -79,11 +84,13 @@ public string CreatePFXFile(string certificateContents, string privateKeyPasswor var results = ps.Invoke(); // Get the result (temporary file path) returned by the script + _logger.LogTrace($"Results after creating PFX File: {results[0].ToString()}"); return results[0].ToString(); } } - catch (Exception) + catch (Exception ex) { + _logger.LogError(ex.ToString()); throw new Exception("An error occurred while attempting to create and write the X509 contents."); } } @@ -120,11 +127,30 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin if (cryptoProviderName == null) { + //string script = @" + //param($pfxFilePath, $privateKeyPassword) + //$output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1 + //$c = $LASTEXITCODE 2>&1 + //$output + //"; + string script = @" param($pfxFilePath, $privateKeyPassword) $output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1 - $c = $LASTEXITCODE + $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" + $stuff = certutil -dump + + if ($stuff.GetType().Name -eq ""String"") + { + $stuff = @($stuff, $exit_message) + } + else + { + $stuff += $exit_message + } + $output + $stuff "; ps.AddScript(script); @@ -136,8 +162,20 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin string script = @" param($pfxFilePath, $privateKeyPassword, $cspName) $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $pfxFilePath 2>&1 - $c = $LASTEXITCODE + $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" + $stuff = certutil -dump + + if ($stuff.GetType().Name -eq ""String"") + { + $stuff = @($stuff, $exit_message) + } + else + { + $stuff += $exit_message + } + $output + $stuff "; ps.AddScript(script); @@ -147,6 +185,7 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin } // Invoke the script + _logger.LogTrace("Attempting to import the PFX"); var results = ps.Invoke(); // Get the last exist code returned from the script @@ -155,7 +194,7 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin int lastExitCode = 0; try { - lastExitCode = (int)ps.Runspace.SessionStateProxy.PSVariable.GetValue("c"); + lastExitCode = GetLastExitCode(results[^1].ToString()); _logger.LogTrace($"Last exit code: {lastExitCode}"); } catch (Exception) @@ -225,6 +264,30 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin } } + private int GetLastExitCode(string result) + { + // Split the string by colon + string[] parts = result.Split(':'); + + // Ensure the split result has the expected parts + if (parts.Length == 2 && parts[0] == "LASTEXITCODE") + { + // Parse the second part into an integer + if (int.TryParse(parts[1], out int lastExitCode)) + { + return lastExitCode; + } + else + { + throw new Exception("Failed to parse the LASTEXITCODE value."); + } + } + else + { + throw new Exception("The last element does not contain the expected format."); + } + } + public void RemoveCertificate(string thumbprint, string storePath) { using var ps = PowerShell.Create(); From 1a5353d56fca20aba9ef24a767d944d9c2b7203b Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 29 May 2024 11:55:41 -0500 Subject: [PATCH 18/26] Added store path and addstore for certs with no private keys --- IISU/ClientPSCertStoreManager.cs | 78 ++++++++++++++----- IISU/ImplementedStoreTypes/Win/Management.cs | 2 +- .../WinIIS/Management.cs | 2 +- .../WinSQL/Management.cs | 2 +- 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index 1a37864..a373c20 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -15,6 +15,7 @@ using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.Extensions.Logging; using System; using System.IO; @@ -108,7 +109,7 @@ public void DeletePFXFile(string filePath, string fileName) } } - public JobResult ImportPFXFile(string filePath, string privateKeyPassword, string cryptoProviderName) + public JobResult ImportPFXFile(string filePath, string privateKeyPassword, string cryptoProviderName, string storePath) { try { @@ -118,30 +119,67 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin if (cryptoProviderName == null) { - string script = @" - param($pfxFilePath, $privateKeyPassword, $cspName) - $output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1 - $c = $LASTEXITCODE - $output - "; + if (privateKeyPassword == null) + { + // If no private key password is provided, import the pfx file directory to the store using addstore argument + string script = @" + param($pfxFilePath, $storePath) + $output = certutil -addstore Cert:\LocalMachine\$storePath $pfxFilePath 2>&1 + $c = $LASTEXITCODE + $output + "; + + ps.AddScript(script); + ps.AddParameter("pfxFilePath", filePath); + ps.AddParameter("storePath", storePath); + } + else + { + // Use ImportPFX to import the pfx file with private key password to the appropriate cert store + string script = @" + param($pfxFilePath, $privateKeyPassword, $storePath) + $output = certutil -p $privateKeyPassword -importpfx Cert:\LocalMachine\$storePath $pfxFilePath 2>&1 + $c = $LASTEXITCODE + $output + "; - ps.AddScript(script); - ps.AddParameter("pfxFilePath", filePath); - ps.AddParameter("privateKeyPassword", privateKeyPassword); + ps.AddScript(script); + ps.AddParameter("pfxFilePath", filePath); + ps.AddParameter("privateKeyPassword", privateKeyPassword); + ps.AddParameter("storePath", storePath); + } } else { - string script = @" - param($pfxFilePath, $privateKeyPassword, $cspName) - $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $pfxFilePath 2>&1 - $c = $LASTEXITCODE - $output - "; + if (privateKeyPassword == null) + { + string script = @" + param($pfxFilePath, $cspName, $storePath) + $output = certutil -csp $cspName -addstore LocalMachine\$storePath $pfxFilePath 2>&1 + $c = $LASTEXITCODE + $output + "; + + ps.AddScript(script); + ps.AddParameter("pfxFilePath", filePath); + ps.AddParameter("cspName", cryptoProviderName); + ps.AddParameter("storePath", storePath); + } + else + { + string script = @" + param($pfxFilePath, $privateKeyPassword, $cspName, $storePath) + $output = certutil -importpfx -csp $cspName -p $privateKeyPassword LocalMachine\$storePath $pfxFilePath 2>&1 + $c = $LASTEXITCODE + $output + "; - ps.AddScript(script); - ps.AddParameter("pfxFilePath", filePath); - ps.AddParameter("privateKeyPassword", privateKeyPassword); - ps.AddParameter("cspName", cryptoProviderName); + ps.AddScript(script); + ps.AddParameter("pfxFilePath", filePath); + ps.AddParameter("privateKeyPassword", privateKeyPassword); + ps.AddParameter("cspName", cryptoProviderName); + ps.AddParameter("storePath", storePath); + } } // Invoke the script diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index ba624ed..f251c7f 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -145,7 +145,7 @@ private JobResult performAddition(ManagementJobConfiguration config) // Using certutil on the remote computer, import the pfx file using a supplied csp if any. _logger.LogTrace($"Importing temporary PFX File: {filePath}."); - JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider); + JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); // Delete the temporary file _logger.LogTrace($"Deleting temporary PFX File: {filePath}."); diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index b4a3c33..72efe5f 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -148,7 +148,7 @@ private JobResult PerformAddCertificate(ManagementJobConfiguration config, strin _logger.LogTrace($"{filePath} was created."); // Using certutil on the remote computer, import the pfx file using a supplied csp if any. - JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider); + JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); // Delete the temporary file manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index 028c6a6..e304f1e 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -145,7 +145,7 @@ private JobResult PerformAddCertificate(ManagementJobConfiguration config, strin _logger.LogTrace($"{filePath} was created."); // Using certutil on the remote computer, import the pfx file using a supplied csp if any. - JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider); + JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); // Delete the temporary file manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); From 5be3649aae40c359098d209edea926dceda89e1a Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Fri, 31 May 2024 14:50:41 -0500 Subject: [PATCH 19/26] #ab58570 Added additional error trapping and logging. Also modified the certutil logic to use -addstore when no password was provided when adding a certificate. --- IISU/ClientPSCertStoreManager.cs | 39 ++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index 069d071..df91f38 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -133,8 +133,17 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin // If no private key password is provided, import the pfx file directory to the store using addstore argument string script = @" param($pfxFilePath, $storePath) - $output = certutil -addstore Cert:\LocalMachine\$storePath $pfxFilePath 2>&1 - $c = $LASTEXITCODE + $output = certutil -addstore $storePath $pfxFilePath 2>&1 + $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" + + if ($output.GetType().Name -eq ""String"") + { + $output = @($output, $exit_message) + } + else + { + $output += $exit_message + } $output "; @@ -148,10 +157,9 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin string script = @" param($pfxFilePath, $privateKeyPassword) - $output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1 + $output = certutil -importpfx -p $privateKeyPassword $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" $stuff = certutil -dump - if ($stuff.GetType().Name -eq ""String"") { $stuff = @($stuff, $exit_message) @@ -160,7 +168,6 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { $stuff += $exit_message } - $output $stuff "; @@ -177,9 +184,20 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { string script = @" param($pfxFilePath, $cspName, $storePath) - $output = certutil -csp $cspName -addstore LocalMachine\$storePath $pfxFilePath 2>&1 - $c = $LASTEXITCODE + $output = certutil -csp $cspName -addstore $storePath $pfxFilePath 2>&1 + $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" + + $stuff = certutil -dump + if ($stuff.GetType().Name -eq ""String"") + { + $stuff = @($stuff, $exit_message) + } + else + { + $stuff += $exit_message + } $output + $stuff "; ps.AddScript(script); @@ -191,10 +209,10 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { string script = @" param($pfxFilePath, $privateKeyPassword, $cspName) - $output = certutil -importpfx -csp $cspName -p $privateKeyPassword LocalMachine\$storePath $pfxFilePath 2>&1 + $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - $stuff = certutil -dump + $stuff = certutil -dump if ($stuff.GetType().Name -eq ""String"") { $stuff = @($stuff, $exit_message) @@ -203,7 +221,6 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { $stuff += $exit_message } - $output $stuff "; @@ -221,8 +238,6 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin var results = ps.Invoke(); // Get the last exist code returned from the script - // This statement is in a try/catch block because PSVariable.GetValue() is not a valid method on a remote PS Session and throws an exception. - // Due to security reasons and Windows architecture, retreiving values from a remote system is not supported. int lastExitCode = 0; try { From 278fa47254151ab3a2eb307f1e89819e9fb52ba7 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Fri, 31 May 2024 14:50:41 -0500 Subject: [PATCH 20/26] AB#58570 Added additional error trapping and logging. Also modified the certutil logic to use -addstore when no password was provided when adding a certificate. --- CHANGELOG.md | 4 ++++ IISU/ClientPSCertStoreManager.cs | 39 ++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7fd92..f4bda5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +2.4.1 +* Modified the CertUtil logic to use the -addstore argument when no password is sent with the certificate information. +* Added additional error trapping and trace logs + 2.4.0 * Changed the way certificates are added to cert stores. CertUtil is now used to import the PFX certificate into the associated store. The CSP is now considered when maintaining certificates, empty CSP values will result in using the machines default CSP. * Added the Crypto Service Provider and SAN Entry Parameters to be used on Inventory queries, Adding and ReEnrollments for the WinCert, WinSQL and IISU extensions. diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index 069d071..df91f38 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -133,8 +133,17 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin // If no private key password is provided, import the pfx file directory to the store using addstore argument string script = @" param($pfxFilePath, $storePath) - $output = certutil -addstore Cert:\LocalMachine\$storePath $pfxFilePath 2>&1 - $c = $LASTEXITCODE + $output = certutil -addstore $storePath $pfxFilePath 2>&1 + $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" + + if ($output.GetType().Name -eq ""String"") + { + $output = @($output, $exit_message) + } + else + { + $output += $exit_message + } $output "; @@ -148,10 +157,9 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin string script = @" param($pfxFilePath, $privateKeyPassword) - $output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1 + $output = certutil -importpfx -p $privateKeyPassword $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" $stuff = certutil -dump - if ($stuff.GetType().Name -eq ""String"") { $stuff = @($stuff, $exit_message) @@ -160,7 +168,6 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { $stuff += $exit_message } - $output $stuff "; @@ -177,9 +184,20 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { string script = @" param($pfxFilePath, $cspName, $storePath) - $output = certutil -csp $cspName -addstore LocalMachine\$storePath $pfxFilePath 2>&1 - $c = $LASTEXITCODE + $output = certutil -csp $cspName -addstore $storePath $pfxFilePath 2>&1 + $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" + + $stuff = certutil -dump + if ($stuff.GetType().Name -eq ""String"") + { + $stuff = @($stuff, $exit_message) + } + else + { + $stuff += $exit_message + } $output + $stuff "; ps.AddScript(script); @@ -191,10 +209,10 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { string script = @" param($pfxFilePath, $privateKeyPassword, $cspName) - $output = certutil -importpfx -csp $cspName -p $privateKeyPassword LocalMachine\$storePath $pfxFilePath 2>&1 + $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - $stuff = certutil -dump + $stuff = certutil -dump if ($stuff.GetType().Name -eq ""String"") { $stuff = @($stuff, $exit_message) @@ -203,7 +221,6 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { $stuff += $exit_message } - $output $stuff "; @@ -221,8 +238,6 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin var results = ps.Invoke(); // Get the last exist code returned from the script - // This statement is in a try/catch block because PSVariable.GetValue() is not a valid method on a remote PS Session and throws an exception. - // Due to security reasons and Windows architecture, retreiving values from a remote system is not supported. int lastExitCode = 0; try { From 85b63a65e1f585cef1f86d9be88138a3c9d53692 Mon Sep 17 00:00:00 2001 From: Matthew Dobrowsky Date: Fri, 28 Jun 2024 04:12:04 +0000 Subject: [PATCH 21/26] move migration files together --- Migration-Scripts/{ => Legacy-IIS}/CreateIISUCertStoreType.sql | 0 Migration-Scripts/{ => Legacy-IIS}/CreateWinCertStoreType.sql | 0 Migration-Scripts/{ => Legacy-IIS}/DeleteIISStores.sql | 0 .../Legacy-IIS/LegacyIISMigrationGuide.md | 0 Migration-Scripts/{ => Legacy-IIS}/UpgradeIISPersonalToIISU.sql | 0 .../{ => Legacy-IIS}/UpgradeIISPersonalToWinCert.sql | 0 .../{ => Legacy-IIS}/UpgradeIISRevokedAndRootsToWinCert.sql | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename Migration-Scripts/{ => Legacy-IIS}/CreateIISUCertStoreType.sql (100%) rename Migration-Scripts/{ => Legacy-IIS}/CreateWinCertStoreType.sql (100%) rename Migration-Scripts/{ => Legacy-IIS}/DeleteIISStores.sql (100%) rename LegacyIISMigrationGuide.md => Migration-Scripts/Legacy-IIS/LegacyIISMigrationGuide.md (100%) rename Migration-Scripts/{ => Legacy-IIS}/UpgradeIISPersonalToIISU.sql (100%) rename Migration-Scripts/{ => Legacy-IIS}/UpgradeIISPersonalToWinCert.sql (100%) rename Migration-Scripts/{ => Legacy-IIS}/UpgradeIISRevokedAndRootsToWinCert.sql (100%) diff --git a/Migration-Scripts/CreateIISUCertStoreType.sql b/Migration-Scripts/Legacy-IIS/CreateIISUCertStoreType.sql similarity index 100% rename from Migration-Scripts/CreateIISUCertStoreType.sql rename to Migration-Scripts/Legacy-IIS/CreateIISUCertStoreType.sql diff --git a/Migration-Scripts/CreateWinCertStoreType.sql b/Migration-Scripts/Legacy-IIS/CreateWinCertStoreType.sql similarity index 100% rename from Migration-Scripts/CreateWinCertStoreType.sql rename to Migration-Scripts/Legacy-IIS/CreateWinCertStoreType.sql diff --git a/Migration-Scripts/DeleteIISStores.sql b/Migration-Scripts/Legacy-IIS/DeleteIISStores.sql similarity index 100% rename from Migration-Scripts/DeleteIISStores.sql rename to Migration-Scripts/Legacy-IIS/DeleteIISStores.sql diff --git a/LegacyIISMigrationGuide.md b/Migration-Scripts/Legacy-IIS/LegacyIISMigrationGuide.md similarity index 100% rename from LegacyIISMigrationGuide.md rename to Migration-Scripts/Legacy-IIS/LegacyIISMigrationGuide.md diff --git a/Migration-Scripts/UpgradeIISPersonalToIISU.sql b/Migration-Scripts/Legacy-IIS/UpgradeIISPersonalToIISU.sql similarity index 100% rename from Migration-Scripts/UpgradeIISPersonalToIISU.sql rename to Migration-Scripts/Legacy-IIS/UpgradeIISPersonalToIISU.sql diff --git a/Migration-Scripts/UpgradeIISPersonalToWinCert.sql b/Migration-Scripts/Legacy-IIS/UpgradeIISPersonalToWinCert.sql similarity index 100% rename from Migration-Scripts/UpgradeIISPersonalToWinCert.sql rename to Migration-Scripts/Legacy-IIS/UpgradeIISPersonalToWinCert.sql diff --git a/Migration-Scripts/UpgradeIISRevokedAndRootsToWinCert.sql b/Migration-Scripts/Legacy-IIS/UpgradeIISRevokedAndRootsToWinCert.sql similarity index 100% rename from Migration-Scripts/UpgradeIISRevokedAndRootsToWinCert.sql rename to Migration-Scripts/Legacy-IIS/UpgradeIISRevokedAndRootsToWinCert.sql From 6753d853eacecf38a880407e46078c91ddbc71ef Mon Sep 17 00:00:00 2001 From: Bob Pokorny <55611381+rcpokorny@users.noreply.github.com> Date: Wed, 3 Jul 2024 08:45:04 -0500 Subject: [PATCH 22/26] 59831 error getting iisu bound certs (#106) * Added additional trace logging information * Minor Exception Handling Update * ab#59831 Added additional error trapping, fixed typo, fixed missing parameter when adding IIS certificate --- CHANGELOG.md | 9 +++- IISU/ClientPSCertStoreInventory.cs | 6 ++- IISU/ClientPSCertStoreManager.cs | 41 ++++++++----------- .../ImplementedStoreTypes/Win/WinInventory.cs | 4 ++ .../ImplementedStoreTypes/WinIIS/Inventory.cs | 6 +-- .../WinIIS/Management.cs | 2 +- .../WinIIS/WinIISInventory.cs | 26 ++++++++++-- .../WinSQL/Management.cs | 2 +- IISU/PSHelper.cs | 10 +++-- IISU/WindowsCertStore.csproj | 2 +- README.md | 4 +- readme_source.md | 4 +- 12 files changed, 75 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4bda5e..d71d6f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +2.4.2 +* Correct false positive error when completing an IIS inventory job. +* Revert to specifying the version of PowerShell to use when establishing a local PowerShell Runspace. +* Fixed typo in error message. + 2.4.1 * Modified the CertUtil logic to use the -addstore argument when no password is sent with the certificate information. * Added additional error trapping and trace logs @@ -5,7 +10,7 @@ 2.4.0 * Changed the way certificates are added to cert stores. CertUtil is now used to import the PFX certificate into the associated store. The CSP is now considered when maintaining certificates, empty CSP values will result in using the machines default CSP. * Added the Crypto Service Provider and SAN Entry Parameters to be used on Inventory queries, Adding and ReEnrollments for the WinCert, WinSQL and IISU extensions. -* Changed how Client Machine Names are handled when a 'localhost' connection is desiered. The new naming convention is: {machineName}|localmachine. This will eliminate the issue of unqiue naming conflicts. +* Changed how Client Machine Names are handled when a 'localhost' connection is desired. The new naming convention is: {machineName}|localmachine. This will eliminate the issue of unique naming conflicts. * Updated the manifest.json to now include WinSQL ReEnrollment. * Updated the integration-manifest.json file for new fields in cert store types. @@ -49,7 +54,7 @@ 2.0.0 * Add support for reenrollment jobs (On Device Key Generation) with the ability to specify a cryptographic provider. Specification of cryptographic provider allows HSM (Hardware Security Module) use. * Local PAM Support added (requires Universal Orchestrator Framework version 10.1) -* Certificate store type changed from IISBin to IISU. See readme for migration notes. +* Certificate store type changed from IISBin to IISU. See README for migration notes. 1.1.3 diff --git a/IISU/ClientPSCertStoreInventory.cs b/IISU/ClientPSCertStoreInventory.cs index 82a6365..0bde6f5 100644 --- a/IISU/ClientPSCertStoreInventory.cs +++ b/IISU/ClientPSCertStoreInventory.cs @@ -64,6 +64,8 @@ public List GetCertificatesFromStore(Runspace runSpace, string stor ps.AddScript(certStoreScript); + _logger.LogTrace($"Executing the following script:\n{certStoreScript}"); + var certs = ps.Invoke(); foreach (var c in certs) @@ -77,11 +79,13 @@ public List GetCertificatesFromStore(Runspace runSpace, string stor SAN = Certificate.Utilities.FormatSAN($"{c.Properties["san"]?.Value}") }); } - + _logger.LogTrace($"found: {myCertificates.Count} certificate(s), exiting GetCertificatesFromStore()"); return myCertificates; } catch (Exception ex) { + _logger.LogTrace($"An error occurred in the WinCert GetCertificatesFromStore method:\n{ex.Message}"); + throw new CertificateStoreException( $"Error listing certificate in {storePath} store on {runSpace.ConnectionInfo.ComputerName}: {ex.Message}"); } diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index df91f38..3a75960 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -133,7 +133,7 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin // If no private key password is provided, import the pfx file directory to the store using addstore argument string script = @" param($pfxFilePath, $storePath) - $output = certutil -addstore $storePath $pfxFilePath 2>&1 + $output = certutil -f -addstore $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" if ($output.GetType().Name -eq ""String"") @@ -156,20 +156,19 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin // Use ImportPFX to import the pfx file with private key password to the appropriate cert store string script = @" - param($pfxFilePath, $privateKeyPassword) - $output = certutil -importpfx -p $privateKeyPassword $storePath $pfxFilePath 2>&1 + param($pfxFilePath, $privateKeyPassword, $storePath) + $output = certutil -f -importpfx -p $privateKeyPassword $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - $stuff = certutil -dump - if ($stuff.GetType().Name -eq ""String"") + + if ($output.GetType().Name -eq ""String"") { - $stuff = @($stuff, $exit_message) + $output = @($output, $exit_message) } else { - $stuff += $exit_message + $output += $exit_message } $output - $stuff "; ps.AddScript(script); @@ -184,20 +183,18 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin { string script = @" param($pfxFilePath, $cspName, $storePath) - $output = certutil -csp $cspName -addstore $storePath $pfxFilePath 2>&1 + $output = certutil -f -csp $cspName -addstore $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - $stuff = certutil -dump - if ($stuff.GetType().Name -eq ""String"") + if ($output.GetType().Name -eq ""String"") { - $stuff = @($stuff, $exit_message) + $output = @($output, $exit_message) } else { - $stuff += $exit_message + $output += $exit_message } $output - $stuff "; ps.AddScript(script); @@ -208,21 +205,19 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin else { string script = @" - param($pfxFilePath, $privateKeyPassword, $cspName) - $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $storePath $pfxFilePath 2>&1 + param($pfxFilePath, $privateKeyPassword, $cspName, $storePath) + $output = certutil -f -importpfx -csp $cspName -p $privateKeyPassword $storePath $pfxFilePath 2>&1 $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - $stuff = certutil -dump - if ($stuff.GetType().Name -eq ""String"") + if ($output.GetType().Name -eq ""String"") { - $stuff = @($stuff, $exit_message) + $output = @($output, $exit_message) } else { - $stuff += $exit_message + $output += $exit_message } $output - $stuff "; ps.AddScript(script); @@ -244,9 +239,9 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin lastExitCode = GetLastExitCode(results[^1].ToString()); _logger.LogTrace($"Last exit code: {lastExitCode}"); } - catch (Exception) + catch (Exception ex) { - _logger.LogTrace("Unable to get the last exit code."); + _logger.LogTrace(ex.Message); } diff --git a/IISU/ImplementedStoreTypes/Win/WinInventory.cs b/IISU/ImplementedStoreTypes/Win/WinInventory.cs index 6332e1a..99954e6 100644 --- a/IISU/ImplementedStoreTypes/Win/WinInventory.cs +++ b/IISU/ImplementedStoreTypes/Win/WinInventory.cs @@ -23,12 +23,15 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { internal class WinInventory : ClientPSCertStoreInventory { + private ILogger _logger; public WinInventory(ILogger logger) : base(logger) { + _logger = logger; } public List GetInventoryItems(Runspace runSpace, string storePath) { + _logger.LogTrace("Entering WinCert GetInventoryItems."); List inventoryItems = new List(); foreach (Certificate cert in base.GetCertificatesFromStore(runSpace, storePath)) @@ -50,6 +53,7 @@ public List GetInventoryItems(Runspace runSpace, string st }); } + _logger.LogTrace($"Found {inventoryItems.Count} certificates. Exiting WinCert GetInventoryItems."); return inventoryItems; } } diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index 86fffe3..a3855ec 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -75,13 +75,13 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven WinIISInventory IISInventory = new WinIISInventory(_logger); inventoryItems = IISInventory.GetInventoryItems(myRunspace, storePath); - _logger.LogTrace($"A total of {inventoryItems.Count} were found"); + _logger.LogTrace($"A total of {inventoryItems.Count} bound certificate(s) were found"); _logger.LogTrace("Closing runspace..."); myRunspace.Close(); - _logger.LogTrace("Invoking Inventory.."); + _logger.LogTrace("Invoking submitInventory.."); submitInventory.Invoke(inventoryItems); - _logger.LogTrace($"Inventory Invoked... {inventoryItems.Count} Items"); + _logger.LogTrace($"submitInventory Invoked... {inventoryItems.Count} Items"); return new JobResult { diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index 72efe5f..09877c3 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -101,7 +101,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { _logger.LogTrace(LogHandler.FlattenException(ex)); - var failureMessage = $"Managemenmt job {config.OperationType} failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; + var failureMessage = $"Management job {config.OperationType} failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; _logger.LogWarning(failureMessage); return new JobResult diff --git a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs index f88f046..2ae070b 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs @@ -27,12 +27,15 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU { internal class WinIISInventory : ClientPSCertStoreInventory { + private ILogger _logger; public WinIISInventory(ILogger logger) : base(logger) { + _logger = logger; } public List GetInventoryItems(Runspace runSpace, string storePath) { + _logger.LogTrace("Entering IISU GetInventoryItems"); // Get the raw certificate inventory from cert store List certificates = base.GetCertificatesFromStore(runSpace, storePath); @@ -51,22 +54,36 @@ public List GetInventoryItems(Runspace runSpace, string st } else { - ps2.AddScript("Set-ExecutionPolicy RemoteSigned"); + ps2.AddScript("Set-ExecutionPolicy RemoteSigned -Scope Process -Force"); ps2.AddScript("Import-Module WebAdministration"); - //var result = ps.Invoke(); } var searchScript = "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}"; ps2.AddScript(searchScript); - var iisBindings = ps2.Invoke(); // Responsible for getting all bound certificates for each website + + _logger.LogTrace($"Attempting to initiate the following script:\n{searchScript}"); + + var iisBindings = ps2.Invoke(); if (ps2.HadErrors) { - var psError = ps2.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); + _logger.LogTrace("The previous script encountered errors. See below for more info."); + string psError = string.Empty; + try + { + psError = ps2.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + (error.ErrorDetails?.Message ?? error.Exception.ToString())); + } + catch + { + } + + if (psError != null) { throw new Exception(psError); } + } if (iisBindings.Count == 0) { + _logger.LogTrace("No binding certificates were found. Exiting IISU GetInventoryItems."); return myBoundCerts; } @@ -123,6 +140,7 @@ public List GetInventoryItems(Runspace runSpace, string st } } + _logger.LogTrace($"Found {myBoundCerts.Count} bound certificates. Exiting IISU GetInventoryItems."); return myBoundCerts; } } diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index e304f1e..f13f7e5 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -96,7 +96,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { _logger.LogTrace(LogHandler.FlattenException(ex)); - var failureMessage = $"Managemenmt job {config.OperationType} failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; + var failureMessage = $"Management job {config.OperationType} failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; _logger.LogWarning(failureMessage); return new JobResult diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 7d2990e..c125751 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -33,7 +33,7 @@ public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMa _logger.MethodEntry(); // 2.4 - Client Machine Name now follows the naming conventions of {clientMachineName}|{localMachine} - // If the clientMachineName is just 'localhost', it will maintain that as locally only (as previosuly) + // If the clientMachineName is just 'localhost', it will maintain that as locally only (as previously) // If there is no 2nd part to the clientMachineName, a remote PowerShell session will be created // Break the clientMachineName into parts @@ -43,14 +43,18 @@ public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMa string machineName = parts.Length > 1 ? parts[0] : clientMachineName; string argument = parts.Length > 1 ? parts[1] : null; - // Determine if this is truely a local connection + // Determine if this is truly a local connection bool isLocal = (machineName.ToLower() == "localhost") || (argument != null && argument.ToLower() == "localmachine"); _logger.LogInformation($"Full clientMachineName={clientMachineName} | machineName={machineName} | argument={argument} | isLocal={isLocal}"); if (isLocal) { - return RunspaceFactory.CreateRunspace(); + //return RunspaceFactory.CreateRunspace(); + PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(5, 1), null, null, false); + Runspace rs = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Array.Empty()), instance); + + return rs; } else { diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index d6d1c51..b224ebf 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -31,7 +31,7 @@ - + diff --git a/README.md b/README.md index b2cf34b..038e85e 100644 --- a/README.md +++ b/README.md @@ -364,7 +364,9 @@ Click Save to save the Certificate Store Type. ## Creating New Certificate Stores Once the Certificate Store Types have been created, you need to create the Certificate Stores prior to using the extension. -**Note:** A new naming convention for the Client Machine allows for multiple stores on the same server with different cert store path and cert store types. This convention is \{MachineName\}\|\{[optional]localmachine\}. If the optional value is 'localmachine' (legacy 'localhost' is still supported) is supplied, a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store. +### Note Regarding Client Machine +If running as an agent (accessing stores on the server where the Universal Orchestrator Services is installed ONLY), the Client Machine can be entered, OR you can bypass a WinRM connection and access the local file system directly by adding "|LocalMachine" to the end of your value for Client Machine, for example "1.1.1.1|LocalMachine". In this instance the value to the left of the pipe (|) is ignored. It is important to make sure the values for Client Machine and Store Path together are unique for each certificate store created, as Keyfactor Command requires the Store Type you select, along with Client Machine, and Store Path together must be unique. To ensure this, it is good practice to put the full DNS or IP Address to the left of the | character when setting up a certificate store that will be accessed without a WinRM connection. + Here are the settings required for each Store Type previously configured.
diff --git a/readme_source.md b/readme_source.md index c5c4a2e..07c1a8c 100644 --- a/readme_source.md +++ b/readme_source.md @@ -264,7 +264,9 @@ Click Save to save the Certificate Store Type. ## Creating New Certificate Stores Once the Certificate Store Types have been created, you need to create the Certificate Stores prior to using the extension. -**Note:** A new naming convention for the Client Machine allows for multiple stores on the same server with different cert store path and cert store types. This convention is \{MachineName\}\|\{[optional]localmachine\}. If the optional value is 'localmachine' (legacy 'localhost' is still supported) is supplied, a local PowerShell runspace executing in the context of the Orchestrator service account will be used to access the certificate store. +### Note Regarding Client Machine +If running as an agent (accessing stores on the server where the Universal Orchestrator Services is installed ONLY), the Client Machine can be entered, OR you can bypass a WinRM connection and access the local file system directly by adding "|LocalMachine" to the end of your value for Client Machine, for example "1.1.1.1|LocalMachine". In this instance the value to the left of the pipe (|) is ignored. It is important to make sure the values for Client Machine and Store Path together are unique for each certificate store created, as Keyfactor Command requires the Store Type you select, along with Client Machine, and Store Path together must be unique. To ensure this, it is good practice to put the full DNS or IP Address to the left of the | character when setting up a certificate store that will be accessed without a WinRM connection. + Here are the settings required for each Store Type previously configured.
From 77ea00692b689424878a1b7eae9d2c0558684311 Mon Sep 17 00:00:00 2001 From: Matthew Dobrowsky Date: Thu, 11 Jul 2024 06:34:39 +0000 Subject: [PATCH 23/26] additional legacy migration notes --- .../Legacy-IIS/LegacyIISMigrationGuide.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Migration-Scripts/Legacy-IIS/LegacyIISMigrationGuide.md b/Migration-Scripts/Legacy-IIS/LegacyIISMigrationGuide.md index 84115a7..380e8ee 100644 --- a/Migration-Scripts/Legacy-IIS/LegacyIISMigrationGuide.md +++ b/Migration-Scripts/Legacy-IIS/LegacyIISMigrationGuide.md @@ -1,9 +1,12 @@ # Built-in IIS Certificate Store Type Migration Guide -As of Keyfactor Command v11, the built-in IIS certificate store types (IIS Personal, IIS Roots, and IIS Revoked) have been deprecated. This guide will instruct you on how to migrate the built-in IIS certificate store types to the open source iis-orchestrator certificate store types (IISU and WinCert) that can be found on the [Keyfactor GitHub page](https://github.com/Keyfactor/iis-orchestrator). +As of Keyfactor Command v11, the built-in IIS certificate store types (IIS Personal, IIS Roots, and IIS Revoked) have been deprecated. +Before upgrading to v11, if you have existing built-in IIS certificate stores they should be migrated to one of the new IIS store types. +This guide will instruct you on how to migrate the built-in IIS certificate store types to the open source iis-orchestrator certificate store types: IISU and WinCert. # Prerequisites +- Upgrade to Keyfactor Command >=10.4 (and < 11). This version or later is required to allow for the legacy IIS stores to be upgraded to both IISU and WinCert. - All orchestrators that are currently being used to orchestrate the built-in IIS store types must support the certificate store type that the built-in stores are being migrated to (IISU, WinCert, or both). - Ensure that you have a restorable database backup created before you begin the upgrade. @@ -15,12 +18,17 @@ There are six SQL scripts that are needed for this migration: Creation Scripts +These scripts can be used to create the Store Type definitions, if they do not already exist. +They may have already been created using `kfutil` or the Command portal. + ## CreateIISUCertStoreType +[CreateIISUCertStoreType.sql](./CreateIISUCertStoreType.sql) This script creates the IISU certificate store type. ## CreateWinCertStoreType +[CreateWinCertStoreType.sql](./CreateWinCertStoreType.sql) This script creates the WinCert certificate store type.
@@ -31,6 +39,7 @@ This script creates the WinCert certificate store type. ## UpgradeIISRevokedAndRootsToWinCert +[UpgradeIISRevokedAndRootsToWinCert.sql](./UpgradeIISRevokedAndRootsToWinCert.sql) This script creates a 'WinCert' certificate store copy for every 'IIS Revoked' and 'IIS Roots' certificate store. It will also create a 'WinCert' version of every 'IIS Revoked' and 'IIS Roots' certificate store container. **Notes** @@ -48,6 +57,7 @@ This script accepts three parameters that allow configuration of WinRM: ## UpgradeIISPersonalToIISU +[UpgradeIISPersonalToIISU.sql](./UpgradeIISPersonalToIISU.sql) This script creates an 'IISU' certificate store copy for a provided list of 'IIS Personal' certificate stores. It will also create an 'IISU' version of every 'IIS Personal' certificate store container. **Notes** @@ -68,6 +78,7 @@ This script accepts four parameters: ## UpgradeIISPersonalToWinCert +[UpgradeIISPersonalToWinCert.sql](./UpgradeIISPersonalToWinCert.sql) This script creates a 'WinCert' certificate store copy for a provided list of 'IIS Personal' certificate stores. It will also create a 'WinCert' version of every 'IIS Personal' certificate store container. **Notes** @@ -92,6 +103,7 @@ This script accepts four parameters: ## DeleteIISStores +[DeleteIISStores.sql](./DeleteIISStores.sql) This script will delete all IIS Personal, IIS Roots, and IIS Revoked certificate store types, certificate stores, and certificate store containers.
From 1e66b7d187b3bd1eed3cc6dedf48412369c3d927 Mon Sep 17 00:00:00 2001 From: Matthew Dobrowsky Date: Thu, 11 Jul 2024 06:55:16 +0000 Subject: [PATCH 24/26] add migration readme and readme-pre to link to migration --- .../Legacy-IIS/{LegacyIISMigrationGuide.md => readme.md} | 0 readme-src/readme-pre.md | 5 +++++ 2 files changed, 5 insertions(+) rename Migration-Scripts/Legacy-IIS/{LegacyIISMigrationGuide.md => readme.md} (100%) create mode 100644 readme-src/readme-pre.md diff --git a/Migration-Scripts/Legacy-IIS/LegacyIISMigrationGuide.md b/Migration-Scripts/Legacy-IIS/readme.md similarity index 100% rename from Migration-Scripts/Legacy-IIS/LegacyIISMigrationGuide.md rename to Migration-Scripts/Legacy-IIS/readme.md diff --git a/readme-src/readme-pre.md b/readme-src/readme-pre.md new file mode 100644 index 0000000..689bbfe --- /dev/null +++ b/readme-src/readme-pre.md @@ -0,0 +1,5 @@ +### Migrating Legacy IIS Stores +If you have existing IIS stores with the built-in IIS types, and plan to upgrade to Keyfactor Command 11 or later, you will need to migrate from the legacy store types to the IISU and WinCert store type defined in this repository. + +You can find the guide and migration scripts in this repository, located here: +[Legacy IIS Migration](./Migration-Scripts/Legacy-IIS) \ No newline at end of file From 21053b5f288f39ec71cb4efd93623b09f49cc8a3 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Thu, 11 Jul 2024 06:55:44 +0000 Subject: [PATCH 25/26] Update generated README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b2cf34b..2dd6f6d 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,11 @@ WinCertStore Orchestrator is supported by Keyfactor for Keyfactor customers. If --- +### Migrating Legacy IIS Stores +If you have existing IIS stores with the built-in IIS types, and plan to upgrade to Keyfactor Command 11 or later, you will need to migrate from the legacy store types to the IISU and WinCert store type defined in this repository. +You can find the guide and migration scripts in this repository, located here: +[Legacy IIS Migration](./Migration-Scripts/Legacy-IIS) --- From 2e3cfe7963c576746aa20bb6f715c675448e03a1 Mon Sep 17 00:00:00 2001 From: Matthew Dobrowsky Date: Thu, 11 Jul 2024 15:17:53 +0000 Subject: [PATCH 26/26] update 2.4.3 changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d71d6f3..c798f14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +2.4.3 +* Adding Legacy IIS Migration scripting and Readme guide + 2.4.2 * Correct false positive error when completing an IIS inventory job. * Revert to specifying the version of PowerShell to use when establishing a local PowerShell Runspace.