Skip to content

Commit 7e15ec8

Browse files
Release23dev (#92)
* SQL Merge
1 parent b661dcf commit 7e15ec8

26 files changed

+1126
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2.3.0
2+
* Added Sql Server Binding Support
3+
14
2.2.2
25
* Removed empty constructor to resolve PAM provider error when using WinCert store types
36

IISU/ClientPSCertStoreManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ function InstallPfxToMachineStore([byte[]]$bytes, [string]$password, [string]$st
104104
{
105105
_logger.LogTrace("ps Has Errors");
106106
var psError = ps.Streams.Error.ReadAll()
107-
.Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message);
107+
.Aggregate(string.Empty, (current, error) => current + error?.ErrorDetails.Message);
108108
{
109109
return new JobResult
110110
{

IISU/ClientPsSqlManager.cs

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
// Copyright 2022 Keyfactor
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Keyfactor.Logging;
16+
using Keyfactor.Orchestrators.Common.Enums;
17+
using Keyfactor.Orchestrators.Extensions;
18+
using Microsoft.Extensions.Logging;
19+
using Microsoft.Management.Infrastructure.Serialization;
20+
using Newtonsoft.Json;
21+
using System;
22+
using System.Linq;
23+
using System.Management.Automation;
24+
using System.Management.Automation.Runspaces;
25+
using System.Security.Cryptography.X509Certificates;
26+
27+
namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore
28+
{
29+
internal class ClientPsSqlManager
30+
{
31+
private string SqlServiceUser { get; set; }
32+
private string SqlInstanceName { get; set; }
33+
private bool RestartService { get; set; }
34+
private string RegistryPath { get; set; }
35+
private string RenewalThumbprint { get; set; } = "";
36+
private string ClientMachineName { get; set; }
37+
private long JobHistoryID { get; set; }
38+
private readonly ILogger _logger;
39+
private readonly Runspace _runSpace;
40+
41+
private PowerShell ps;
42+
43+
public ClientPsSqlManager(ManagementJobConfiguration config, string serverUsername, string serverPassword)
44+
{
45+
_logger = LogHandler.GetClassLogger<ClientPsSqlManager>();
46+
47+
try
48+
{
49+
ClientMachineName = config.CertificateStoreDetails.ClientMachine;
50+
JobHistoryID = config.JobHistoryId;
51+
52+
if (config.JobProperties.ContainsKey("InstanceName"))
53+
{
54+
var instanceRef = config.JobProperties["InstanceName"]?.ToString();
55+
SqlInstanceName = string.IsNullOrEmpty(instanceRef) ? "MSSQLSERVER":instanceRef;
56+
}
57+
58+
// Establish PowerShell Runspace
59+
var jobProperties = JsonConvert.DeserializeObject<JobProperties>(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate });
60+
string winRmProtocol = jobProperties.WinRmProtocol;
61+
string winRmPort = jobProperties.WinRmPort;
62+
bool includePortInSPN = jobProperties.SpnPortFlag;
63+
RestartService = jobProperties.RestartService;
64+
65+
_logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}");
66+
_runSpace = PsHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword);
67+
}
68+
catch (Exception e)
69+
{
70+
throw new Exception($"Error when initiating a SQL Management Job: {e.Message}", e.InnerException);
71+
}
72+
}
73+
74+
public ClientPsSqlManager(InventoryJobConfiguration config,Runspace runSpace)
75+
{
76+
_logger = LogHandler.GetClassLogger<ClientPsSqlManager>();
77+
78+
try
79+
{
80+
ClientMachineName = config.CertificateStoreDetails.ClientMachine;
81+
JobHistoryID = config.JobHistoryId;
82+
83+
// Establish PowerShell Runspace
84+
var jobProperties = JsonConvert.DeserializeObject<JobProperties>(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate });
85+
string winRmProtocol = jobProperties.WinRmProtocol;
86+
string winRmPort = jobProperties.WinRmPort;
87+
bool includePortInSPN = jobProperties.SpnPortFlag;
88+
89+
_logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}");
90+
_runSpace = runSpace;
91+
}
92+
catch (Exception e)
93+
{
94+
throw new Exception($"Error when initiating a SQL Management Job: {e.Message}", e.InnerException);
95+
}
96+
}
97+
98+
public JobResult UnBindCertificate()
99+
{
100+
try
101+
{
102+
_logger.MethodEntry();
103+
104+
_runSpace.Open();
105+
ps = PowerShell.Create();
106+
ps.Runspace = _runSpace;
107+
108+
RegistryPath = GetSqlCertRegistryLocation(SqlInstanceName, ps);
109+
110+
var funcScript = string.Format($"Clear-ItemProperty -Path \"{RegistryPath}\" -Name Certificate");
111+
foreach (var cmd in ps.Commands.Commands)
112+
{
113+
_logger.LogTrace("Logging PowerShell Command");
114+
_logger.LogTrace(cmd.CommandText);
115+
}
116+
117+
_logger.LogTrace($"funcScript {funcScript}");
118+
ps.AddScript(funcScript);
119+
_logger.LogTrace("funcScript added...");
120+
ps.Invoke();
121+
_logger.LogTrace("funcScript Invoked...");
122+
123+
if (ps.HadErrors)
124+
{
125+
var psError = ps.Streams.Error.ReadAll()
126+
.Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message);
127+
{
128+
return new JobResult
129+
{
130+
Result = OrchestratorJobStatusJobResult.Failure,
131+
JobHistoryId = JobHistoryID,
132+
FailureMessage = $"Unable to unbind certificate to Sql Server"
133+
};
134+
}
135+
}
136+
137+
return new JobResult
138+
{
139+
Result = OrchestratorJobStatusJobResult.Success,
140+
JobHistoryId = JobHistoryID,
141+
FailureMessage = ""
142+
};
143+
}
144+
catch (Exception e)
145+
{
146+
return new JobResult
147+
{
148+
Result = OrchestratorJobStatusJobResult.Failure,
149+
JobHistoryId = JobHistoryID,
150+
FailureMessage = $"Error Occurred in unbind {LogHandler.FlattenException(e)}"
151+
};
152+
}
153+
finally
154+
{
155+
_runSpace.Close();
156+
ps.Runspace.Close();
157+
ps.Dispose();
158+
}
159+
}
160+
161+
public string GetSqlInstanceValue(string instanceName,PowerShell ps)
162+
{
163+
try
164+
{
165+
var funcScript = string.Format(@$"Get-ItemPropertyValue ""HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL"" -Name {instanceName}");
166+
foreach (var cmd in ps.Commands.Commands)
167+
{
168+
_logger.LogTrace("Logging PowerShell Command");
169+
_logger.LogTrace(cmd.CommandText);
170+
}
171+
172+
_logger.LogTrace($"funcScript {funcScript}");
173+
ps.AddScript(funcScript);
174+
_logger.LogTrace("funcScript added...");
175+
var SqlInstanceValue = ps.Invoke()[0].ToString();
176+
_logger.LogTrace("funcScript Invoked...");
177+
ps.Commands.Clear();
178+
179+
if (!ps.HadErrors)
180+
{
181+
return SqlInstanceValue;
182+
}
183+
return null;
184+
}
185+
catch (Exception e)
186+
{
187+
throw new Exception($"Error when initiating getting instance name from registry: {e.Message}", e.InnerException);
188+
}
189+
}
190+
191+
public string GetSqlCertRegistryLocation(string instanceName,PowerShell ps)
192+
{
193+
return $"HKLM:\\SOFTWARE\\Microsoft\\Microsoft SQL Server\\{GetSqlInstanceValue(instanceName,ps)}\\MSSQLServer\\SuperSocketNetLib\\";
194+
}
195+
196+
public string GetSqlServerServiceName(string instanceValue)
197+
{
198+
if(string.IsNullOrEmpty(instanceValue))
199+
return string.Empty;
200+
201+
//Default SQL Instance has this format
202+
if (instanceValue.Split('.')[1] == "MSSQLSERVER")
203+
return "MSSQLSERVER";
204+
205+
//Named Instance service has this format
206+
return $"MSSQL`${instanceValue.Split('.')[1]}";
207+
}
208+
209+
public JobResult BindCertificates(string renewalThumbprint, X509Certificate2 x509Cert)
210+
{
211+
try
212+
{
213+
var bindingError = string.Empty;
214+
RenewalThumbprint = renewalThumbprint;
215+
216+
_runSpace.Open();
217+
ps = PowerShell.Create();
218+
ps.Runspace = _runSpace;
219+
if (!string.IsNullOrEmpty(renewalThumbprint))
220+
{
221+
var funcScript = string.Format(@$"(Get-ItemProperty ""HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server"").InstalledInstances");
222+
ps.AddScript(funcScript);
223+
_logger.LogTrace("funcScript added...");
224+
var instances = ps.Invoke();
225+
ps.Commands.Clear();
226+
foreach (var instance in instances)
227+
{
228+
var regLocation = GetSqlCertRegistryLocation(instance.ToString(), ps);
229+
230+
funcScript = string.Format(@$"Get-ItemPropertyValue ""{regLocation}"" -Name Certificate");
231+
ps.AddScript(funcScript);
232+
_logger.LogTrace("funcScript added...");
233+
var thumbprint = ps.Invoke()[0].ToString();
234+
ps.Commands.Clear();
235+
236+
if (RenewalThumbprint.Contains(thumbprint, StringComparison.CurrentCultureIgnoreCase))
237+
{
238+
bindingError=BindCertificate(x509Cert, ps);
239+
}
240+
}
241+
}
242+
else
243+
{
244+
bindingError=BindCertificate(x509Cert, ps);
245+
}
246+
247+
if (bindingError.Length == 0)
248+
{
249+
return new JobResult
250+
{
251+
Result = OrchestratorJobStatusJobResult.Success,
252+
JobHistoryId = JobHistoryID,
253+
FailureMessage = ""
254+
};
255+
}
256+
else
257+
{
258+
return new JobResult
259+
{
260+
Result = OrchestratorJobStatusJobResult.Failure,
261+
JobHistoryId = JobHistoryID,
262+
FailureMessage = bindingError
263+
};
264+
}
265+
266+
}
267+
catch (Exception e)
268+
{
269+
return new JobResult
270+
{
271+
Result = OrchestratorJobStatusJobResult.Failure,
272+
JobHistoryId = JobHistoryID,
273+
FailureMessage = $"Error Occurred in BindCertificates {LogHandler.FlattenException(e)}"
274+
};
275+
}
276+
finally
277+
{
278+
_runSpace.Close();
279+
ps.Runspace.Close();
280+
ps.Dispose();
281+
}
282+
283+
}
284+
public string BindCertificate(X509Certificate2 x509Cert,PowerShell ps)
285+
{
286+
try
287+
{
288+
_logger.MethodEntry();
289+
290+
291+
//If they comma separated the instance entry param, they are trying to install to more than 1 instance
292+
var instances = SqlInstanceName.Split(',');
293+
294+
foreach (var instanceName in instances)
295+
{
296+
RegistryPath = GetSqlCertRegistryLocation(instanceName, ps);
297+
298+
var thumbPrint = string.Empty;
299+
if (x509Cert != null)
300+
thumbPrint = x509Cert.Thumbprint.ToLower(); //sql server config mgr expects lower
301+
302+
var funcScript = string.Format($"Set-ItemProperty -Path \"{RegistryPath}\" -Name Certificate {thumbPrint}");
303+
foreach (var cmd in ps.Commands.Commands)
304+
{
305+
_logger.LogTrace("Logging PowerShell Command");
306+
_logger.LogTrace(cmd.CommandText);
307+
}
308+
309+
_logger.LogTrace($"funcScript {funcScript}");
310+
ps.AddScript(funcScript);
311+
_logger.LogTrace("funcScript added...");
312+
ps.Invoke();
313+
_logger.LogTrace("funcScript Invoked...");
314+
315+
_logger.LogTrace("Setting up Acl Access for Manage Private Keys");
316+
ps.Commands.Clear();
317+
318+
//Get the SqlServer Service User Name
319+
var serviceName = GetSqlServerServiceName(GetSqlInstanceValue(instanceName, ps));
320+
funcScript = @$"(Get-WmiObject Win32_Service -Filter ""Name='{serviceName}'"").StartName";
321+
ps.AddScript(funcScript);
322+
_logger.LogTrace("funcScript added...");
323+
SqlServiceUser = ps.Invoke()[0].ToString();
324+
_logger.LogTrace("funcScript Invoked...");
325+
_logger.LogTrace("Got service login user for ACL Permissions");
326+
ps.Commands.Clear();
327+
328+
funcScript = $@"$thumbprint = '{thumbPrint}'
329+
$Cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {{ $_.Thumbprint -eq $thumbprint }}
330+
$privKey = $Cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
331+
$keyPath = ""$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\""
332+
$privKeyPath = (Get-Item ""$keyPath\$privKey"")
333+
$Acl = Get-Acl $privKeyPath
334+
$Ar = New-Object System.Security.AccessControl.FileSystemAccessRule(""{SqlServiceUser.Replace("$", "`$")}"", ""Read"", ""Allow"")
335+
$Acl.SetAccessRule($Ar)
336+
Set-Acl $privKeyPath.FullName $Acl";
337+
338+
ps.AddScript(funcScript);
339+
ps.Invoke();
340+
_logger.LogTrace("ACL FuncScript Invoked...");
341+
342+
//If user filled in a service name in the store then restart the SQL Server Services
343+
if (RestartService)
344+
{
345+
_logger.LogTrace("Starting to Restart SQL Server Service...");
346+
ps.Commands.Clear();
347+
funcScript = $@"Restart-Service -Name ""{serviceName}"" -Force";
348+
349+
ps.AddScript(funcScript);
350+
ps.Invoke();
351+
_logger.LogTrace("Invoked Restart SQL Server Service....");
352+
}
353+
354+
if (ps.HadErrors)
355+
{
356+
var psError = ps.Streams.Error.ReadAll()
357+
.Aggregate(string.Empty, (current, error) => current + error?.Exception.Message);
358+
{
359+
return psError;
360+
}
361+
}
362+
}
363+
return "";
364+
}
365+
catch (Exception e)
366+
{
367+
return LogHandler.FlattenException(e);
368+
}
369+
370+
}
371+
}
372+
}
373+

0 commit comments

Comments
 (0)