Skip to content

Commit 808415a

Browse files
authored
Merge pull request #5 from Keyfactor/ab#69420
Ab#69420
2 parents f01464c + c5c142a commit 808415a

File tree

5 files changed

+102
-98
lines changed

5 files changed

+102
-98
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
v1.2.0
2+
- Allow for the management (renew/replace) of bound certificates
3+
14
v1.1.0
25
- Modified Fortigate authentication to use Authorization Bearer Token header rather than query string
36
- Logging improvements

Fortigate/FortigateStore.cs

Lines changed: 69 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
using System;
1616
using System.Collections.Generic;
17-
using System.Threading.Tasks;
1817

1918
using Newtonsoft.Json;
2019

@@ -29,11 +28,7 @@
2928
using System.Web;
3029
using System.Text;
3130
using System.Net.Http.Headers;
32-
using Keyfactor.Orchestrators.Extensions;
3331
using Microsoft.Extensions.Logging;
34-
using Keyfactor.Orchestrators.Common.Enums;
35-
using System.Reflection.Metadata;
36-
using System.Linq.Expressions;
3732

3833
namespace Keyfactor.Extensions.Orchestrator.Fortigate
3934
{
@@ -98,27 +93,6 @@ public void Delete(string alias)
9893
}
9994
}
10095

101-
public bool Exists(string alias)
102-
{
103-
logger.MethodEntry(LogLevel.Debug);
104-
105-
try
106-
{
107-
var result = GetResource(get_certificate_api + alias);
108-
var response = JsonConvert.DeserializeObject<FortigateResponse<Certificate[]>>(result);
109-
110-
return (response.results != null && response.results.Length > 0);
111-
}
112-
catch(Exception)
113-
{
114-
return false;
115-
}
116-
finally
117-
{
118-
logger.MethodExit(LogLevel.Debug);
119-
}
120-
}
121-
12296
public void UpdateUsage(string alias, string path, string name, string attribute)
12397
{
12498
logger.MethodEntry(LogLevel.Debug);
@@ -148,15 +122,15 @@ public void UpdateUsage(string alias, string path, string name, string attribute
148122
}
149123
}
150124

151-
public Usage Usage(string alias)
125+
public Usage Usage(string alias, int qtype)
152126
{
153127
logger.MethodEntry(LogLevel.Debug);
154128

155129
var parameters = new Dictionary<String, String>();
156130
parameters.Add("vdom", "root");
157131
parameters.Add("scope", "global");
158132
parameters.Add("mkey", alias);
159-
parameters.Add("qtypes", "[160]");
133+
parameters.Add("qtypes", $"[{qtype.ToString()}]");
160134

161135
try
162136
{
@@ -181,64 +155,66 @@ public void Insert(string alias, string cert, string privateKey, bool overwrite,
181155

182156
try
183157
{
158+
Certificate[] byAlias = List(alias);
159+
184160
if (overwrite)
185161
{
186-
var tmpAlias = alias + "_kftmp";
187-
var existing = Exists(alias);
188-
var tmpExisting = Exists(tmpAlias);
162+
var newAlias = CreateNewAlias(alias);
163+
Certificate[] byNewAlias = List(newAlias);
164+
Usage existingUsage = null;
189165

190166
//if there is an existing record
191-
if (existing)
167+
if (byAlias.Length > 0)
192168
{
169+
Certificate certItem = byAlias[0];
170+
193171
//check to see if it's in use
194-
var existingUsage = Usage(alias);
172+
existingUsage = Usage(alias, certItem.q_type);
195173

196174
//if it's currently in use
197-
if (existingUsage.currently_using.Length > 0)
175+
if (existingUsage != null && existingUsage.currently_using != null && existingUsage.currently_using.Length > 0)
198176
{
199-
//if we don't have a tmp create a temp
200-
if (!tmpExisting)
177+
//if newAlias exists, end with error
178+
if (byNewAlias.Length > 0)
201179
{
202-
//create tmp
203-
Insert(tmpAlias, cert, privateKey);
204-
205-
tmpExisting = true;
180+
throw new Exception($"Error inserting certificate {alias}. New alias {newAlias} already exists, so certificate {alias} that is bound to one or more objects, cannot be replaced and rebound. Please remove {newAlias}, and try again.");
206181
}
207182

183+
//create newAlias entry
184+
logger.LogDebug("Inserting alias:" + newAlias);
185+
Insert(newAlias, cert, privateKey, password);
186+
208187
foreach (var existingUsing in existingUsage.currently_using)
209188
{
210-
UpdateUsage(tmpAlias, existingUsing.path, existingUsing.name, existingUsing.attribute);
189+
logger.LogDebug($"Update binding for path/name/attribute {existingUsing.path}/{existingUsing.name}/{existingUsing.attribute} for new alias {newAlias}");
190+
UpdateUsage(newAlias, existingUsing.path, existingUsing.name, existingUsing.attribute);
211191
}
192+
193+
logger.LogDebug("Deleting alias:" + alias);
194+
Delete(alias);
212195
}
196+
else
197+
{
198+
logger.LogDebug("Deleting alias:" + alias);
199+
Delete(alias);
213200

214-
logger.LogDebug("Deleting alias:" + alias);
215-
Delete(alias);
201+
logger.LogDebug("Inserting alias:" + alias);
202+
Insert(alias, cert, privateKey, password);
203+
}
216204
}
217-
218-
logger.LogDebug("Inserting alias:" + alias);
219-
Insert(alias, cert, privateKey, password);
220-
221-
//if we have an existing temp record
222-
if (tmpExisting)
205+
else
223206
{
224-
//check to see if it has any binds
225-
var tmpUsage = Usage(tmpAlias);
226-
if (tmpUsage.currently_using.Length > 0)
227-
{
228-
//transfer binds back to original
229-
foreach (var tmpUsing in tmpUsage.currently_using)
230-
{
231-
UpdateUsage(alias, tmpUsing.path, tmpUsing.name, tmpUsing.attribute);
232-
}
233-
}
234-
logger.LogDebug("Deleting alias:" + tmpExisting);
235-
Delete(tmpAlias);
207+
logger.LogDebug("Inserting alias:" + alias);
208+
Insert(alias, cert, privateKey, password);
236209
}
237210
}
238211
else
239212
{
213+
if (byAlias.Length > 0)
214+
throw new Exception($"Certificate {alias} already exists, but overwrite is set to false. Try rescheduling job with overwrite set to true if you wish to replace this certificate.");
215+
240216
//no overwrite so we just try to insert
241-
logger.LogDebug("Inserting certificate with alias: " + alias);
217+
logger.LogDebug("Inserting alias: " + alias);
242218
Insert(alias, cert, privateKey, password);
243219
}
244220
}
@@ -284,50 +260,35 @@ private void Insert(string alias, string cert, string privateKey, string passwor
284260
}
285261
}
286262

287-
public List<CurrentInventoryItem> List()
263+
public Certificate[] List(string mkey)
288264
{
289265
logger.MethodEntry(LogLevel.Debug);
290-
291-
List<CurrentInventoryItem> items = new List<CurrentInventoryItem>();
266+
Certificate[] certificates = new List<Certificate>().ToArray();
292267

293268
try
294-
{
295-
var result = GetResource(available_certificates);
296-
var response = JsonConvert.DeserializeObject<FortigateResponse<Certificate[]>>(result);
297-
298-
foreach( var cert in response.results)
299-
{
300-
if (cert.type == "local-cer")
301-
{
302-
var certFile = DownloadFileAsString(cert.name, cert.type);
303-
304-
var item = new CurrentInventoryItem()
305-
{
306-
Alias = cert.name,
307-
Certificates = new string[] { certFile },
308-
ItemStatus = OrchestratorInventoryItemStatus.Unknown,
309-
PrivateKeyEntry = true,
310-
UseChainLevel = false
311-
};
312-
313-
items.Add(item);
314-
}
315-
}
316-
317-
return items;
269+
{
270+
string endpoint = string.IsNullOrEmpty(mkey) ? available_certificates : available_certificates;
271+
Dictionary<String, String> parameters = mkey == null ? null : new Dictionary<string, string> { { "mkey", mkey } };
272+
var result = GetResource(endpoint, parameters);
273+
certificates = JsonConvert.DeserializeObject<FortigateResponse<Certificate[]>>(result).results;
318274
}
319275
catch (Exception ex)
320276
{
321-
logger.LogError(FortigateException.FlattenExceptionMessages(ex, "Error retrieving certificate list: "));
322-
throw;
277+
if (ex.GetType() != typeof(HttpRequestException) || ((HttpRequestException)ex).StatusCode != System.Net.HttpStatusCode.NotFound)
278+
{
279+
logger.LogError(FortigateException.FlattenExceptionMessages(ex, "Error retrieving certificate list: "));
280+
throw;
281+
}
323282
}
324283
finally
325284
{
326285
logger.MethodExit(LogLevel.Debug);
327286
}
287+
288+
return certificates;
328289
}
329290

330-
private string DownloadFileAsString(string mkey, string type)
291+
public string DownloadFileAsString(string mkey, string type)
331292
{
332293
logger.MethodEntry(LogLevel.Debug);
333294

@@ -476,5 +437,21 @@ private string GetResource(string endpoint, Dictionary<String, String> additiona
476437
logger.MethodExit(LogLevel.Debug);
477438
}
478439
}
440+
441+
private string CreateNewAlias(string alias)
442+
{
443+
string suffix = $"--{DateTime.Now.Ticks.ToString("X8")}";
444+
int suffixIdx = alias.IndexOf("--");
445+
string newAlias = suffixIdx == -1 ? alias : alias.Substring(0, suffixIdx);
446+
447+
int aliasLengthOver = alias.Length + suffix.Length - 25;
448+
if (aliasLengthOver > 0)
449+
{
450+
alias = alias.Substring(0, alias.Length - aliasLengthOver);
451+
}
452+
string rtnAlias = alias + suffix;
453+
454+
return rtnAlias;
455+
}
479456
}
480457
}

Fortigate/Inventory.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,25 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd
4444

4545
try
4646
{
47-
inventoryItems = store.List();
47+
Api.Certificate[] certificates = store.List(null);
48+
foreach (var cert in certificates)
49+
{
50+
if (cert.type == "local-cer")
51+
{
52+
var certFile = store.DownloadFileAsString(cert.name, cert.type);
53+
54+
var item = new CurrentInventoryItem()
55+
{
56+
Alias = cert.name,
57+
Certificates = new string[] { certFile },
58+
ItemStatus = OrchestratorInventoryItemStatus.Unknown,
59+
PrivateKeyEntry = true,
60+
UseChainLevel = false
61+
};
62+
63+
inventoryItems.Add(item);
64+
}
65+
}
4866
}
4967
catch (Exception ex)
5068
{

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,25 @@
3434
The Fortigate Orchestrator Extension supports the following use cases:
3535
1. Inventory of local user and factory cerificates
3636
2. Ability to add new local certificates
37-
3. Ability to renew **unbound** local user certificates
37+
3. Ability to replace bound* and unbound local user certificates (usually after renewal in Keyfactor Command)
3838
4. Ability to delete **unbound** local user certificates
3939

4040
The Fortigate Orchestrator Extension DOES NOT support the following use cases:
4141
1. The renewal or removal of certificates enrolled through the internal Fortigate CA
4242
2. The renewal or removal of factory certificates
43-
3. The renewal or removal of ANY certificate bound to a Fortigate object
43+
3. The removal of ANY certificate bound to a Fortigate object
4444
4. Certificate enrollment using the internal Fortigate CA (Keyfactor's "reenrollment" or "on device key generation" use case)
4545

46+
\* Because the Fortigate API does not allow for updating certificates in place, and to avoid temporary outages, when replacing local certificates that are bound, it is necessary to create a new name (alias) for the certificate. The new name is created using the first 8 characters of the previous name (larger names truncated due to Fortigate name length constraints) allong with a suffix comprised of "--" and a 15 character hash of the current date/time. The replaced certificate with the old name is then removed from the Fortigate instance. For example, a bound certificate with the name "CertName" would be replaced and the name would then be "CertName--8DD76A97A98E4C1". The existing bindings would remain in place with the new name. At no point during the management job would any of the bound objects be left without a valid certificate binding.
47+
4648

4749

4850
## Compatibility
4951

5052
This integration is compatible with Keyfactor Universal Orchestrator version 10.1 and later.
5153

5254
## Support
53-
The Fortigate Universal Orchestrator extension is open source and community supported, meaning that there is **no SLA** applicable.
55+
The Fortigate Universal Orchestrator extension is open source and there is **no SLA**. Keyfactor will address issues as resources become available. Keyfactor customers may request escalation by opening up a support ticket through their Keyfactor representative.
5456

5557
> To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab.
5658
@@ -112,6 +114,8 @@ To use the Fortigate Universal Orchestrator extension, you **must** create the F
112114

113115
![Fortigate Advanced Tab](docsource/images/Fortigate-advanced-store-type-dialog.png)
114116

117+
> For Keyfactor **Command versions 24.4 and later**, a Certificate Format dropdown is available with PFX and PEM options. Ensure that **PFX** is selected, as this determines the format of new and renewed certificates sent to the Orchestrator during a Management job. Currently, all Keyfactor-supported Orchestrator extensions support only PFX.
118+
115119
#### Custom Fields Tab
116120
Custom fields operate at the certificate store level and are used to control how the orchestrator connects to the remote target server containing the certificate store to be managed. The following custom fields should be added to the store type:
117121

docsource/content.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
The Fortigate Orchestrator Extension supports the following use cases:
44
1. Inventory of local user and factory cerificates
55
2. Ability to add new local certificates
6-
3. Ability to renew **unbound** local user certificates
6+
3. Ability to replace bound* and unbound local user certificates (usually after renewal in Keyfactor Command)
77
4. Ability to delete **unbound** local user certificates
88

99
The Fortigate Orchestrator Extension DOES NOT support the following use cases:
1010
1. The renewal or removal of certificates enrolled through the internal Fortigate CA
1111
2. The renewal or removal of factory certificates
12-
3. The renewal or removal of ANY certificate bound to a Fortigate object
12+
3. The removal of ANY certificate bound to a Fortigate object
1313
4. Certificate enrollment using the internal Fortigate CA (Keyfactor's "reenrollment" or "on device key generation" use case)
1414

15+
\* Because the Fortigate API does not allow for updating certificates in place, and to avoid temporary outages, when replacing local certificates that are bound, it is necessary to create a new name (alias) for the certificate. The new name is created using the first 8 characters of the previous name (larger names truncated due to Fortigate name length constraints) allong with a suffix comprised of "--" and a 15 character hash of the current date/time. The replaced certificate with the old name is then removed from the Fortigate instance. For example, a bound certificate with the name "CertName" would be replaced and the name would then be "CertName--8DD76A97A98E4C1". The existing bindings would remain in place with the new name. At no point during the management job would any of the bound objects be left without a valid certificate binding.
16+
1517

1618
## Requirements
1719

0 commit comments

Comments
 (0)