From 293f748d51a38918568820dd8cd80b119d55e36f Mon Sep 17 00:00:00 2001 From: Macey Dobrowsky Date: Tue, 11 Mar 2025 16:53:04 +0000 Subject: [PATCH 01/18] remove powershell handler and point to workflow repo --- .../AkamaiExpirationHandler.ps1 | 347 ------------------ readme_source.md | 19 +- 2 files changed, 4 insertions(+), 362 deletions(-) delete mode 100644 akamai-cps-orchestrator/AkamaiExpirationHandler.ps1 diff --git a/akamai-cps-orchestrator/AkamaiExpirationHandler.ps1 b/akamai-cps-orchestrator/AkamaiExpirationHandler.ps1 deleted file mode 100644 index 363f3ff..0000000 --- a/akamai-cps-orchestrator/AkamaiExpirationHandler.ps1 +++ /dev/null @@ -1,347 +0,0 @@ -# // Copyright 2023 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. - -[hashtable]$context -$Thumb = $context["Thumbprint"] -$CA = $context["CAConfiguration"] -$Template = $context["Template"] - -# script variables -############################################################# -$apiUrl = "https://my.keyfactor.instance01/KeyfactorAPI" # update to be Keyfactor API endpoint -$LogDest = "C:\Keyfactor\logs\AkamaiExpirationHandler.log" # the location for the error log file, the script will create this file -############################################################# - -Function LogWrite($LogString) -{ - Add-Content $LogDest -value $LogString -} - -Function GetIdAndSans -{ - try - { - $searchURL = $apiUrl + "/certificates/?verbose=1&maxResults=50&page=1&query=Thumbprint%20-eq%20`""+$Thumb+"`"" - - $certificateResponse = Invoke-RestMethod ` - -Method Get ` - -Uri $searchUrl ` - -UseDefaultCredentials ` - -ContentType "application/json" - - $certValues = @{} - - $certValues.Id = $certificateResponse.Id - $certValues.Sans = "" - - foreach($san in $certificateResponse.SubjectAltNameElements) - { - if ($san.Type -eq 2) #include only dns sans (type 2) - { - if ($cert.Sans) - { - $certValues.Sans = $certValues.Sans + "&" # add ampersand delimiter for additional DNS SANs - } - $certValues.Sans = $certValues.Sans + $san.Value - } - } - - return $certValues - } - catch - { - LogWrite "An error occurred looking up the certificate in Keyfactor" - LogWrite $_ - return "SEARCH_ERROR" - } -} - -Function GetLocations($certId) -{ - try - { - $locationsURL = $apiUrl + "/certificates/locations/" + $certId - $locationsResponse = Invoke-RestMethod ` - -Method Get ` - -Uri $locationsURL ` - -UseDefaultCredentials ` - -ContentType "application/json" - - foreach($storetype in $locationsResponse.Details) - { - if ($storetype.StoreType -match "Akamai") - { - return $storetype.Locations # array of all akamai locations this cert is in - } - } - } - catch - { - LogWrite "An error occurred looking up the certificate locations in Keyfactor" - LogWrite $_ - return "LOCATIONS_ERROR" - } -} - -Function GetOrchestratorId($storeId) -{ - try - { - $storeURL = $apiUrl + "/certificatestores/" + $storeId - $storeResponse = Invoke-RestMethod ` - -Method Get ` - -Uri $storeURL ` - -UseDefaultCredentials ` - -ContentType "application/json" - - return $storeResponse.AgentId - } - catch - { - LogWrite "An error occurred while retrieving the Orchestrator ID of the store with id: " + $storeId - LogWrite $_ - return "GET_ORCHESTRATORID_ERROR" - } -} - -Function GetStoreInventory($storeId) -{ - try - { - $inventoryURL = $apiUrl + "/certificatestores/" + $storeId + "/inventory" - $inventoryResponse = Invoke-RestMethod ` - -Method Get ` - -Uri $inventoryURL ` - -UseDefaultCredentials ` - -ContentType "application/json" - - return $inventoryResponse # - } - catch - { - LogWrite "An error occurred while retrieving the inventory of certs in the store with id: " + $storeId - LogWrite $_ - return "GET_INVENTORY_ERROR" - } -} - -Function FindInventoryParameters($inventoryList) -{ - try - { - $parsedResults = @{} - # search through the store inventory list for matching cert - foreach($inventoryItem in $inventoryList) - { - if ($inventoryItem.Name -eq $Thumb) - { - # get cert subject - foreach ($cert in $inventoryItem.Certificates) - { - if ($cert.Id -eq $CertId) - { - $parsedResults.Subject = $cert.IssuedDN - } - } - $parsedResults.Parameters = $inventoryItem.Parameters - return $parsedResults - } - } - } - catch - { - LogWrite "An error occurred while parsing the job parameters from the cert store inventory" - LogWrite $_ - return "PARSE_INVENTORY_ERROR" - } -} - -Function FilterSubjectForAkamai($certSubject) -{ - # Subject fields allowed by Akamai: - # CN, C, L, O, OU, ST - $subjectElements = $certSubject.split(',') - - $parsedSubject = "" - - $noCN? = $true - $noC? = $true - $noL? = $true - $noO? = $true - $noOU? = $true - $noST? = $true - foreach($ele in $subjectElements) - { - if ($noCN?) - { - if ($ele -match 'CN=') - { - $parsedSubject += $ele + ',' - $noCN? = $false - } - } - if ($noC?) - { - if ($ele -match 'C=') - { - $parsedSubject += $ele + ',' - $noC? = $false - } - } - if ($noL?) - { - if ($ele -match 'L=') - { - $parsedSubject += $ele + ',' - $noL? = $false - } - } - if ($noO?) - { - if ($ele -match 'O=') - { - $parsedSubject += $ele + ',' - $noO? = $false - } - } - if ($noOU?) - { - if ($ele -match 'OU=') - { - $parsedSubject += $ele + ',' - $noOU? = $false - } - } - if ($noST?) - { - if ($ele -match 'ST=') - { - $parsedSubject += $ele + ',' - $noST? = $false - } - } - } - - # trim comma at end - $i = $parsedSubject.LastIndexOf(',') - return $parsedSubject.Substring(0, $i) -} - -Function ScheduleReenrollment($storeId, $orchId, $inventoryList, $sans) -{ - try - { - # parse inventory parameters for reenrollment, and get subject - $reenrollmentParameters = FindInventoryParameters($inventoryList) - LogWrite "Parsed Subject: " - $Subject = $reenrollmentParameters.Subject - LogWrite $Subject - LogWrite "Subject filtered for Akamai Reenrollment: " - $FilteredSubject = FilterSubjectForAkamai($Subject) - LogWrite $FilteredSubject - LogWrite "Parsed Inventory Parameters" - $Parameters = $reenrollmentParameters.Parameters - - # add sans, which are not returned on inventory - $Parameters.Sans = $sans - LogWrite $Parameters - - # escape backslashes in CA name - $escapedCA = $CA -replace '\\', '\\' - - # convert Parameters object to Json - $paramsJSON = $Parameters | ConvertTo-Json - - # get Orchestrator Agent Id for scheduling reenrollment - - $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" - $headers.Add('content-type', 'application/json') - $headers.Add("X-Keyfactor-Requested-With", "APIClient") - $body = @" -{ - "KeystoreId": "$storeId", - "SubjectName": "$FilteredSubject", - "AgentGuid": "$orchId", - "Alias": "$Thumb", - "JobProperties": $paramsJson, - "CertificateAuthority": "$escapedCA", - "CertificateTemplate": "$Template" -} -"@ - LogWrite $body - $reenrollmentURL = $apiUrl + "/certificatestores/reenrollment" - $reenrollmentResponse = Invoke-RestMethod ` - -Method Post ` - -Uri $reenrollmentURL ` - -Headers $headers ` - -UseDefaultCredentials ` - -Body $body ` - -ContentType "application/json" - - } - catch - { - LogWrite "An error occurred while scheduling the reenrollment job" - LogWrite $_ - return "REENROLLMENT_ERROR" - } -} - -try -{ - LogWrite (Get-Date).ToUniversalTime().ToString() - LogWrite $Thumb - LogWrite $CA - LogWrite $Template - LogWrite $CertLocations - LogWrite $context -} -catch -{ - LogWrite "An error occurred reading info into the logs" - LogWrite $_ - return "INFO_ERROR" -} - -try -{ - # get CertID and Sans - $CertValues = GetIdAndSans - $CertId = $CertValues.Id - $CertSans = $CertValues.Sans - - LogWrite $CertId - LogWrite $CertSans - - - # get cert locations - $Locations = GetLocations($CertId) - LogWrite "Retrieved Locations" - - # process for each store location this cert is in - foreach($Location in $Locations) - { - $StoreId = $Location.StoreId - - # get Orchestrator Id for store - $OrchestratorId = GetOrchestratorId $StoreId - - # get inventory of location - $InventoryList = GetStoreInventory $StoreId - LogWrite "Got Store Inventory Parameters" - LogWrite $InventoryList - - # schedule reenrollment with parameters - ScheduleReenrollment $StoreId $OrchestratorId $InventoryList $CertSans - LogWrite "Scheduled Reenrollment" - } -} -catch -{ - LogWrite (Get-Date).ToUniversalTime().ToString() - LogWrite "Script Failed Gracefully" -} \ No newline at end of file diff --git a/readme_source.md b/readme_source.md index 9bca043..c60283e 100644 --- a/readme_source.md +++ b/readme_source.md @@ -71,23 +71,12 @@ Change any default values as needed, and enter an Enrollment ID if an existing e The SAN entry needs to be filled out with the DNS value you are using for the certificate's CN. If there are multiple DNS SANs, they should be separted with an ampersand. Example: `www.example01.com&www.example02.com` -**6. (Optional) Configure Renewal of Certificates using Expiration Alert Handler** +**6. (Optional) Configure Renewal of Certificates using a Workflow** Akamai does not support traditional certificate Renewal or one-click Renewal done in the Keyfactor Command platform. The Renewal process creates Certificates with outside keys which are not allowed to be imported into Akamai CPS. As a result, the Reenrollment Job must be used in order to renew existing certificates that reside on the Akamai system. Reenrollment is required as opposed to the Renewal process as it allows Akamai to generate the keys on their platform, which are used to create a certificate in Keyfactor. -Renewing existing certificates in Akamai means running a Reenrollment Job with the same Enrollment ID that was used for an existing Certificate Enrollment. This can be done manually through the Reenrollment process, but an automated process can also be configured using a Keyfactor Expiration Alert Handler. +Renewing existing certificates in Akamai means running a Reenrollment Job with the same Enrollment ID that was used for an existing Certificate Enrollment. This can be done manually through the Reenrollment prompt, but an automated process can also be configured using a Keyfactor Workflow. -The Expiration Alert Handler should be configured to target a Keyfactor Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to `Production` or `Staging`. +The Workflow should be configured to target a Keyfactor Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to `Production` or `Staging`. -With the Expiration Alert Handler using the correct Collection, the Alert should be set to use the `ExpirationPowershell` Handler. A [sample Powershell Handler script](./akamai-cps-orchestrator/AkamaiExpirationHandler.ps1) is included in this repository. The sample script needs to be updated with the correct URL for API requests, and may need other changes as well, as it assumes that Default Credentials (Windows Auth) can be used to authenticate API requests to the Keyfactor instance. __This script needs to be placed in the Keyfactor Command installation's configured Extension Handler Path (default: {installation_dir}\ExtensionLibrary) location so that it can be run.__ - -The `ExpirationPowershell` Event Handler configuration should be configured with the following values: - -| Parameter Name | Type | Value | -| - | - | - | -| Thumbprint | Special Text | Thumbprint | -| Template | Renewal Template | `desired renewal template` | -| CAConfiguration | Renewal Certificate Authority | `desired renewal CA` | -| ScriptName | PowerShell Script Name | AkamaiExpirationHandler.ps1 | - -When running the sample script, it will assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. +A sample workflow for ODKG / Reenrollment scheduling for renewals can be viewed in the [kf-workflow-samples repo](https://github.com/Keyfactor/kf-workflow-samples). When running the sample workflow, it will assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. From 19a8cb1e2f17f98f1554344606fc2f56556bb119 Mon Sep 17 00:00:00 2001 From: Macey Dobrowsky <11599974+doebrowsk@users.noreply.github.com> Date: Tue, 11 Mar 2025 12:56:01 -0400 Subject: [PATCH 02/18] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37d52b..6ee1586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +1.1.1 +- Point to kf-workflow-samples repo for setting up "renewals" for ODKG certificates +- Remove Powershell script sample previously provided for Expiration Alert Handler renewal process + 1.1.0 - Update Expiration Alert Handler sample to filter Subject Elements for duplicates / invalid fields and set the subject to one that will be accepted and matched in the CSR from Akamai - Additional error handling around renewing / updating existing Akamai Enrollments From 80d95c5c9deb49bf5a4dc31ee541ced50683c78f Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Tue, 13 May 2025 14:01:58 -0400 Subject: [PATCH 03/18] chore(test): Add a test console for testing Akamai CPS API calls. Add unit tests for extracting retry delay from CPS response headers. --- .gitignore | 3 + TestConsole/Program.cs | 120 ++++++++++++++++++ TestConsole/TestConsole.csproj | 19 +++ akamai-cps-orchestrator.sln | 17 +++ http-interface.Tests/HttpUtilitiesTests.cs | 72 +++++++++++ .../http-interface.Tests.csproj | 29 +++++ http-interface/HttpUtilities.cs | 28 ++++ 7 files changed, 288 insertions(+) create mode 100644 TestConsole/Program.cs create mode 100644 TestConsole/TestConsole.csproj create mode 100644 http-interface.Tests/HttpUtilitiesTests.cs create mode 100644 http-interface.Tests/http-interface.Tests.csproj create mode 100644 http-interface/HttpUtilities.cs diff --git a/.gitignore b/.gitignore index 9491a2f..c75737e 100644 --- a/.gitignore +++ b/.gitignore @@ -356,6 +356,9 @@ healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ +# JetBrains IDEA configurations +.idea/ + # Ionide (cross platform F# VS Code tools) working folder .ionide/ diff --git a/TestConsole/Program.cs b/TestConsole/Program.cs new file mode 100644 index 0000000..e84d208 --- /dev/null +++ b/TestConsole/Program.cs @@ -0,0 +1,120 @@ +using System.Diagnostics; +using Keyfactor.Orchestrator.Extensions.AkamaiCpsOrchestrator.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace TestConsole; + +class Program +{ + private static ILogger _logger; + + public static void Main(string[] args) + { + string clientSecret = GetEnvironmentVariable("AKAMAI_CLIENT_SECRET"); + string clientToken = GetEnvironmentVariable("AKAMAI_CLIENT_TOKEN"); + string accessToken = GetEnvironmentVariable("AKAMAI_ACCESS_TOKEN"); + + string clientMachine = GetEnvironmentVariable("AKAMAI_HOST"); + + Dictionary storeProps = new() + { + { "client_secret", clientSecret }, + { "client_token", clientToken }, + { "access_token", accessToken } + }; + + using var loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddConsole() + .SetMinimumLevel(LogLevel.Debug); // Set minimum log level here + }); + + _logger = loggerFactory.CreateLogger(); + + AkamaiAuth auth = new AkamaiAuth(storeProps); + AkamaiClient client = new AkamaiClient(_logger, clientMachine, auth); + client.SetDeploymentType(Constants.StorePaths.Staging); + + // StressTestGetEnrollments(client); + + var enrollments = GetEnrollments(client); + var enrollment = enrollments.FirstOrDefault(); + + if (enrollment == null) + { + _logger.LogWarning("No enrollments found in system. Unable to perform certificate / enrollment actions"); + return; + } + + GetEnrollment(client, enrollment.id); + GetCertificate(client, enrollment.id); + GetEnrollmentChangeHistory(client, enrollment.id); + UpdateEnrollment(client, enrollment.id, enrollment); + + _logger.LogInformation("API test suite completed successfully."); + } + + private static void StressTestGetEnrollments(AkamaiClient client) + { + int maxRequests = 51; + // https://techdocs.akamai.com/cps/reference/rate-limiting + // According to the docs here, we should be rate limited 50 requests per minute + // or 20 requests per 2 seconds. + for (int i = 1; i <= maxRequests; i++) + { + var stopwatch = Stopwatch.StartNew(); + _logger.LogInformation($"Sending request {i} of {maxRequests}"); + client.GetEnrollments(); + stopwatch.Stop(); + _logger.LogInformation($"Completed request {i} of {maxRequests}. Time elapsed (ms): {stopwatch.ElapsedMilliseconds}"); + } + } + + private static Enrollment[] GetEnrollments(AkamaiClient client) + { + _logger.LogInformation($"Getting enrollments..."); + var result = client.GetEnrollments(); + _logger.LogInformation($"Enrollments retrieved successfully. Count: {result.Length}"); + return result; + } + + private static Enrollment GetEnrollment(AkamaiClient client, string id) + { + _logger.LogInformation($"Getting enrollment {id}..."); + var enrollment = client.GetEnrollment(id); + _logger.LogInformation($"Enrollment {id} retrieved successfully."); + return enrollment; + } + + private static void GetCertificate(AkamaiClient client, string enrollmentId) + { + _logger.LogInformation($"Getting certificate with enrollment ID {enrollmentId}..."); + var cert = client.GetCertificate(enrollmentId); + _logger.LogInformation($"Certificate with enrollment ID {enrollmentId} retrieved successfully."); + } + + private static void GetEnrollmentChangeHistory(AkamaiClient client, string enrollmentId) + { + _logger.LogInformation($"Getting enrollment change history for enrollment ID {enrollmentId}..."); + var history = client.GetEnrollmentChangeHistory(enrollmentId); + _logger.LogInformation($"Enrollment change history for enrollment ID {enrollmentId} retrieved successfully. Change history count: {history.changes.Length}"); + } + + private static void UpdateEnrollment(AkamaiClient client, string enrollmentId, Enrollment enrollment) + { + _logger.LogInformation($"Updating enrollment {enrollmentId}..."); + client.UpdateEnrollment(enrollmentId, enrollment); + _logger.LogInformation($"Enrollment {enrollmentId} updated successfully."); + } + + private static string GetEnvironmentVariable(string env) + { + string? value = Environment.GetEnvironmentVariable(env); + + if (!string.IsNullOrWhiteSpace(value)) return value; + + throw new ArgumentException($"Environment variable {env} is required for test console"); + } +} diff --git a/TestConsole/TestConsole.csproj b/TestConsole/TestConsole.csproj new file mode 100644 index 0000000..a914d38 --- /dev/null +++ b/TestConsole/TestConsole.csproj @@ -0,0 +1,19 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + + diff --git a/akamai-cps-orchestrator.sln b/akamai-cps-orchestrator.sln index ed1f9cf..4c4b70a 100644 --- a/akamai-cps-orchestrator.sln +++ b/akamai-cps-orchestrator.sln @@ -7,6 +7,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "akamai-cps-orchestrator", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "http-interface", "http-interface\http-interface.csproj", "{C43887FD-287A-4A69-9777-F7895CCFE44F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsole", "TestConsole\TestConsole.csproj", "{AA406C8E-2ACE-43B2-BF03-1B7659B7D082}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "http-interface.Tests", "http-interface.Tests\http-interface.Tests.csproj", "{9E9E9B40-868F-4D6B-9D79-FD668B6F7199}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D55E4F97-06FB-45DE-B041-0DE83EEC87E1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +27,14 @@ Global {C43887FD-287A-4A69-9777-F7895CCFE44F}.Debug|Any CPU.Build.0 = Debug|Any CPU {C43887FD-287A-4A69-9777-F7895CCFE44F}.Release|Any CPU.ActiveCfg = Release|Any CPU {C43887FD-287A-4A69-9777-F7895CCFE44F}.Release|Any CPU.Build.0 = Release|Any CPU + {AA406C8E-2ACE-43B2-BF03-1B7659B7D082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA406C8E-2ACE-43B2-BF03-1B7659B7D082}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA406C8E-2ACE-43B2-BF03-1B7659B7D082}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA406C8E-2ACE-43B2-BF03-1B7659B7D082}.Release|Any CPU.Build.0 = Release|Any CPU + {9E9E9B40-868F-4D6B-9D79-FD668B6F7199}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E9E9B40-868F-4D6B-9D79-FD668B6F7199}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E9E9B40-868F-4D6B-9D79-FD668B6F7199}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E9E9B40-868F-4D6B-9D79-FD668B6F7199}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -28,4 +42,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C851E932-4FDA-4BCF-A048-5F89DDD14106} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {9E9E9B40-868F-4D6B-9D79-FD668B6F7199} = {D55E4F97-06FB-45DE-B041-0DE83EEC87E1} + EndGlobalSection EndGlobal diff --git a/http-interface.Tests/HttpUtilitiesTests.cs b/http-interface.Tests/HttpUtilitiesTests.cs new file mode 100644 index 0000000..72ddc65 --- /dev/null +++ b/http-interface.Tests/HttpUtilitiesTests.cs @@ -0,0 +1,72 @@ +using Keyfactor.Extensions.Utilities.HttpInterface; +using Xunit; + +namespace http_interface.Tests; + +public class HttpUtilitiesTests +{ + [Fact] + public void GetRetryDate_WhenHeaderIncludesRetryDate_DateInFuture_ReturnsRetryDelayFromHeader() + { + var response = new HttpResponseMessage(); + response.Headers.Add("X-RateLimit-Next", "2025-05-13T14:28:19.4732Z"); + + var mockNow = DateTimeOffset.Parse("2025-05-13T14:28:17.1234Z"); + + // A 1-second buffer is added onto the retry timestamp + var expected = TimeSpan.FromSeconds(3.3498); + + var result = HttpUtilities.GetRetryAfterDelay(response, mockNow); + Assert.Equal(expected, result); + } + + [Fact] + public void GetRetryDate_WhenHeaderIncludesRetryDate_DateInPast_ReturnsRetryDelayOfOneSecond() + { + var response = new HttpResponseMessage(); + response.Headers.Add("X-RateLimit-Next", "2025-05-13T14:28:14.4732Z"); + + var mockNow = DateTimeOffset.Parse("2025-05-13T14:28:17.1234Z"); + var expected = TimeSpan.FromSeconds(1); + + var result = HttpUtilities.GetRetryAfterDelay(response, mockNow); + Assert.Equal(expected, result); + } + + [Fact] + public void GetRetryDate_WhenHeaderIncludesRetryDate_DateInvalid_ReturnsDefaultRetryDelay() + { + var response = new HttpResponseMessage(); + response.Headers.Add("X-RateLimit-Next", "invalid"); + + var mockNow = DateTimeOffset.Parse("2025-05-13T14:28:17.1234Z"); + var expected = TimeSpan.FromSeconds(60); + + var result = HttpUtilities.GetRetryAfterDelay(response, mockNow); + Assert.Equal(expected, result); + } + + [Fact] + public void GetRetryDate_WhenHeaderDoesNotIncludeRetryDate_ReturnsDefaultOffsetFromCurrentDate() + { + var response = new HttpResponseMessage(); + + var mockNow = DateTimeOffset.Parse("2025-05-13T14:28:17.1234Z"); + var expected = TimeSpan.FromSeconds(60); + + var result = HttpUtilities.GetRetryAfterDelay(response, mockNow); + Assert.Equal(expected, result); + } + + [Fact] + public void GetRetryDate_WhenHeaderDoesNotIncludeRetryDate_DefaultIsOverridden_ReturnsProvidedOffsetFromCurrentDate() + { + var response = new HttpResponseMessage(); + + var mockNow = DateTimeOffset.Parse("2025-05-13T14:28:17.1234Z"); + var expected = TimeSpan.FromSeconds(15); + + var result = HttpUtilities.GetRetryAfterDelay(response, mockNow, 15); + Assert.Equal(expected, result); + } +} diff --git a/http-interface.Tests/http-interface.Tests.csproj b/http-interface.Tests/http-interface.Tests.csproj new file mode 100644 index 0000000..0324358 --- /dev/null +++ b/http-interface.Tests/http-interface.Tests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + http_interface.Tests + enable + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/http-interface/HttpUtilities.cs b/http-interface/HttpUtilities.cs new file mode 100644 index 0000000..0a134c7 --- /dev/null +++ b/http-interface/HttpUtilities.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; +using System.Net.Http; + +namespace Keyfactor.Extensions.Utilities.HttpInterface +{ + public static class HttpUtilities + { + public static TimeSpan GetRetryAfterDelay(HttpResponseMessage response, DateTimeOffset now, int defaultTimeoutSeconds = 60) + { + var defaultDelay = TimeSpan.FromSeconds(defaultTimeoutSeconds); + if (!response.Headers.Contains("X-RateLimit-Next")) + { + return defaultDelay; + } + + var nextHeader = response.Headers.First(p => p.Key == "X-RateLimit-Next"); + if (DateTimeOffset.TryParse(nextHeader.Value.FirstOrDefault(), out var retryUtc)) + { + // Add a 1-second buffer to the retry to avoid sending requests before server is ready. + var delay = (retryUtc - now) + TimeSpan.FromSeconds(1); + return delay > TimeSpan.Zero ? delay : TimeSpan.FromSeconds(1); + } + + return defaultDelay; + } + } +} From 873d14c52e4c77ee529c2c911abe9b7ab6820242 Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Tue, 13 May 2025 16:46:17 -0400 Subject: [PATCH 04/18] feat(http): Add HTTP retries for failed requests --- TestConsole/Program.cs | 8 +- akamai-cps-orchestrator/Jobs/Inventory.cs | 1 + .../Models/AkamaiClient.cs | 140 ++++++++--------- http-interface.Tests/HttpUtilitiesTests.cs | 40 +++++ .../Models => http-interface}/AkamaiAuth.cs | 4 +- http-interface/HttpInterface.cs | 145 +++++++++++------- http-interface/HttpService.cs | 136 ++++++++++++++++ http-interface/HttpUtilities.cs | 57 ++++++- http-interface/http-interface.csproj | 1 + 9 files changed, 393 insertions(+), 139 deletions(-) rename {akamai-cps-orchestrator/Models => http-interface}/AkamaiAuth.cs (97%) create mode 100644 http-interface/HttpService.cs diff --git a/TestConsole/Program.cs b/TestConsole/Program.cs index e84d208..7e4abbe 100644 --- a/TestConsole/Program.cs +++ b/TestConsole/Program.cs @@ -1,7 +1,7 @@ using System.Diagnostics; +using Keyfactor.Extensions.Utilities.HttpInterface; using Keyfactor.Orchestrator.Extensions.AkamaiCpsOrchestrator.Models; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; namespace TestConsole; @@ -41,7 +41,7 @@ public static void Main(string[] args) var enrollments = GetEnrollments(client); var enrollment = enrollments.FirstOrDefault(); - + // if (enrollment == null) { _logger.LogWarning("No enrollments found in system. Unable to perform certificate / enrollment actions"); @@ -50,7 +50,7 @@ public static void Main(string[] args) GetEnrollment(client, enrollment.id); GetCertificate(client, enrollment.id); - GetEnrollmentChangeHistory(client, enrollment.id); + // GetEnrollmentChangeHistory(client, enrollment.id); UpdateEnrollment(client, enrollment.id, enrollment); _logger.LogInformation("API test suite completed successfully."); @@ -65,7 +65,7 @@ private static void StressTestGetEnrollments(AkamaiClient client) for (int i = 1; i <= maxRequests; i++) { var stopwatch = Stopwatch.StartNew(); - _logger.LogInformation($"Sending request {i} of {maxRequests}"); + _logger.LogInformation($"Sending request {i} of {maxRequests}..."); client.GetEnrollments(); stopwatch.Stop(); _logger.LogInformation($"Completed request {i} of {maxRequests}. Time elapsed (ms): {stopwatch.ElapsedMilliseconds}"); diff --git a/akamai-cps-orchestrator/Jobs/Inventory.cs b/akamai-cps-orchestrator/Jobs/Inventory.cs index cd3ee7b..9a2b102 100644 --- a/akamai-cps-orchestrator/Jobs/Inventory.cs +++ b/akamai-cps-orchestrator/Jobs/Inventory.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; using System.Text; +using Keyfactor.Extensions.Utilities.HttpInterface; using Microsoft.Extensions.Logging; namespace Keyfactor.Orchestrator.Extensions.AkamaiCpsOrchestrator.Jobs diff --git a/akamai-cps-orchestrator/Models/AkamaiClient.cs b/akamai-cps-orchestrator/Models/AkamaiClient.cs index ac51ac6..f718576 100644 --- a/akamai-cps-orchestrator/Models/AkamaiClient.cs +++ b/akamai-cps-orchestrator/Models/AkamaiClient.cs @@ -13,10 +13,7 @@ // limitations under the License. using System; -using System.Collections.Generic; using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; using Keyfactor.Extensions.Utilities.HttpInterface; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -28,7 +25,7 @@ public class AkamaiClient private ILogger _logger; private HttpInterface _http; private AkamaiAuth _auth; - private JsonSerializerSettings _serializerSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }; + public string Username; public string ApiKey; // accountSwitchKey @@ -42,7 +39,7 @@ public AkamaiClient(ILogger logger, string clientMachine, AkamaiAuth auth) Hostname = clientMachine; _auth = auth; - _http = new HttpInterface(_logger, Hostname, useSSL: true); + _http = new HttpInterface(_logger, _auth, Hostname, useSSL: true); } public void SetDeploymentType(string storePath) @@ -66,13 +63,12 @@ public CertificateInfo GetCertificate(string enrollmentId) var path = string.Format(Constants.Endpoints.Deployments, enrollmentId); var acceptHeader = "application/vnd.akamai.cps.deployments.v7+json"; - _http.SetRequestHeaders(new Dictionary() + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("GET", Hostname, path)); + Accept = acceptHeader, + }; - Deployment deployment = _http.Get(path); + Deployment deployment = _http.Get(config, path); // deployments are returned for in process enrollments, so null coalesce to filter for fully deployed certs if (IsProduction) @@ -91,14 +87,13 @@ public Enrollment[] GetEnrollments() { var path = Constants.Endpoints.Enrollments; var acceptHeader = "application/vnd.akamai.cps.enrollments.v11+json"; - - _http.SetRequestHeaders(new Dictionary() + + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("GET", Hostname, path)); + Accept = acceptHeader, + }; - Enrollments enrollmentList = _http.Get(path); + Enrollments enrollmentList = _http.Get(config, path); return enrollmentList.enrollments; } @@ -106,28 +101,26 @@ public Enrollment GetEnrollment(string enrollmentId) { var path = $"{Constants.Endpoints.Enrollments}/{enrollmentId}"; var acceptHeader = "application/vnd.akamai.cps.enrollment.v11+json"; - - _http.SetRequestHeaders(new Dictionary() + + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("GET", Hostname, path)); + Accept = acceptHeader, + }; - return _http.Get(path); + return _http.Get(config, path); } public ChangeHistory GetEnrollmentChangeHistory(string enrollmentId) { var path = $"{Constants.Endpoints.Enrollments}/{enrollmentId}/history/changes"; var acceptHeader = "application/vnd.akamai.cps.change-history.v5+json"; - - _http.SetRequestHeaders(new Dictionary() + + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("GET", Hostname, path)); + Accept = acceptHeader, + }; - return _http.Get(path); + return _http.Get(config, path); } public CreatedEnrollment CreateEnrollment(Enrollment newEnrollment, string contractId) @@ -136,37 +129,31 @@ public CreatedEnrollment CreateEnrollment(Enrollment newEnrollment, string contr newEnrollment.changeManagement = !IsProduction; var path = $"{Constants.Endpoints.Enrollments}?contractId={contractId}"; - var body = JsonConvert.SerializeObject(newEnrollment, _serializerSettings); - var requestContent = new StringContent(body); var acceptHeader = "application/vnd.akamai.cps.enrollment-status.v1+json"; var contentHeader = "application/vnd.akamai.cps.enrollment.v11+json"; - - _http.SetRequestHeaders(new Dictionary() + + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("POST", Hostname, path, body)); - requestContent.Headers.ContentType = new MediaTypeHeaderValue(contentHeader); + Accept = acceptHeader, + ContentType = contentHeader, + }; - return _http.Post(path, requestContent); + return _http.Post(config, path, newEnrollment); } public CreatedEnrollment UpdateEnrollment(string enrollmentId, Enrollment enrollment) { var path = $"{Constants.Endpoints.Enrollments}/{enrollmentId}?force-renewal=true&allow-cancel-pending-changes=true"; //&allow-staging-bypass={IsProduction.ToString().ToLower()}"; - var body = JsonConvert.SerializeObject(enrollment, _serializerSettings); - var requestContent = new StringContent(body); var acceptHeader = "application/vnd.akamai.cps.enrollment-status.v1+json"; var contentHeader = "application/vnd.akamai.cps.enrollment.v11+json"; - - _http.SetRequestHeaders(new Dictionary() + + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("PUT", Hostname, path)); - requestContent.Headers.ContentType = new MediaTypeHeaderValue(contentHeader); + Accept = acceptHeader, + ContentType = contentHeader, + }; - return _http.Put(path, requestContent); + return _http.Put(config, path, enrollment); } public string GetCSR(string enrollmentId, string changeId, string keyType) @@ -174,14 +161,13 @@ public string GetCSR(string enrollmentId, string changeId, string keyType) // get CSR from new pending change var path = string.Format(Constants.Endpoints.GetChange, enrollmentId, changeId); var acceptHeader = "application/vnd.akamai.cps.csr.v2+json"; - - _http.SetRequestHeaders(new Dictionary() + + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("GET", Hostname, path)); + Accept = acceptHeader, + }; - PendingChange change = _http.Get(path); + PendingChange change = _http.Get(config, path); // get CSR for correct key type of reenrollment template PendingCSR csr = change.csrs.Where(csr => string.Equals(csr.keyAlgorithm, keyType, StringComparison.CurrentCultureIgnoreCase)).SingleOrDefault(); @@ -193,14 +179,13 @@ public void DeletePendingChange(string enrollmentId, string changeId) { var path = string.Format(Constants.Endpoints.Changes, enrollmentId) + $"/{changeId}"; var acceptHeader = "application/vnd.akamai.cps.change-id.v1+json"; - - _http.SetRequestHeaders(new Dictionary() + + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("DELETE", Hostname, path)); + Accept = acceptHeader, + }; - var response = _http.DeleteRaw(path); + var response = _http.DeleteRaw(config, path); return; } @@ -220,18 +205,16 @@ public void PostCertificate(string enrollmentId, string changeId, string certifi } }; var body = JsonConvert.SerializeObject(cert); - var requestContent = new StringContent(body); var acceptHeader = "application/vnd.akamai.cps.change-id.v1+json"; var contentHeader = "application/vnd.akamai.cps.certificate-and-trust-chain.v2+json"; - - _http.SetRequestHeaders(new Dictionary() + + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("POST", Hostname, path, body)); - requestContent.Headers.ContentType = new MediaTypeHeaderValue(contentHeader); + Accept = acceptHeader, + ContentType = contentHeader, + }; - var response = _http.PostRaw(path, requestContent); + var response = _http.PostRaw(config, path, body); return; } @@ -240,15 +223,14 @@ public void DeployCertificate(string enrollmentId, string changeId) var path = string.Format(Constants.Endpoints.UpdateDeployment, enrollmentId, changeId); var acceptHeader = "application/vnd.akamai.cps.change-id.v1+json"; var contentHeader = "application/vnd.akamai.cps.deployment-schedule.v1+json"; - - _http.SetRequestHeaders(new Dictionary() + + var config = new HttpRequestConfig() { - {"Accept", acceptHeader}, - {"Content-Type", contentHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("PUT", Hostname, path)); + Accept = acceptHeader, + ContentType = contentHeader, + }; - var response = _http.GetRaw(path); + var response = _http.GetRaw(config, path); return; } @@ -257,18 +239,16 @@ public void AcknowledgeWarnings(string enrollmentId, string changeId) var path = string.Format(Constants.Endpoints.AcknowledgePostVerification, enrollmentId, changeId); var ack = new Acknowledgement(); var body = JsonConvert.SerializeObject(ack); - var requestContent = new StringContent(body); var acceptHeader = "application/vnd.akamai.cps.change-id.v1+json"; var contentHeader = "application/vnd.akamai.cps.acknowledgement.v1+json"; - _http.SetRequestHeaders(new Dictionary() + var config = new HttpRequestConfig() { - {"Accept", acceptHeader} - }); - _http.AddAuthHeader(_auth.GenerateAuthHeader("POST", Hostname, path, body)); - requestContent.Headers.ContentType = new MediaTypeHeaderValue(contentHeader); + Accept = acceptHeader, + ContentType = contentHeader, + }; - var response = _http.PostRaw(path, requestContent); + var response = _http.PostRaw(config, path, body); return; } } diff --git a/http-interface.Tests/HttpUtilitiesTests.cs b/http-interface.Tests/HttpUtilitiesTests.cs index 72ddc65..3d53849 100644 --- a/http-interface.Tests/HttpUtilitiesTests.cs +++ b/http-interface.Tests/HttpUtilitiesTests.cs @@ -1,3 +1,17 @@ +// Copyright 2025 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.Extensions.Utilities.HttpInterface; using Xunit; @@ -69,4 +83,30 @@ public void GetRetryDate_WhenHeaderDoesNotIncludeRetryDate_DefaultIsOverridden_R var result = HttpUtilities.GetRetryAfterDelay(response, mockNow, 15); Assert.Equal(expected, result); } + + [Theory] + [InlineData("hostname.com", "hostname.com")] + [InlineData("https://hostname.com", "hostname.com")] + [InlineData("http://hostname.com", "hostname.com")] + [InlineData("hostname.com/some/path", "hostname.com")] + [InlineData("api.hostname.com", "api.hostname.com")] + [InlineData("api.hostname.com/", "api.hostname.com")] + public void TryGetHostname_WhenInputIsValid_ReturnsExpectedHostname(string input, string expected) + { + var ok = HttpUtilities.TryGetHostname(input, out string result); + Assert.True(ok); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + [InlineData("some host")] + public void TryGetHostname_WhenInputIsInvalid_ReturnsFalseResult(string input) + { + var ok = HttpUtilities.TryGetHostname(input, out string result); + Assert.False(ok); + Assert.Equal(input, result); + } } diff --git a/akamai-cps-orchestrator/Models/AkamaiAuth.cs b/http-interface/AkamaiAuth.cs similarity index 97% rename from akamai-cps-orchestrator/Models/AkamaiAuth.cs rename to http-interface/AkamaiAuth.cs index be2e5a2..f3b10ac 100644 --- a/akamai-cps-orchestrator/Models/AkamaiAuth.cs +++ b/http-interface/AkamaiAuth.cs @@ -1,4 +1,4 @@ -// Copyright 2023 Keyfactor +// Copyright 2025 Keyfactor // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ using System.Security.Cryptography; using System.Text; -namespace Keyfactor.Orchestrator.Extensions.AkamaiCpsOrchestrator.Models +namespace Keyfactor.Extensions.Utilities.HttpInterface { public class AkamaiAuth { diff --git a/http-interface/HttpInterface.cs b/http-interface/HttpInterface.cs index b621917..988cec4 100644 --- a/http-interface/HttpInterface.cs +++ b/http-interface/HttpInterface.cs @@ -23,63 +23,75 @@ namespace Keyfactor.Extensions.Utilities.HttpInterface { + public class HttpRequestConfig + { + public string Accept { get; set; } = "application/json"; + public string ContentType { get; set; } = "application/json"; + } + public class HttpInterface { private ILogger _logger; private HttpClient _http; private SocketsHttpHandler _httpHandler; // TODO: use to make requests with headers + private HttpService _httpService; + private readonly AkamaiAuth _auth; + private readonly string _hostname; + private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }; public int Timeout { get; set; } - public HttpInterface(ILogger logger, string baseAddress, bool useSSL) + public HttpInterface(ILogger logger, AkamaiAuth auth, string hostname, bool useSSL) { _logger = logger; _http = new HttpClient(); - // TODO: check if http(s) prefix is already present - // TODO: parse incoming baseAddress as URI - _logger.LogDebug($"Base Address - {baseAddress}"); + + _logger.LogDebug($"Base Address - {hostname}"); _logger.LogDebug($"Using SSL - {useSSL}"); + + _auth = auth; + + // TODO: Configure the max retries via a parameter? + _httpService = new HttpService(_logger, _http, 3); + + HttpUtilities.TryGetHostname(hostname, out _hostname); + if (useSSL) { - _http.BaseAddress = new Uri($"https://{baseAddress}/"); + _http.BaseAddress = new Uri($"https://{_hostname}/"); } else { - _http.BaseAddress = new Uri($"http://{baseAddress}/"); + _http.BaseAddress = new Uri($"http://{_hostname}/"); } } - - public void SetRequestHeaders(Dictionary headers) + + public T Get(HttpRequestConfig req, string path) { - // TODO: set on httpHandler per request instead of defaults for whole client each time - _http.DefaultRequestHeaders.Clear(); - foreach (var key in headers.Keys) - { - _logger.LogDebug($"Adding header - {key}: {headers[key]}"); - _http.DefaultRequestHeaders.Add(key, headers[key]); - } - - } - - public void AddAuthHeader(AuthenticationHeaderValue authHeader) - { - _http.DefaultRequestHeaders.Authorization = authHeader; - } - - public T Get(string path) - { - string json = GetRaw(path); + string json = GetRaw(req, path); _logger.LogTrace($"Received GET response. Deserializing into type {typeof(T)}"); return JsonConvert.DeserializeObject(json); } - - public string GetRaw(string path) + + public string GetRaw(HttpRequestConfig req, string path) { try { _logger.LogDebug($"Performing GET request to {_http.BaseAddress}/{path}"); - var response = _http.GetAsync(path).Result; + + var config = new HttpServiceConfig() + { + Accept = req.Accept, + ContentType = req.ContentType, + AuthorizationDelegate = async () => _auth.GenerateAuthHeader("GET", _hostname, path) + }; + + var response = _httpService + .GetAsync(config, path) + .GetAwaiter() + .GetResult(); + _logger.LogTrace($"Completed GET request. Reading response"); return ReadHttpResponse(response); } @@ -104,20 +116,31 @@ public string GetRaw(string path) throw; } } - - public T Post(string path, StringContent body) + + public T2 Post(HttpRequestConfig req, string path, T1 body) { - string json = PostRaw(path, body); - _logger.LogTrace($"Received POST response. Deserializing into type {typeof(T)}"); - return JsonConvert.DeserializeObject(json); + string content = JsonConvert.SerializeObject(body, _serializerSettings); + string json = PostRaw(req, path, content); + _logger.LogTrace($"Received POST response. Deserializing into type {typeof(T2)}"); + return JsonConvert.DeserializeObject(json); } - - public string PostRaw(string path, StringContent body) + + public string PostRaw(HttpRequestConfig req, string path, string body) { try { _logger.LogDebug($"Performing POST request to {_http.BaseAddress}/{path}"); - var response = _http.PostAsync(path, body).Result; + + var config = new HttpServiceConfig() + { + Accept = req.Accept, + ContentType = req.ContentType, + AuthorizationDelegate = async () => _auth.GenerateAuthHeader("POST", _hostname, path, body) + }; + + var content = new StringContent(body); + + var response = _httpService.PostAsync(config, path, content).GetAwaiter().GetResult(); _logger.LogTrace($"Completed POST request. Reading response"); return ReadHttpResponse(response); } @@ -142,20 +165,30 @@ public string PostRaw(string path, StringContent body) throw; } } - - public T Put(string path, StringContent body) + + public T2 Put(HttpRequestConfig req, string path, T1 content) { - string json = PutRaw(path, body); - _logger.LogTrace($"Received PUT response. Deserializing into type {typeof(T)}"); - return JsonConvert.DeserializeObject(json); + string body = JsonConvert.SerializeObject(content, _serializerSettings); + string json = PutRaw(req, path, body); + _logger.LogTrace($"Received PUT response. Deserializing into type {typeof(T2)}"); + return JsonConvert.DeserializeObject(json); } - - public string PutRaw(string path, StringContent body) + + public string PutRaw(HttpRequestConfig req, string path, string body) { try { _logger.LogDebug($"Performing PUT request to {_http.BaseAddress}/{path}"); - var response = _http.PutAsync(path, body).Result; + + var config = new HttpServiceConfig() + { + Accept = req.Accept, + ContentType = req.ContentType, + AuthorizationDelegate = async () => _auth.GenerateAuthHeader("PUT", _hostname, path) + }; + + var content = new StringContent(body); + var response = _httpService.PutAsync(config, path, content).GetAwaiter().GetResult(); _logger.LogTrace($"Completed PUT request. Reading response"); return ReadHttpResponse(response); } @@ -180,20 +213,28 @@ public string PutRaw(string path, StringContent body) throw; } } - - public T Delete(string path) + + public T Delete(HttpRequestConfig req, string path) { - string json = DeleteRaw(path); + string json = DeleteRaw(req, path); _logger.LogTrace($"Received DELETE response. Deserializing into type {typeof(T)}"); return JsonConvert.DeserializeObject(json); } - - public string DeleteRaw(string path) + + public string DeleteRaw(HttpRequestConfig req, string path) { try { _logger.LogDebug($"Performing DELETE request to {_http.BaseAddress}/{path}"); - var response = _http.DeleteAsync(path).Result; + + var config = new HttpServiceConfig() + { + Accept = req.Accept, + ContentType = req.ContentType, + AuthorizationDelegate = async () => _auth.GenerateAuthHeader("DELETE", _hostname, path) + }; + + var response = _httpService.DeleteAsync(config, path).GetAwaiter().GetResult(); _logger.LogTrace($"Completed DELETE request. Reading response"); return ReadHttpResponse(response); } @@ -221,7 +262,7 @@ public string DeleteRaw(string path) private string ReadHttpResponse(HttpResponseMessage response) { - string responseMessage = response.Content.ReadAsStringAsync().Result; + string responseMessage = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); if (response.IsSuccessStatusCode) { return responseMessage; diff --git a/http-interface/HttpService.cs b/http-interface/HttpService.cs new file mode 100644 index 0000000..191add7 --- /dev/null +++ b/http-interface/HttpService.cs @@ -0,0 +1,136 @@ +// Copyright 2025 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 System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Polly; +using Polly.Retry; + +namespace Keyfactor.Extensions.Utilities.HttpInterface +{ + + public class HttpServiceConfig + { + public string Accept { get; set; } = "application/json"; + public string ContentType { get; set; } = "application/json"; + public Func> AuthorizationDelegate { get; set; } + } + + + public class HttpService + { + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + private readonly AsyncRetryPolicy _retryPolicy; + + public HttpService(ILogger logger, HttpClient httpClient, int maxRetries = 3) + { + _logger = logger; + _httpClient = httpClient; + + // Retry API requests that fail due to an HTTP 429 (rate limiting) + // See https://techdocs.akamai.com/cps/reference/rate-limiting + _retryPolicy = Policy + .HandleResult(r => r.StatusCode == (HttpStatusCode)429) + .WaitAndRetryAsync( + retryCount: maxRetries, + sleepDurationProvider: (retryAttempt, response, context) => + { + var retryAfter = HttpUtilities.GetRetryAfterDelay(response.Result, DateTimeOffset.UtcNow); + _logger.LogInformation($"Retrying in {retryAfter.TotalSeconds} seconds (Attempt {retryAttempt})"); + return retryAfter; + }, + onRetryAsync: (outcome, delay, retryAttempt, context) => + { + _logger.LogDebug($"Retry {retryAttempt} scheduled after {delay.TotalSeconds} seconds due to {outcome.Result.StatusCode}"); + return Task.CompletedTask; + }); + } + + public async Task GetAsync(HttpServiceConfig config, string url) + { + return await _retryPolicy.ExecuteAsync(async () => + { + var request = await BuildHttpRequestMessage(config, HttpMethod.Get, url); + + return await _httpClient.SendAsync(request).ConfigureAwait(false); + }) + .ConfigureAwait(false); + } + + public async Task PostAsync(HttpServiceConfig config, string url, HttpContent content) + { + return await _retryPolicy.ExecuteAsync(async () => + { + var request = await BuildHttpRequestMessage(config, HttpMethod.Post, url); + AddContentToRequest(config, request, content); + return await _httpClient.SendAsync(request); + }) + .ConfigureAwait(false); + } + + public async Task PutAsync(HttpServiceConfig config, string url, HttpContent content) + { + return await _retryPolicy.ExecuteAsync(async () => + { + var request = await BuildHttpRequestMessage(config, HttpMethod.Put, url); + AddContentToRequest(config, request, content); + return await _httpClient.SendAsync(request); + }) + .ConfigureAwait(false); + } + + public async Task DeleteAsync(HttpServiceConfig config, string url) + { + return await _retryPolicy.ExecuteAsync(async () => + { + var request = await BuildHttpRequestMessage(config, HttpMethod.Delete, url); + + return await _httpClient.SendAsync(request).ConfigureAwait(false); + }) + .ConfigureAwait(false); + } + + private async Task BuildHttpRequestMessage(HttpServiceConfig config, HttpMethod method, string url) + { + var request = new HttpRequestMessage(method, url); + + if (config.Accept != null) + { + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(config.Accept)); + } + + if (config.AuthorizationDelegate != null) + { + request.Headers.Authorization = await config.AuthorizationDelegate().ConfigureAwait(false); + } + + return request; + } + + private void AddContentToRequest(HttpServiceConfig config, HttpRequestMessage request, HttpContent content) + { + if (config.ContentType != null) + { + content.Headers.ContentType = new MediaTypeHeaderValue(config.ContentType); + } + + request.Content = content; + } + } +} diff --git a/http-interface/HttpUtilities.cs b/http-interface/HttpUtilities.cs index 0a134c7..57b8ba8 100644 --- a/http-interface/HttpUtilities.cs +++ b/http-interface/HttpUtilities.cs @@ -1,4 +1,19 @@ +// Copyright 2025 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 System; +using System.Globalization; using System.Linq; using System.Net.Http; @@ -6,6 +21,14 @@ namespace Keyfactor.Extensions.Utilities.HttpInterface { public static class HttpUtilities { + /// + /// Parses a retry date from the "X-RateLimit-Next" header on an HTTP response, if present, and generate a TimeSpan based off the current time. + /// If the header value is missing or if parsing fails, it will return a default TimeSpan. + /// + /// An HTTP response message for a failed request + /// The current timestamp in UTC + /// The default retry time interval if parsing fails + /// A TimeSpan indicating how long to wait to retry the request. public static TimeSpan GetRetryAfterDelay(HttpResponseMessage response, DateTimeOffset now, int defaultTimeoutSeconds = 60) { var defaultDelay = TimeSpan.FromSeconds(defaultTimeoutSeconds); @@ -15,7 +38,7 @@ public static TimeSpan GetRetryAfterDelay(HttpResponseMessage response, DateTime } var nextHeader = response.Headers.First(p => p.Key == "X-RateLimit-Next"); - if (DateTimeOffset.TryParse(nextHeader.Value.FirstOrDefault(), out var retryUtc)) + if (DateTimeOffset.TryParse(nextHeader.Value.FirstOrDefault(), null, DateTimeStyles.AdjustToUniversal, out var retryUtc)) { // Add a 1-second buffer to the retry to avoid sending requests before server is ready. var delay = (retryUtc - now) + TimeSpan.FromSeconds(1); @@ -24,5 +47,37 @@ public static TimeSpan GetRetryAfterDelay(HttpResponseMessage response, DateTime return defaultDelay; } + + /// + /// Returns a hostname from a URL. If paring succeeds, the output string will have the parsed hostname and the method will return true. + /// If parsing fails, the output string will return the original value and the method will return false. + /// + /// + /// + /// A boolean indicating if parsing was successful + public static bool TryGetHostname(string input, out string parsed) + { + parsed = input; + + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + // Ensure the input has a scheme so Uri.TryCreate parses correctly + if (!input.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && + !input.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + input = "http://" + input; + } + + if (Uri.TryCreate(input, UriKind.Absolute, out var uri)) + { + parsed = uri.Host; + return true; + } + + return false; + } } } diff --git a/http-interface/http-interface.csproj b/http-interface/http-interface.csproj index befe96d..0da2b03 100644 --- a/http-interface/http-interface.csproj +++ b/http-interface/http-interface.csproj @@ -8,6 +8,7 @@ + From fb992f50f17d96d8c720198eb1c06af2cfe1255a Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Tue, 13 May 2025 17:09:06 -0400 Subject: [PATCH 05/18] chore(docs): Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37d52b..e5aff41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +1.2.0 +- Add support for retrying API requests that fail due to [Akamai's rate limiting](https://techdocs.akamai.com/cps/reference/rate-limiting) + 1.1.0 - Update Expiration Alert Handler sample to filter Subject Elements for duplicates / invalid fields and set the subject to one that will be accepted and matched in the CSR from Akamai - Additional error handling around renewing / updating existing Akamai Enrollments From 146a66799e1b5e8af1348fa975fcef86bb03deac Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Wed, 14 May 2025 09:39:00 -0400 Subject: [PATCH 06/18] chore: Code reorganization Signed-off-by: Matthew H. Irby --- TestConsole/Program.cs | 16 +++++++++++++++- http-interface/HttpInterface.cs | 6 ------ http-interface/HttpRequestConfig.cs | 22 ++++++++++++++++++++++ http-interface/HttpService.cs | 19 +++++-------------- http-interface/HttpServiceConfig.cs | 27 +++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 http-interface/HttpRequestConfig.cs create mode 100644 http-interface/HttpServiceConfig.cs diff --git a/TestConsole/Program.cs b/TestConsole/Program.cs index 7e4abbe..da03b3c 100644 --- a/TestConsole/Program.cs +++ b/TestConsole/Program.cs @@ -1,4 +1,18 @@ -using System.Diagnostics; +// Copyright 2025 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 System.Diagnostics; using Keyfactor.Extensions.Utilities.HttpInterface; using Keyfactor.Orchestrator.Extensions.AkamaiCpsOrchestrator.Models; using Microsoft.Extensions.Logging; diff --git a/http-interface/HttpInterface.cs b/http-interface/HttpInterface.cs index 988cec4..4f72e1b 100644 --- a/http-interface/HttpInterface.cs +++ b/http-interface/HttpInterface.cs @@ -23,12 +23,6 @@ namespace Keyfactor.Extensions.Utilities.HttpInterface { - public class HttpRequestConfig - { - public string Accept { get; set; } = "application/json"; - public string ContentType { get; set; } = "application/json"; - } - public class HttpInterface { private ILogger _logger; diff --git a/http-interface/HttpRequestConfig.cs b/http-interface/HttpRequestConfig.cs new file mode 100644 index 0000000..a06e8d0 --- /dev/null +++ b/http-interface/HttpRequestConfig.cs @@ -0,0 +1,22 @@ +// Copyright 2025 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. + +namespace Keyfactor.Extensions.Utilities.HttpInterface +{ + public class HttpRequestConfig + { + public string Accept { get; set; } = "application/json"; + public string ContentType { get; set; } = "application/json"; + } +} diff --git a/http-interface/HttpService.cs b/http-interface/HttpService.cs index 191add7..4ef8543 100644 --- a/http-interface/HttpService.cs +++ b/http-interface/HttpService.cs @@ -23,15 +23,6 @@ namespace Keyfactor.Extensions.Utilities.HttpInterface { - - public class HttpServiceConfig - { - public string Accept { get; set; } = "application/json"; - public string ContentType { get; set; } = "application/json"; - public Func> AuthorizationDelegate { get; set; } - } - - public class HttpService { private readonly ILogger _logger; @@ -66,7 +57,7 @@ public async Task GetAsync(HttpServiceConfig config, string { return await _retryPolicy.ExecuteAsync(async () => { - var request = await BuildHttpRequestMessage(config, HttpMethod.Get, url); + using HttpRequestMessage request = await BuildHttpRequestMessage(config, HttpMethod.Get, url); return await _httpClient.SendAsync(request).ConfigureAwait(false); }) @@ -77,7 +68,7 @@ public async Task PostAsync(HttpServiceConfig config, strin { return await _retryPolicy.ExecuteAsync(async () => { - var request = await BuildHttpRequestMessage(config, HttpMethod.Post, url); + using HttpRequestMessage request = await BuildHttpRequestMessage(config, HttpMethod.Post, url); AddContentToRequest(config, request, content); return await _httpClient.SendAsync(request); }) @@ -88,7 +79,7 @@ public async Task PutAsync(HttpServiceConfig config, string { return await _retryPolicy.ExecuteAsync(async () => { - var request = await BuildHttpRequestMessage(config, HttpMethod.Put, url); + using HttpRequestMessage request = await BuildHttpRequestMessage(config, HttpMethod.Put, url); AddContentToRequest(config, request, content); return await _httpClient.SendAsync(request); }) @@ -99,7 +90,7 @@ public async Task DeleteAsync(HttpServiceConfig config, str { return await _retryPolicy.ExecuteAsync(async () => { - var request = await BuildHttpRequestMessage(config, HttpMethod.Delete, url); + using HttpRequestMessage request = await BuildHttpRequestMessage(config, HttpMethod.Delete, url); return await _httpClient.SendAsync(request).ConfigureAwait(false); }) @@ -108,7 +99,7 @@ public async Task DeleteAsync(HttpServiceConfig config, str private async Task BuildHttpRequestMessage(HttpServiceConfig config, HttpMethod method, string url) { - var request = new HttpRequestMessage(method, url); + HttpRequestMessage request = new HttpRequestMessage(method, url); if (config.Accept != null) { diff --git a/http-interface/HttpServiceConfig.cs b/http-interface/HttpServiceConfig.cs new file mode 100644 index 0000000..5fc5c7a --- /dev/null +++ b/http-interface/HttpServiceConfig.cs @@ -0,0 +1,27 @@ +// Copyright 2025 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 System; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Utilities.HttpInterface +{ + public class HttpServiceConfig + { + public string Accept { get; set; } = "application/json"; + public string ContentType { get; set; } = "application/json"; + public Func> AuthorizationDelegate { get; set; } + } +} From 908c38d88f1eef040301762d2a37d0b19981999c Mon Sep 17 00:00:00 2001 From: Macey Dobrowsky Date: Mon, 19 May 2025 19:59:45 +0000 Subject: [PATCH 07/18] update to v3 workflow with doctool and net6/net8 target updates --- .../workflows/keyfactor-starter-workflow.yml | 47 ++--- .../akamai-cps-orchestrator.csproj | 2 +- docsource/akamai.md | 1 + docsource/content.md | 82 ++++++++ integration-manifest.json | 177 +++++++++--------- 5 files changed, 192 insertions(+), 117 deletions(-) create mode 100644 docsource/akamai.md create mode 100644 docsource/content.md diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml index b779b53..d92bd57 100644 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ b/.github/workflows/keyfactor-starter-workflow.yml @@ -1,33 +1,20 @@ -name: Starter Workflow -on: [workflow_dispatch, push, pull_request] +name: Keyfactor Bootstrap Workflow -jobs: - call-create-github-release-workflow: - uses: Keyfactor/actions/.github/workflows/github-release.yml@main - - call-assign-from-json-workflow: - uses: Keyfactor/actions/.github/workflows/assign-env-from-json.yml@main - - call-dotnet-build-and-release-workflow: - needs: [call-create-github-release-workflow, call-assign-from-json-workflow] - uses: Keyfactor/actions/.github/workflows/dotnet-build-and-release.yml@main - with: - release_version: ${{ needs.call-create-github-release-workflow.outputs.release_version }} - release_url: ${{ needs.call-create-github-release-workflow.outputs.release_url }} - release_dir: ${{ needs.call-assign-from-json-workflow.outputs.release_dir }} +on: + workflow_dispatch: + pull_request: + types: [opened, closed, synchronize, edited, reopened] + push: + create: + branches: + - 'release-*.*' - secrets: - token: ${{ secrets.PRIVATE_PACKAGE_ACCESS }} - - call-generate-readme-workflow: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - uses: Keyfactor/actions/.github/workflows/generate-readme.yml@main +jobs: + call-starter-workflow: + uses: keyfactor/actions/.github/workflows/starter.yml@v3 secrets: - token: ${{ secrets.APPROVE_README_PUSH }} - - call-update-catalog-workflow: - needs: call-assign-from-json-workflow - if: needs.call-assign-from-json-workflow.outputs.update_catalog == 'True' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') - uses: Keyfactor/actions/.github/workflows/update-catalog.yml@main - secrets: - token: ${{ secrets.SDK_SYNC_PAT }} + token: ${{ secrets.V2BUILDTOKEN}} + APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}} + gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} + gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} + scan_token: ${{ secrets.SAST_TOKEN }} diff --git a/akamai-cps-orchestrator/akamai-cps-orchestrator.csproj b/akamai-cps-orchestrator/akamai-cps-orchestrator.csproj index 2617ec7..141763d 100644 --- a/akamai-cps-orchestrator/akamai-cps-orchestrator.csproj +++ b/akamai-cps-orchestrator/akamai-cps-orchestrator.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0;net8.0 Keyfactor.Orchestrator.Extensions.AkamaiCpsOrchestrator true diff --git a/docsource/akamai.md b/docsource/akamai.md new file mode 100644 index 0000000..0aa5d83 --- /dev/null +++ b/docsource/akamai.md @@ -0,0 +1 @@ +## Overview \ No newline at end of file diff --git a/docsource/content.md b/docsource/content.md new file mode 100644 index 0000000..c60283e --- /dev/null +++ b/docsource/content.md @@ -0,0 +1,82 @@ + +## Use Cases + +The Akamai CPS orchestrator extension implements the following capabilities: +1. Inventory - Return all certificates of the type defined in the cert store (Production or Staging) +2. Reenrollment - Process a key generation request and create a new certificate with a Keyfactor CA. Two scenarios are supported: + 1. No Enrollment Id provided - create a new Enrollment and certificate in Akamai + 2. Existing Enrollment Id provided - renew an existing certificate in Akamai and update the Enrollment + +## Keyfactor Version Supported + +The Akamai CPS orchestrator extension requires a Keyfactor Platform version of 9.10 or greater to support encrypted certificate store parameters for authentication. + +## Akamai Platform Configuration + +In the Akamai instance, an API Credential needs to be configured and used to provide authentication for the Keyfactor Orchestrator extension to function. To do this, navigate to `Account Admin` -> `Identity & access`. Clicking `Create API client`, select a user whose permissions should be used to access and manage certificates. This user should already have the needed permissions to access CPS. The access of the API Client can be restricted to just the CPS APIs, but the API Client should have `READ-WRITE` access. + +With the API Client created, a new credential should be generated by clicking `Create credential`. The contents of the credential should be downloaded or saved temporarily to use for configuring the Keyfactor Certificate Store. Afterwards, it should be deleted as the credential file serves as authentication for accessing APIs, and should be treated as a plaintext password and not saved long-term. + +## Akamai Orchestrator Extension Configuration + +**1a. Use `kfutil` to create the entire store type definition** + +Using `kfutil` to create the store type is the preferred method to create the Akamai store type. It will create all of the needed Custom Fields and Entry parameters (of which there are many). + +Creating the store can be done by running the following `kfutil` command: + +``` +kfutil store-types create -n Akamai +``` + +If using `kfutil`, skip steps __1b__ and __2__ and go to step __3__ to set the default values of the Entry Parameters. + +**1b. Manually Create the New Certificate Store Type for the Akamai orchestrator extension** + +In Keyfactor Command create a new Certificate Store Type similar to the one below by clicking Settings (the gear icon in the top right) => Certificate Store Types => Add: + +![](images/store-type-basic.png) +![](images/store-type-advanced.png) + +Custom fields and entry parameters will be added after the store is created. This is required as there are many entry parameters. + +**2. Add Custom Fields and Entry Paramaters** + +_Only requried if manually adding the certificate store._ +To add the needed Custom Fields and Entry Parameters, [run the script](akamai-cps-orchestrator/jobproperties.sql) on the Keyfactor database to generate all the fields and parameters needed. + +**3. Set default values of Entry Parameters** + +The Entry Parameters are used during Enrollment creation in Akamai CPS to provide contact information and associate new certificates with the correct Contract in Akamai. After adding the parameters, re-open the Certificate Store Type configuration and set the default values. + +The Contract ID should be set to the default contract to be used for new Enrollments. All of the address information should be filled out with default expected values, as they are required fields for **each** enrollment created and should not be entered manually unless they need to be overwritten for a specific Enrollment in Akamai. +The Tech contact information should be your Akamai company contact, and needs to have an Akamai email address and should have Akamai as the organization name. + +**4. Create a new Akamai Certificate Store** + +After the Certificate Store Type has been configured, a new Akamai Certificate Store can be created. When creating the store, the credentials generated in the Akamai platform for the API Client will be used. + +| Certificate Store parameter | Akamai credential value | +|-|-| +| Client Machine | `host` | +| Access Token | `access_token` | +| Client Token | `client_token` | +| Client Secret | `client_secret` | + +**5. (Optional) Enroll a new certificate in Akamai** + +Adding new certificates to Akamai requires generating a key in Akamai CPS via the Reenrollment process in Keyfactor. To start this process, go to the Certificate Store that the certicate should be added to. Select the certificate store, and click the `Reenrollment` button to bring up the reenrollment dialog. + +Change any default values as needed, and enter an Enrollment ID if an existing enrollment needs to be updated instead of creating a new Enrollment. This is different from the Slot ID - the Enrollment ID is found by clicking on an Active certificate in Akamai CPS, and looking at the `ID` value. +The SAN entry needs to be filled out with the DNS value you are using for the certificate's CN. If there are multiple DNS SANs, they should be separted with an ampersand. Example: `www.example01.com&www.example02.com` + + +**6. (Optional) Configure Renewal of Certificates using a Workflow** + +Akamai does not support traditional certificate Renewal or one-click Renewal done in the Keyfactor Command platform. The Renewal process creates Certificates with outside keys which are not allowed to be imported into Akamai CPS. As a result, the Reenrollment Job must be used in order to renew existing certificates that reside on the Akamai system. Reenrollment is required as opposed to the Renewal process as it allows Akamai to generate the keys on their platform, which are used to create a certificate in Keyfactor. + +Renewing existing certificates in Akamai means running a Reenrollment Job with the same Enrollment ID that was used for an existing Certificate Enrollment. This can be done manually through the Reenrollment prompt, but an automated process can also be configured using a Keyfactor Workflow. + +The Workflow should be configured to target a Keyfactor Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to `Production` or `Staging`. + +A sample workflow for ODKG / Reenrollment scheduling for renewals can be viewed in the [kf-workflow-samples repo](https://github.com/Keyfactor/kf-workflow-samples). When running the sample workflow, it will assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. diff --git a/integration-manifest.json b/integration-manifest.json index 6204c5c..51bc801 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -3,7 +3,9 @@ "integration_type": "orchestrator", "name": "Akamai Certificate Provisioning System (CPS)", "status": "production", - "release_dir": "akamai-cps-orchestrator/bin/Release/netcoreapp3.1", + "support_level": "kf-supported", + "release_dir": "akamai-cps-orchestrator/bin/Release", + "release_project": "akamai-cps-orchestrator/akamai-cps-orchestrator.csproj", "update_catalog": true, "link_github": true, "description": "The Akamai Certificate Provisioning System (CPS) Orchestrator is capable of inventorying existing certificates on the Akamai platform, and performing enrollments and renewals of certificates with keys generated on the Akamai system.", @@ -29,8 +31,8 @@ "supportsInventory": true, "platformSupport": "Unused" }, - "store_types": { - "Akamai": { + "store_types": [ + { "Name": "Akamai Certificate Provisioning Service", "ShortName": "Akamai", "Capability": "Akamai", @@ -44,36 +46,38 @@ }, "Properties": [ { - "StoreTypeId;omitempty": 0, "Name": "access_token", "DisplayName": "Access Token", "Type": "Secret", - "DependsOn": null, - "DefaultValue": null, - "Required": true + "DependsOn": "", + "DefaultValue": "", + "Required": true, + "IsPAMEligible": false, + "Description": "The Akamai access_token for authentication." }, { - "StoreTypeId;omitempty": 0, "Name": "client_token", "DisplayName": "Client Token", "Type": "Secret", - "DependsOn": null, - "DefaultValue": null, - "Required": true + "DependsOn": "", + "DefaultValue": "", + "Required": true, + "IsPAMEligible": false, + "Description": "The Akamai client_token for authentication." }, { - "StoreTypeId;omitempty": 0, "Name": "client_secret", "DisplayName": "Client Secret", "Type": "Secret", - "DependsOn": null, - "DefaultValue": null, - "Required": true + "DependsOn": "", + "DefaultValue": "", + "Required": true, + "IsPAMEligible": false, + "Description": "The Akamai client_secret for authentication." } ], "EntryParameters": [ { - "StoreTypeId;omitempty": 0, "Name": "EnrollmentId", "DisplayName": "Enrollment ID", "Type": "String", @@ -82,10 +86,10 @@ "OnAdd": false, "OnRemove": false, "OnReenrollment": false - } + }, + "Description": "Enrollment ID of a certificate enrollment in Akamai. This should only be supplied for ODKG when replacing an existing certificate." }, { - "StoreTypeId;omitempty": 0, "Name": "ContractId", "DisplayName": "Contract ID", "Type": "String", @@ -95,10 +99,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "The Contract ID of your account in Akamai." }, { - "StoreTypeId;omitempty": 0, "Name": "Sans", "DisplayName": "SANs", "Type": "String", @@ -107,10 +111,10 @@ "OnAdd": false, "OnRemove": false, "OnReenrollment": true - } + }, + "Description": "SANs for the new certificate. If multiple are supplied, they should be split with an ampersand character '&'" }, { - "StoreTypeId;omitempty": 0, "Name": "admin-addressLineOne", "DisplayName": "Admin - Address Line 1", "Type": "String", @@ -120,10 +124,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-addressLineTwo", "DisplayName": "Admin - Address Line 2", "Type": "String", @@ -132,10 +136,10 @@ "OnAdd": false, "OnRemove": false, "OnReenrollment": false - } + }, + "Description": "Optional field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-city", "DisplayName": "Admin - City", "Type": "String", @@ -145,10 +149,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-country", "DisplayName": "Admin - Country", "Type": "String", @@ -158,10 +162,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-email", "DisplayName": "Admin - Email", "Type": "String", @@ -171,10 +175,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-firstName", "DisplayName": "Admin - First Name", "Type": "String", @@ -184,10 +188,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-lastName", "DisplayName": "Admin - Last Name", "Type": "String", @@ -197,10 +201,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-organizationName", "DisplayName": "Admin - Organization Name", "Type": "String", @@ -210,10 +214,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-phone", "DisplayName": "Admin - Phone", "Type": "String", @@ -223,10 +227,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-postalCode", "DisplayName": "Admin - Postal Code", "Type": "String", @@ -236,10 +240,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-region", "DisplayName": "Admin - Region", "Type": "String", @@ -249,10 +253,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "admin-title", "DisplayName": "Admin - Title", "Type": "String", @@ -262,10 +266,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Administrator contact." }, { - "StoreTypeId;omitempty": 0, "Name": "org-addressLineOne", "DisplayName": "Org - Address Line 1", "Type": "String", @@ -275,10 +279,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Organization contact." }, { - "StoreTypeId;omitempty": 0, "Name": "org-addressLineTwo", "DisplayName": "Org - Address Line 2", "Type": "String", @@ -287,10 +291,10 @@ "OnAdd": false, "OnRemove": false, "OnReenrollment": false - } + }, + "Description": "Optional field for Organization contact." }, { - "StoreTypeId;omitempty": 0, "Name": "org-city", "DisplayName": "Org - City", "Type": "String", @@ -300,10 +304,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Organization contact." }, { - "StoreTypeId;omitempty": 0, "Name": "org-country", "DisplayName": "Org - Country", "Type": "String", @@ -313,10 +317,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Organization contact." }, { - "StoreTypeId;omitempty": 0, "Name": "org-organizationName", "DisplayName": "Org - Organization Name", "Type": "String", @@ -326,10 +330,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Organization contact." }, { - "StoreTypeId;omitempty": 0, "Name": "org-phone", "DisplayName": "Org - Phone", "Type": "String", @@ -339,10 +343,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Organization contact." }, { - "StoreTypeId;omitempty": 0, "Name": "org-postalCode", "DisplayName": "Org - Postal Code", "Type": "String", @@ -352,10 +356,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Organization contact." }, { - "StoreTypeId;omitempty": 0, "Name": "org-region", "DisplayName": "Org - Region", "Type": "String", @@ -365,10 +369,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Organization contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-addressLineOne", "DisplayName": "Tech - Address Line 1", "Type": "String", @@ -378,10 +382,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-addressLineTwo", "DisplayName": "Tech - Address Line 2", "Type": "String", @@ -390,10 +394,10 @@ "OnAdd": false, "OnRemove": false, "OnReenrollment": false - } + }, + "Description": "Optional field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-city", "DisplayName": "Tech - City", "Type": "String", @@ -403,10 +407,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-country", "DisplayName": "Tech - Country", "Type": "String", @@ -416,10 +420,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-email", "DisplayName": "Tech - Email", "Type": "String", @@ -429,10 +433,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact. Must be an akamai.com email address." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-firstName", "DisplayName": "Tech - First Name", "Type": "String", @@ -442,10 +446,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-lastName", "DisplayName": "Tech - Last Name", "Type": "String", @@ -455,10 +459,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-organizationName", "DisplayName": "Tech - Organization Name", "Type": "String", @@ -468,10 +472,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "Akamai", + "Description": "Required field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-phone", "DisplayName": "Tech - Phone", "Type": "String", @@ -481,10 +485,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-postalCode", "DisplayName": "Tech - Postal Code", "Type": "String", @@ -494,10 +498,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-region", "DisplayName": "Tech - Region", "Type": "String", @@ -507,10 +511,10 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact." }, { - "StoreTypeId;omitempty": 0, "Name": "tech-title", "DisplayName": "Tech - Title", "Type": "String", @@ -520,7 +524,8 @@ "OnRemove": false, "OnReenrollment": true }, - "DefaultValue": null + "DefaultValue": "SET-DEFAULT", + "Description": "Required field for Akamai Tech contact." } ], "PasswordOptions": { @@ -536,7 +541,7 @@ "BlueprintAllowed": false, "CustomAliasAllowed": "Forbidden" } - } + ] } } } \ No newline at end of file From bca24018fd07370a947cddaa4c5f937c568ff522 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Mon, 19 May 2025 20:01:32 +0000 Subject: [PATCH 08/18] Update generated docs --- README.md | 351 ++++++++++++++++++++++++++++++++++++++----- docsource/content.md | 6 +- 2 files changed, 320 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 13c4412..346e6b1 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,334 @@ -# Akamai Certificate Provisioning System (CPS) +

+ Akamai Certificate Provisioning System (CPS) Universal Orchestrator Extension +

-The Akamai Certificate Provisioning System (CPS) Orchestrator is capable of inventorying existing certificates on the Akamai platform, and performing enrollments and renewals of certificates with keys generated on the Akamai system. +

+ +Integration Status: production +Release +Issues +GitHub Downloads (all assets, all releases) +

-#### Integration status: Production - Ready for use in production environments. +

+ + + Support + + · + + Installation + + · + + License + + · + + Related Integrations + +

+## Overview -## About the Keyfactor Universal Orchestrator Extension +TODO Overview is a required section -This repository contains a Universal Orchestrator Extension which is a plugin to the Keyfactor Universal Orchestrator. Within the Keyfactor Platform, Orchestrators are used to manage “certificate stores” — collections of certificates and roots of trust that are found within and used by various applications. -The Universal Orchestrator is part of the Keyfactor software distribution and is available via the Keyfactor customer portal. For general instructions on installing Extensions, see the “Keyfactor Command Orchestrator Installation and Configuration Guide” section of the Keyfactor documentation. For configuration details of this specific Extension see below in this readme. -The Universal Orchestrator is the successor to the Windows Orchestrator. This Orchestrator Extension plugin only works with the Universal Orchestrator and does not work with the Windows Orchestrator. +## Compatibility +This integration is compatible with Keyfactor Universal Orchestrator version 10.1 and later. -## Support for Akamai Certificate Provisioning System (CPS) +## Support +The Akamai Certificate Provisioning System (CPS) Universal Orchestrator extension If you have a support issue, please open a support ticket by either contacting your Keyfactor representative or via the Keyfactor Support Portal at https://support.keyfactor.com. -Akamai Certificate Provisioning System (CPS) +> 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. -###### 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. +## Requirements & Prerequisites +Before installing the Akamai Certificate Provisioning System (CPS) Universal Orchestrator extension, we recommend that you install [kfutil](https://github.com/Keyfactor/kfutil). Kfutil is a command-line tool that simplifies the process of creating store types, installing extensions, and instantiating certificate stores in Keyfactor Command. ---- +## Akamai Certificate Store Type +To use the Akamai Certificate Provisioning System (CPS) Universal Orchestrator extension, you **must** create the Akamai Certificate Store Type. This only needs to happen _once_ per Keyfactor Command instance. -## Keyfactor Version Supported -The minimum version of the Keyfactor Universal Orchestrator Framework needed to run this version of the extension is 10.1 -## Platform Specific Notes +
Click to expand details + + + + +#### Supported Operations + +| Operation | Is Supported | +|--------------|------------------------------------------------------------------------------------------------------------------------| +| Add | 🔲 Unchecked | +| Remove | 🔲 Unchecked | +| Discovery | 🔲 Unchecked | +| Reenrollment | ✅ Checked | +| Create | 🔲 Unchecked | + +#### Store Type Creation + +##### Using kfutil: +`kfutil` is a custom CLI for the Keyfactor Command API and can be used to created certificate store types. +For more information on [kfutil](https://github.com/Keyfactor/kfutil) check out the [docs](https://github.com/Keyfactor/kfutil?tab=readme-ov-file#quickstart) +
Click to expand Akamai kfutil details + + ##### Using online definition from GitHub: + This will reach out to GitHub and pull the latest store-type definition + ```shell + # Akamai Certificate Provisioning Service + kfutil store-types create Akamai + ``` + + ##### Offline creation using integration-manifest file: + If required, it is possible to create store types from the [integration-manifest.json](./integration-manifest.json) included in this repo. + You would first download the [integration-manifest.json](./integration-manifest.json) and then run the following command + in your offline environment. + ```shell + kfutil store-types create --from-file integration-manifest.json + ``` +
+ + +#### Manual Creation +Below are instructions on how to create the Akamai store type manually in +the Keyfactor Command Portal +
Click to expand manual Akamai details + + Create a store type called `Akamai` with the attributes in the tables below: + + ##### Basic Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Name | Akamai Certificate Provisioning Service | Display name for the store type (may be customized) | + | Short Name | Akamai | Short display name for the store type | + | Capability | Akamai | Store type name orchestrator will register with. Check the box to allow entry of value | + | Supports Add | 🔲 Unchecked | Indicates that the Store Type supports Management Add | + | Supports Remove | 🔲 Unchecked | Indicates that the Store Type supports Management Remove | + | Supports Discovery | 🔲 Unchecked | Indicates that the Store Type supports Discovery | + | Supports Reenrollment | ✅ Checked | Indicates that the Store Type supports Reenrollment | + | Supports Create | 🔲 Unchecked | Indicates that the Store Type supports store creation | + | Needs Server | 🔲 Unchecked | 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 | + | Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell | + | Requires Store Password | 🔲 Unchecked | Enables users to optionally specify a store password when defining a Certificate Store. | + | Supports Entry Password | 🔲 Unchecked | Determines if an individual entry within a store can have a password. | + + The Basic tab should look like this: + + ![Akamai Basic Tab](docsource/images/Akamai-basic-store-type-dialog.png) + + ##### Advanced Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Supports Custom Alias | Forbidden | Determines if an individual entry within a store can have a custom Alias. | + | Private Key Handling | Forbidden | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. | + | PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) | + + The Advanced tab should look like this: + + ![Akamai Advanced Tab](docsource/images/Akamai-advanced-store-type-dialog.png) + + > 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. + + ##### Custom Fields Tab + 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: + + | Name | Display Name | Description | Type | Default Value/Options | Required | + | ---- | ------------ | ---- | --------------------- | -------- | ----------- | + | access_token | Access Token | The Akamai access_token for authentication. | Secret | | ✅ Checked | + | client_token | Client Token | The Akamai client_token for authentication. | Secret | | ✅ Checked | + | client_secret | Client Secret | The Akamai client_secret for authentication. | Secret | | ✅ Checked | + + The Custom Fields tab should look like this: + + ![Akamai Custom Fields Tab](docsource/images/Akamai-custom-fields-store-type-dialog.png) + + ##### Entry Parameters Tab + + | Name | Display Name | Description | Type | Default Value | Entry has a private key | Adding an entry | Removing an entry | Reenrolling an entry | + | ---- | ------------ | ---- | ------------- | ----------------------- | ---------------- | ----------------- | ------------------- | ----------- | + | EnrollmentId | Enrollment ID | Enrollment ID of a certificate enrollment in Akamai. This should only be supplied for ODKG when replacing an existing certificate. | String | | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | + | ContractId | Contract ID | The Contract ID of your account in Akamai. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | Sans | SANs | SANs for the new certificate. If multiple are supplied, they should be split with an ampersand character '&' | String | | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-addressLineOne | Admin - Address Line 1 | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-addressLineTwo | Admin - Address Line 2 | Optional field for Administrator contact. | String | | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | + | admin-city | Admin - City | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-country | Admin - Country | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-email | Admin - Email | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-firstName | Admin - First Name | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-lastName | Admin - Last Name | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-organizationName | Admin - Organization Name | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-phone | Admin - Phone | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-postalCode | Admin - Postal Code | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-region | Admin - Region | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | admin-title | Admin - Title | Required field for Administrator contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | org-addressLineOne | Org - Address Line 1 | Required field for Organization contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | org-addressLineTwo | Org - Address Line 2 | Optional field for Organization contact. | String | | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | + | org-city | Org - City | Required field for Organization contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | org-country | Org - Country | Required field for Organization contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | org-organizationName | Org - Organization Name | Required field for Organization contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | org-phone | Org - Phone | Required field for Organization contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | org-postalCode | Org - Postal Code | Required field for Organization contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | org-region | Org - Region | Required field for Organization contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-addressLineOne | Tech - Address Line 1 | Required field for Akamai Tech contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-addressLineTwo | Tech - Address Line 2 | Optional field for Akamai Tech contact. | String | | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | + | tech-city | Tech - City | Required field for Akamai Tech contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-country | Tech - Country | Required field for Akamai Tech contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-email | Tech - Email | Required field for Akamai Tech contact. Must be an akamai.com email address. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-firstName | Tech - First Name | Required field for Akamai Tech contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-lastName | Tech - Last Name | Required field for Akamai Tech contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-organizationName | Tech - Organization Name | Required field for Akamai Tech contact. | String | Akamai | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-phone | Tech - Phone | Required field for Akamai Tech contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-postalCode | Tech - Postal Code | Required field for Akamai Tech contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-region | Tech - Region | Required field for Akamai Tech contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + | tech-title | Tech - Title | Required field for Akamai Tech contact. | String | SET-DEFAULT | 🔲 Unchecked | 🔲 Unchecked | 🔲 Unchecked | ✅ Checked | + + The Entry Parameters tab should look like this: + + ![Akamai Entry Parameters Tab](docsource/images/Akamai-entry-parameters-store-type-dialog.png) + + + +
+ +## Installation + +1. **Download the latest Akamai Certificate Provisioning System (CPS) Universal Orchestrator extension from GitHub.** + + Navigate to the [Akamai Certificate Provisioning System (CPS) Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/akamai-cps-orchestrator/releases/latest). Refer to the compatibility matrix below to determine whether the `net6.0` or `net8.0` asset should be downloaded. Then, click the corresponding asset to download the zip archive. + + | Universal Orchestrator Version | Latest .NET version installed on the Universal Orchestrator server | `rollForward` condition in `Orchestrator.runtimeconfig.json` | `akamai-cps-orchestrator` .NET version to download | + | --------- | ----------- | ----------- | ----------- | + | Older than `11.0.0` | | | `net6.0` | + | Between `11.0.0` and `11.5.1` (inclusive) | `net6.0` | | `net6.0` | + | Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `Disable` | `net6.0` | + | Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `LatestMajor` | `net8.0` | + | `11.6` _and_ newer | `net8.0` | | `net8.0` | + + Unzip the archive containing extension assemblies to a known location. + + > **Note** If you don't see an asset with a corresponding .NET version, you should always assume that it was compiled for `net6.0`. + +2. **Locate the Universal Orchestrator extensions directory.** + + * **Default on Windows** - `C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions` + * **Default on Linux** - `/opt/keyfactor/orchestrator/extensions` + +3. **Create a new directory for the Akamai Certificate Provisioning System (CPS) Universal Orchestrator extension inside the extensions directory.** + + Create a new directory called `akamai-cps-orchestrator`. + > The directory name does not need to match any names used elsewhere; it just has to be unique within the extensions directory. + +4. **Copy the contents of the downloaded and unzipped assemblies from __step 2__ to the `akamai-cps-orchestrator` directory.** -The Keyfactor Universal Orchestrator may be installed on either Windows or Linux based platforms. The certificate operations supported by a capability may vary based what platform the capability is installed on. The table below indicates what capabilities are supported based on which platform the encompassing Universal Orchestrator is running. -| Operation | Win | Linux | -|-----|-----|------| -|Supports Management Add| | | -|Supports Management Remove| | | -|Supports Create Store| | | -|Supports Discovery| | | -|Supports Renrollment|✓ |✓ | -|Supports Inventory|✓ |✓ | +5. **Restart the Universal Orchestrator service.** + Refer to [Starting/Restarting the Universal Orchestrator service](https://software.keyfactor.com/Core-OnPrem/Current/Content/InstallingAgents/NetCoreOrchestrator/StarttheService.htm). +6. **(optional) PAM Integration** + The Akamai Certificate Provisioning System (CPS) Universal Orchestrator extension is compatible with all supported Keyfactor PAM extensions to resolve PAM-eligible secrets. PAM extensions running on Universal Orchestrators enable secure retrieval of secrets from a connected PAM provider. + + To configure a PAM provider, [reference the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam) to select an extension and follow the associated instructions to install it on the Universal Orchestrator (remote). + + +> The above installation steps can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/InstallingAgents/NetCoreOrchestrator/CustomExtensions.htm?Highlight=extensions). + + + +## Defining Certificate Stores + + + +### Store Creation + +#### Manually with the Command UI + +
Click to expand details + +1. **Navigate to the _Certificate Stores_ page in Keyfactor Command.** + + Log into Keyfactor Command, toggle the _Locations_ dropdown, and click _Certificate Stores_. + +2. **Add a Certificate Store.** + + Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form. + + | Attribute | Description | + | --------- | ----------- | + | Category | Select "Akamai Certificate Provisioning Service" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `Akamai` certificates. Specifically, one with the `Akamai` capability. | + | access_token | The Akamai access_token for authentication. | + | client_token | The Akamai client_token for authentication. | + | client_secret | The Akamai client_secret for authentication. | + +
+ + + +#### Using kfutil CLI + +
Click to expand details + +1. **Generate a CSV template for the Akamai certificate store** + + ```shell + kfutil stores import generate-template --store-type-name Akamai --outpath Akamai.csv + ``` +2. **Populate the generated CSV file** + + Open the CSV file, and reference the table below to populate parameters for each **Attribute**. + + | Attribute | Description | + | --------- | ----------- | + | Category | Select "Akamai Certificate Provisioning Service" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | | + | Store Path | | + | Orchestrator | Select an approved orchestrator capable of managing `Akamai` certificates. Specifically, one with the `Akamai` capability. | + | Properties.access_token | The Akamai access_token for authentication. | + | Properties.client_token | The Akamai client_token for authentication. | + | Properties.client_secret | The Akamai client_secret for authentication. | + +3. **Import the CSV file to create the certificate stores** + + ```shell + kfutil stores import csv --store-type-name Akamai --file Akamai.csv + ``` + +
+ + +#### PAM Provider Eligible Fields +
Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator + +If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_. + + | Attribute | Description | + | --------- | ----------- | + | access_token | The Akamai access_token for authentication. | + | client_token | The Akamai client_token for authentication. | + | client_secret | The Akamai client_secret for authentication. | + +Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side. +> Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself. + +
+ + + +> The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). ---- @@ -122,24 +404,21 @@ Change any default values as needed, and enter an Enrollment ID if an existing e The SAN entry needs to be filled out with the DNS value you are using for the certificate's CN. If there are multiple DNS SANs, they should be separted with an ampersand. Example: `www.example01.com&www.example02.com` -**6. (Optional) Configure Renewal of Certificates using Expiration Alert Handler** +**6. (Optional) Configure Renewal of Certificates using a Workflow** Akamai does not support traditional certificate Renewal or one-click Renewal done in the Keyfactor Command platform. The Renewal process creates Certificates with outside keys which are not allowed to be imported into Akamai CPS. As a result, the Reenrollment Job must be used in order to renew existing certificates that reside on the Akamai system. Reenrollment is required as opposed to the Renewal process as it allows Akamai to generate the keys on their platform, which are used to create a certificate in Keyfactor. -Renewing existing certificates in Akamai means running a Reenrollment Job with the same Enrollment ID that was used for an existing Certificate Enrollment. This can be done manually through the Reenrollment process, but an automated process can also be configured using a Keyfactor Expiration Alert Handler. +Renewing existing certificates in Akamai means running a Reenrollment Job with the same Enrollment ID that was used for an existing Certificate Enrollment. This can be done manually through the Reenrollment prompt, but an automated process can also be configured using a Keyfactor Workflow. + +The Workflow should be configured to target a Keyfactor Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to `Production` or `Staging`. -The Expiration Alert Handler should be configured to target a Keyfactor Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to `Production` or `Staging`. +A sample workflow for ODKG / Reenrollment scheduling for renewals can be viewed in the [kf-workflow-samples repo](https://github.com/Keyfactor/kf-workflow-samples). When running the sample workflow, it will assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. -With the Expiration Alert Handler using the correct Collection, the Alert should be set to use the `ExpirationPowershell` Handler. A [sample Powershell Handler script](./akamai-cps-orchestrator/AkamaiExpirationHandler.ps1) is included in this repository. The sample script needs to be updated with the correct URL for API requests, and may need other changes as well, as it assumes that Default Credentials (Windows Auth) can be used to authenticate API requests to the Keyfactor instance. __This script needs to be placed in the Keyfactor Command installation's configured Extension Handler Path (default: {installation_dir}\ExtensionLibrary) location so that it can be run.__ -The `ExpirationPowershell` Event Handler configuration should be configured with the following values: +## License -| Parameter Name | Type | Value | -| - | - | - | -| Thumbprint | Special Text | Thumbprint | -| Template | Renewal Template | `desired renewal template` | -| CAConfiguration | Renewal Certificate Authority | `desired renewal CA` | -| ScriptName | PowerShell Script Name | AkamaiExpirationHandler.ps1 | +Apache License 2.0, see [LICENSE](LICENSE). -When running the sample script, it will assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. +## Related Integrations +See all [Keyfactor Universal Orchestrator extensions](https://github.com/orgs/Keyfactor/repositories?q=orchestrator). \ No newline at end of file diff --git a/docsource/content.md b/docsource/content.md index c60283e..cd51cc6 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -1,4 +1,3 @@ - ## Use Cases The Akamai CPS orchestrator extension implements the following capabilities: @@ -80,3 +79,8 @@ Renewing existing certificates in Akamai means running a Reenrollment Job with t The Workflow should be configured to target a Keyfactor Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to `Production` or `Staging`. A sample workflow for ODKG / Reenrollment scheduling for renewals can be viewed in the [kf-workflow-samples repo](https://github.com/Keyfactor/kf-workflow-samples). When running the sample workflow, it will assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. + +## Overview + +TODO Overview is a required section + From d6bbd7b48c5abe86d5c1118b986f16a4097f6edf Mon Sep 17 00:00:00 2001 From: Macey Dobrowsky Date: Mon, 19 May 2025 20:16:05 +0000 Subject: [PATCH 09/18] add overview section for docs --- docsource/content.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docsource/content.md b/docsource/content.md index c60283e..0a7def0 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -1,3 +1,8 @@ +## Overview + +Akamai Certificate Provisioning Service (CPS) provides ceritificate management and administration for certificates that are used within Akamai systems. This orchestrator interacts with Akamai CPS in order to Inventory certificates currently defined in that platform, and also supports adding new certificates or renewing existing ones. +New certificates are created in Akamai CPS via On Device Key Generation (ODKG aka Reenrollment). This means that certificates cannot be directly added to the CPS system, but instead need to have their keys generated in Akamai and then have a certificate issued for that keypair via a CSR. +This workflow is completely automated in the Akamai CPS Orchestrator. As a result, full contact information for the resulting certificates needs to be configured as this is required by Akamai. Additionally, the orchestrator automatically approves certificate deployments, even if there are warnings. This behavior should be understood and acknowledged if continuing to use the Akamai CPS Orchestrator. ## Use Cases From f0eff0fd1b40c5ea6a75f016fad269a2be128f97 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Mon, 19 May 2025 20:22:57 +0000 Subject: [PATCH 10/18] Update generated docs --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 346e6b1..eb1617b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ ## Overview -TODO Overview is a required section +Akamai Certificate Provisioning Service (CPS) provides ceritificate management and administration for certificates that are used within Akamai systems. This orchestrator interacts with Akamai CPS in order to Inventory certificates currently defined in that platform, and also supports adding new certificates or renewing existing ones. +New certificates are created in Akamai CPS via On Device Key Generation (ODKG aka Reenrollment). This means that certificates cannot be directly added to the CPS system, but instead need to have their keys generated in Akamai and then have a certificate issued for that keypair via a CSR. +This workflow is completely automated in the Akamai CPS Orchestrator. As a result, full contact information for the resulting certificates needs to be configured as this is required by Akamai. Additionally, the orchestrator automatically approves certificate deployments, even if there are warnings. This behavior should be understood and acknowledged if continuing to use the Akamai CPS Orchestrator. From 8a1ac74e55092e9a34bdece58076b1d23b966681 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 22 May 2025 18:02:21 -0700 Subject: [PATCH 11/18] chore(docs): Refactor docs and refresh screenshots --- README.md | 108 +++++------------- docsource/content.md | 94 ++++----------- .../Akamai-advanced-store-type-dialog.png | Bin 0 -> 42676 bytes .../images/Akamai-basic-store-type-dialog.png | Bin 0 -> 51443 bytes ...Akamai-custom-fields-store-type-dialog.png | Bin 0 -> 29882 bytes ...mai-entry-parameters-store-type-dialog.png | Bin 0 -> 56043 bytes 6 files changed, 48 insertions(+), 154 deletions(-) create mode 100644 docsource/images/Akamai-advanced-store-type-dialog.png create mode 100644 docsource/images/Akamai-basic-store-type-dialog.png create mode 100644 docsource/images/Akamai-custom-fields-store-type-dialog.png create mode 100644 docsource/images/Akamai-entry-parameters-store-type-dialog.png diff --git a/README.md b/README.md index eb1617b..297fa9d 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,16 @@ ## Overview -Akamai Certificate Provisioning Service (CPS) provides ceritificate management and administration for certificates that are used within Akamai systems. This orchestrator interacts with Akamai CPS in order to Inventory certificates currently defined in that platform, and also supports adding new certificates or renewing existing ones. -New certificates are created in Akamai CPS via On Device Key Generation (ODKG aka Reenrollment). This means that certificates cannot be directly added to the CPS system, but instead need to have their keys generated in Akamai and then have a certificate issued for that keypair via a CSR. -This workflow is completely automated in the Akamai CPS Orchestrator. As a result, full contact information for the resulting certificates needs to be configured as this is required by Akamai. Additionally, the orchestrator automatically approves certificate deployments, even if there are warnings. This behavior should be understood and acknowledged if continuing to use the Akamai CPS Orchestrator. +Akamai Certificate Provisioning Service (CPS) provides certificate management and administration for certificates that +are used within Akamai systems. This orchestrator interacts with Akamai CPS in order to Inventory certificates currently +defined in that platform, and also supports adding new certificates or renewing existing ones. +New certificates are created in Akamai CPS via On Device Key Generation (ODKG aka Reenrollment). This means that +certificates cannot be directly added to the CPS system, but instead need to have their keys generated in Akamai and +then have a certificate issued for that keypair via a CSR. +This workflow is completely automated in the Akamai CPS Orchestrator. As a result, full contact information for the +resulting certificates needs to be configured as this is required by Akamai. Additionally, the orchestrator automatically +approves certificate deployments, even if there are warnings. This behavior should be understood and acknowledged if +continuing to use the Akamai CPS Orchestrator. @@ -51,6 +58,19 @@ The Akamai Certificate Provisioning System (CPS) Universal Orchestrator extensio Before installing the Akamai Certificate Provisioning System (CPS) Universal Orchestrator extension, we recommend that you install [kfutil](https://github.com/Keyfactor/kfutil). Kfutil is a command-line tool that simplifies the process of creating store types, installing extensions, and instantiating certificate stores in Keyfactor Command. +### Akamai Platform Configuration + +In the Akamai instance, an API Credential needs to be configured and used to provide authentication for the Keyfactor +Orchestrator extension to function. To do this, navigate to `Account Admin` -> `Identity & access`. Clicking +`Create API client`, select a user whose permissions should be used to access and manage certificates. This user should +already have the needed permissions to access CPS. The access of the API Client can be restricted to just the CPS APIs, +but the API Client should have `READ-WRITE` access. + +With the API Client created, a new credential should be generated by clicking `Create credential`. The contents of the +credential should be downloaded or saved temporarily to use for configuring the Keyfactor Certificate Store. Afterward, +it should be deleted as the credential file serves as authentication for accessing APIs, and should be treated as a +plaintext password and not saved long-term. + ## Akamai Certificate Store Type @@ -58,8 +78,6 @@ To use the Akamai Certificate Provisioning System (CPS) Universal Orchestrator e -
Click to expand details - @@ -195,8 +213,6 @@ the Keyfactor Command Portal ![Akamai Entry Parameters Tab](docsource/images/Akamai-entry-parameters-store-type-dialog.png) - -
## Installation @@ -334,6 +350,10 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov +
+ + + ## Use Cases The Akamai CPS orchestrator extension implements the following capabilities: @@ -342,80 +362,6 @@ The Akamai CPS orchestrator extension implements the following capabilities: 1. No Enrollment Id provided - create a new Enrollment and certificate in Akamai 2. Existing Enrollment Id provided - renew an existing certificate in Akamai and update the Enrollment -## Keyfactor Version Supported - -The Akamai CPS orchestrator extension requires a Keyfactor Platform version of 9.10 or greater to support encrypted certificate store parameters for authentication. - -## Akamai Platform Configuration - -In the Akamai instance, an API Credential needs to be configured and used to provide authentication for the Keyfactor Orchestrator extension to function. To do this, navigate to `Account Admin` -> `Identity & access`. Clicking `Create API client`, select a user whose permissions should be used to access and manage certificates. This user should already have the needed permissions to access CPS. The access of the API Client can be restricted to just the CPS APIs, but the API Client should have `READ-WRITE` access. - -With the API Client created, a new credential should be generated by clicking `Create credential`. The contents of the credential should be downloaded or saved temporarily to use for configuring the Keyfactor Certificate Store. Afterwards, it should be deleted as the credential file serves as authentication for accessing APIs, and should be treated as a plaintext password and not saved long-term. - -## Akamai Orchestrator Extension Configuration - -**1a. Use `kfutil` to create the entire store type definition** - -Using `kfutil` to create the store type is the preferred method to create the Akamai store type. It will create all of the needed Custom Fields and Entry parameters (of which there are many). - -Creating the store can be done by running the following `kfutil` command: - -``` -kfutil store-types create -n Akamai -``` - -If using `kfutil`, skip steps __1b__ and __2__ and go to step __3__ to set the default values of the Entry Parameters. - -**1b. Manually Create the New Certificate Store Type for the Akamai orchestrator extension** - -In Keyfactor Command create a new Certificate Store Type similar to the one below by clicking Settings (the gear icon in the top right) => Certificate Store Types => Add: - -![](images/store-type-basic.png) -![](images/store-type-advanced.png) - -Custom fields and entry parameters will be added after the store is created. This is required as there are many entry parameters. - -**2. Add Custom Fields and Entry Paramaters** - -_Only requried if manually adding the certificate store._ -To add the needed Custom Fields and Entry Parameters, [run the script](akamai-cps-orchestrator/jobproperties.sql) on the Keyfactor database to generate all the fields and parameters needed. - -**3. Set default values of Entry Parameters** - -The Entry Parameters are used during Enrollment creation in Akamai CPS to provide contact information and associate new certificates with the correct Contract in Akamai. After adding the parameters, re-open the Certificate Store Type configuration and set the default values. - -The Contract ID should be set to the default contract to be used for new Enrollments. All of the address information should be filled out with default expected values, as they are required fields for **each** enrollment created and should not be entered manually unless they need to be overwritten for a specific Enrollment in Akamai. -The Tech contact information should be your Akamai company contact, and needs to have an Akamai email address and should have Akamai as the organization name. - -**4. Create a new Akamai Certificate Store** - -After the Certificate Store Type has been configured, a new Akamai Certificate Store can be created. When creating the store, the credentials generated in the Akamai platform for the API Client will be used. - -| Certificate Store parameter | Akamai credential value | -|-|-| -| Client Machine | `host` | -| Access Token | `access_token` | -| Client Token | `client_token` | -| Client Secret | `client_secret` | - -**5. (Optional) Enroll a new certificate in Akamai** - -Adding new certificates to Akamai requires generating a key in Akamai CPS via the Reenrollment process in Keyfactor. To start this process, go to the Certificate Store that the certicate should be added to. Select the certificate store, and click the `Reenrollment` button to bring up the reenrollment dialog. - -Change any default values as needed, and enter an Enrollment ID if an existing enrollment needs to be updated instead of creating a new Enrollment. This is different from the Slot ID - the Enrollment ID is found by clicking on an Active certificate in Akamai CPS, and looking at the `ID` value. -The SAN entry needs to be filled out with the DNS value you are using for the certificate's CN. If there are multiple DNS SANs, they should be separted with an ampersand. Example: `www.example01.com&www.example02.com` - - -**6. (Optional) Configure Renewal of Certificates using a Workflow** - -Akamai does not support traditional certificate Renewal or one-click Renewal done in the Keyfactor Command platform. The Renewal process creates Certificates with outside keys which are not allowed to be imported into Akamai CPS. As a result, the Reenrollment Job must be used in order to renew existing certificates that reside on the Akamai system. Reenrollment is required as opposed to the Renewal process as it allows Akamai to generate the keys on their platform, which are used to create a certificate in Keyfactor. - -Renewing existing certificates in Akamai means running a Reenrollment Job with the same Enrollment ID that was used for an existing Certificate Enrollment. This can be done manually through the Reenrollment prompt, but an automated process can also be configured using a Keyfactor Workflow. - -The Workflow should be configured to target a Keyfactor Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to `Production` or `Staging`. - -A sample workflow for ODKG / Reenrollment scheduling for renewals can be viewed in the [kf-workflow-samples repo](https://github.com/Keyfactor/kf-workflow-samples). When running the sample workflow, it will assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. - ## License diff --git a/docsource/content.md b/docsource/content.md index 0a7def0..4bd675c 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -1,8 +1,15 @@ ## Overview -Akamai Certificate Provisioning Service (CPS) provides ceritificate management and administration for certificates that are used within Akamai systems. This orchestrator interacts with Akamai CPS in order to Inventory certificates currently defined in that platform, and also supports adding new certificates or renewing existing ones. -New certificates are created in Akamai CPS via On Device Key Generation (ODKG aka Reenrollment). This means that certificates cannot be directly added to the CPS system, but instead need to have their keys generated in Akamai and then have a certificate issued for that keypair via a CSR. -This workflow is completely automated in the Akamai CPS Orchestrator. As a result, full contact information for the resulting certificates needs to be configured as this is required by Akamai. Additionally, the orchestrator automatically approves certificate deployments, even if there are warnings. This behavior should be understood and acknowledged if continuing to use the Akamai CPS Orchestrator. +Akamai Certificate Provisioning Service (CPS) provides certificate management and administration for certificates that +are used within Akamai systems. This orchestrator interacts with Akamai CPS in order to Inventory certificates currently +defined in that platform, and also supports adding new certificates or renewing existing ones. +New certificates are created in Akamai CPS via On Device Key Generation (ODKG aka Reenrollment). This means that +certificates cannot be directly added to the CPS system, but instead need to have their keys generated in Akamai and +then have a certificate issued for that keypair via a CSR. +This workflow is completely automated in the Akamai CPS Orchestrator. As a result, full contact information for the +resulting certificates needs to be configured as this is required by Akamai. Additionally, the orchestrator automatically +approves certificate deployments, even if there are warnings. This behavior should be understood and acknowledged if +continuing to use the Akamai CPS Orchestrator. ## Use Cases @@ -12,76 +19,17 @@ The Akamai CPS orchestrator extension implements the following capabilities: 1. No Enrollment Id provided - create a new Enrollment and certificate in Akamai 2. Existing Enrollment Id provided - renew an existing certificate in Akamai and update the Enrollment -## Keyfactor Version Supported +## Requirements -The Akamai CPS orchestrator extension requires a Keyfactor Platform version of 9.10 or greater to support encrypted certificate store parameters for authentication. +### Akamai Platform Configuration -## Akamai Platform Configuration +In the Akamai instance, an API Credential needs to be configured and used to provide authentication for the Keyfactor +Orchestrator extension to function. To do this, navigate to `Account Admin` -> `Identity & access`. Clicking +`Create API client`, select a user whose permissions should be used to access and manage certificates. This user should +already have the needed permissions to access CPS. The access of the API Client can be restricted to just the CPS APIs, +but the API Client should have `READ-WRITE` access. -In the Akamai instance, an API Credential needs to be configured and used to provide authentication for the Keyfactor Orchestrator extension to function. To do this, navigate to `Account Admin` -> `Identity & access`. Clicking `Create API client`, select a user whose permissions should be used to access and manage certificates. This user should already have the needed permissions to access CPS. The access of the API Client can be restricted to just the CPS APIs, but the API Client should have `READ-WRITE` access. - -With the API Client created, a new credential should be generated by clicking `Create credential`. The contents of the credential should be downloaded or saved temporarily to use for configuring the Keyfactor Certificate Store. Afterwards, it should be deleted as the credential file serves as authentication for accessing APIs, and should be treated as a plaintext password and not saved long-term. - -## Akamai Orchestrator Extension Configuration - -**1a. Use `kfutil` to create the entire store type definition** - -Using `kfutil` to create the store type is the preferred method to create the Akamai store type. It will create all of the needed Custom Fields and Entry parameters (of which there are many). - -Creating the store can be done by running the following `kfutil` command: - -``` -kfutil store-types create -n Akamai -``` - -If using `kfutil`, skip steps __1b__ and __2__ and go to step __3__ to set the default values of the Entry Parameters. - -**1b. Manually Create the New Certificate Store Type for the Akamai orchestrator extension** - -In Keyfactor Command create a new Certificate Store Type similar to the one below by clicking Settings (the gear icon in the top right) => Certificate Store Types => Add: - -![](images/store-type-basic.png) -![](images/store-type-advanced.png) - -Custom fields and entry parameters will be added after the store is created. This is required as there are many entry parameters. - -**2. Add Custom Fields and Entry Paramaters** - -_Only requried if manually adding the certificate store._ -To add the needed Custom Fields and Entry Parameters, [run the script](akamai-cps-orchestrator/jobproperties.sql) on the Keyfactor database to generate all the fields and parameters needed. - -**3. Set default values of Entry Parameters** - -The Entry Parameters are used during Enrollment creation in Akamai CPS to provide contact information and associate new certificates with the correct Contract in Akamai. After adding the parameters, re-open the Certificate Store Type configuration and set the default values. - -The Contract ID should be set to the default contract to be used for new Enrollments. All of the address information should be filled out with default expected values, as they are required fields for **each** enrollment created and should not be entered manually unless they need to be overwritten for a specific Enrollment in Akamai. -The Tech contact information should be your Akamai company contact, and needs to have an Akamai email address and should have Akamai as the organization name. - -**4. Create a new Akamai Certificate Store** - -After the Certificate Store Type has been configured, a new Akamai Certificate Store can be created. When creating the store, the credentials generated in the Akamai platform for the API Client will be used. - -| Certificate Store parameter | Akamai credential value | -|-|-| -| Client Machine | `host` | -| Access Token | `access_token` | -| Client Token | `client_token` | -| Client Secret | `client_secret` | - -**5. (Optional) Enroll a new certificate in Akamai** - -Adding new certificates to Akamai requires generating a key in Akamai CPS via the Reenrollment process in Keyfactor. To start this process, go to the Certificate Store that the certicate should be added to. Select the certificate store, and click the `Reenrollment` button to bring up the reenrollment dialog. - -Change any default values as needed, and enter an Enrollment ID if an existing enrollment needs to be updated instead of creating a new Enrollment. This is different from the Slot ID - the Enrollment ID is found by clicking on an Active certificate in Akamai CPS, and looking at the `ID` value. -The SAN entry needs to be filled out with the DNS value you are using for the certificate's CN. If there are multiple DNS SANs, they should be separted with an ampersand. Example: `www.example01.com&www.example02.com` - - -**6. (Optional) Configure Renewal of Certificates using a Workflow** - -Akamai does not support traditional certificate Renewal or one-click Renewal done in the Keyfactor Command platform. The Renewal process creates Certificates with outside keys which are not allowed to be imported into Akamai CPS. As a result, the Reenrollment Job must be used in order to renew existing certificates that reside on the Akamai system. Reenrollment is required as opposed to the Renewal process as it allows Akamai to generate the keys on their platform, which are used to create a certificate in Keyfactor. - -Renewing existing certificates in Akamai means running a Reenrollment Job with the same Enrollment ID that was used for an existing Certificate Enrollment. This can be done manually through the Reenrollment prompt, but an automated process can also be configured using a Keyfactor Workflow. - -The Workflow should be configured to target a Keyfactor Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to `Production` or `Staging`. - -A sample workflow for ODKG / Reenrollment scheduling for renewals can be viewed in the [kf-workflow-samples repo](https://github.com/Keyfactor/kf-workflow-samples). When running the sample workflow, it will assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. +With the API Client created, a new credential should be generated by clicking `Create credential`. The contents of the +credential should be downloaded or saved temporarily to use for configuring the Keyfactor Certificate Store. Afterward, +it should be deleted as the credential file serves as authentication for accessing APIs, and should be treated as a +plaintext password and not saved long-term. \ No newline at end of file diff --git a/docsource/images/Akamai-advanced-store-type-dialog.png b/docsource/images/Akamai-advanced-store-type-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..916866b09c1ba31f4c58af2047dc69eddbbcb22b GIT binary patch literal 42676 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�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 literal 0 HcmV?d00001 diff --git a/docsource/images/Akamai-basic-store-type-dialog.png b/docsource/images/Akamai-basic-store-type-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..d50d36da34aeb9f6e4057c0ea4e08abe316a392b GIT binary patch literal 51443 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⋙|bvB+Ny9Ny82VjPh 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 nB9QXk&@0fw31JkK51b=}u9VK0}(q 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%0Awk08w|l#u9>dnya>RQ)&t!~j z@=r6ieUgIT&I`_L(W3Kh)=R}xXf{Et3x*C4pH22=D0Lt;V^fa8PS%ZG7_7euFZad&}~Q}Ft8PyJQgWNzn5vEHMQntvf!g$ z&-Mijn5eF+D}u%b-At&{k)W-oruOt^nwg17Rqr+IyM-&uP9F7OOK7FbRs;oV&<+CI z4!#u*0UW6qG>?&ZgCiq~jL%T2n~IPoC$ou*Ur0RjKK!6O&-SKao7$7t^%ed!t=iK4 z3JRO$+++FgN=kZBaT1(39@5flm#x+n3aZ>^+`3iP+3r;VOZ}n$igoC^WovIQg)s8K zu$Vm1CulCuW@i-?F!4J>T#1$Z|BeY(a~dpX0cb0cM|!>};(InuP+!TJnd=-$=9A)_ZU{Qr2Y& zpl7+}rU6uqP z|DAB7=fW(WN7v-iC9}n6FOGR@<0m&(28LBumgJXH=&yQ>-vxyQhb^@e&xHEO7B!!+ zahCT9qJ4ykZX_fCOAmC|=(=+3rK~T|ha9kSGc#uzi(|9$Raj@ag}~;z3jBgU!z50~ zo=?&^OF=>ulzA1j=hu5xjDB5%L^0<^4hsxI9VFqb=Kv>u_OxL~Sw@^|qe_gV; zL!YfTdOkh*-ggT(X<$^52YPF}{J)HiCEEfGC%TvVvDEdAjYU3wyxeGu0UBQ>9gK{O zP-<27BbB|-=ClX8DVTw?IBD(1nRcHgE-kQ(BF$$nZ<(Y zJRBPzPf_KD?0IgK=<&upzVSQeo*;3qx5Vy;_6x^V2-l!>pM;nVT!lk>dATPyTs#;> zsK|Z~`Yx6uj#!~0aJnTWZDMWi?979VVP-8h#6YP`!|Lrb(}>@~=RCV_+hmI4#!oLD zE9w6?q#H~r`5-)yI*JA()DxJDln#@0MurfkCGpLPJRAei{>VSXjEza)6d^ ze4L+#0s}LU#SS$+tu(4WjJ?A6mCAm?kA1|^=6A^WhiVQ`G0oLz+5Txg$0`Q@|4h&E zPdHqb2P6|vGuT9sR>WMdm>3z(;NDCSACMc#c0nY9VW%3&n(zWFK&^m)mY2E(9d&j5 z!oo8+gmJt^iC;HDnF_}cKpA?v-_a~9o#{S_IOxro(7s{AYIwX9H}@6HpK*BM`Ok(Z zu=$9|?(XVB-FBSqH1tA14R~D}n`ubIAuV-+5(-UB_}PpM4QY)p74M#DwZ6t9g<&FoUH%SCCffObbXi;@s!BwE_(A!@fIs|#@ZV!y|#ZI8{;(Sn87%}=PPbunm{rZo z=g)I7|4A9GjX6U6vYBe2m26LCeMJT8Y*=@=Fm%J%7!lxJm7f1^V1Aq4H|r%L1Nc3m zxXRH+d&2%hdZJ~7&j2x}XlKHnm+>(6{Xq2{0~<6%EV%Dn6J2JXV`undZ81?3(3AZ| z!|~eXr-(E~w$MMzIF@RelmCQ><3A_~46@k&t|XXWzWWS1Ca3|@(hl^!VS0Gb_&xS4 zpQb?&MfRU=7z2*jNGt(g12N9;T7vWl_XG-w?}G;i2|e-~|JlSJkWWA-40V$d=cbJt zcU_w!8H2hTsUDLD!j%GieF1_|WV)oyPhOkLJ|ZrG7BHP(#8zT2@|;wuc!O1}0kIwA)jty8?{}VmhX9 z@PTQ=srv0;KG4-chsxE|I?;O*OV z6zebxD;3cY8$EwXsVn189SE-}EtLtg#+HSC`qisw;kaPzT4CY_%JZ%;^l^Mi)JMZ6 zBD;ortK!k4ZjSGMDLHWb5Tk3#cj#eY0a%>Avslg}!AkH-zP)@v*z|**=!1Lr-V9h1 z6Da{upuoVUi7$|7gpKtPB}r+`gVWVdUZ#6W_hPa5O8MWp10vo-x)ee3X(-?Wi0yZE&MTA)$UE&@Ssq_ObPF7!;1vY1Nv!;cTG>EJ z3HqBaL@_ffi}{HK#*=(H-B40WlGM zvRGN&&;`~M_~<7)HcO0C)I*Lq*D@kg&~)YJ!I?scwOr^>Rt?!K;Cu6a`Ywg&a{$;) zpD=?8qre0GiNrIv{b1+9v=SNZ;{y9&;EFj1CK%b$3ax7s$QU}<>8e2Wz4)tnDI{)S z)Ag)>ly7W0h|HQrUt$jE7oY8u>eaVSCpJs&#gA-3+!7L+Ut`q;xCstFzvxN-mL-84 zZ3qnwB%|~&@Wdc>acRjrH$Y#gvBO%97dKZKJ%^q@TJg_=UQ!%& zFgyJBQ3jV1Sjb84Y``;|Tv}9=eY)j%xkRC#I*YBtafmZ5Vv|UgQ4B#B+w0xFhNSo| z)at(&e$nP%6#t$#|24%QeVC{Oey8}Qgm=bu68Z`op;Frj-D(z_nihM{j9k{dBefeI z8&ZRq4-pZmsIYvu%jPZdcl2rK6cEM4?}ny-60g@+PyK@&gMMd8BGC?*Sc{JYeSD0>=?0)M`DJ6m&WxM`>yS0MH2*^6JFGxQx+){*vQm}%FQW_o zR|*kq=zuQ15g1+;#%=t2H8Bgal3!!X6F)?EfPIEk4V?qT^B{2w{(4X821uU~vxKQ= zGc{$$cIGVn*6@^&x|YaAqxU#Y!gc=} z&F#QIu6>l#u~2ugmi1nHrW^^80Agj(nnynXQ=)045l^0FJo zO$21;xpcpy6c;o#`)qziL!nfIK*JiM0t!_449P6exJL|Y<{e`KM8kvC1BZamg4^jg z^#*oGFgVY%{N_o$XQ0a@>-#qLgfkh4+i_$SXupm#u(F)1Lbdn-UO!(#&HXJ z(F4}#dOn71H#{8aO1E%&!x)U3_Fq#A4EoA$akgB9u##}|PsT?@MZw=(thG25wSuN9 zwqBF~xkxMkj3E#@d<^#XUvESejQn9T0kbJ2XW#_U+Bgt?M~M?=Go1GQ?reVs?qlHD zo#`-A$7T?Nsf(D3HGgjhs}QD=fyD(C~`QF!6sr!!UK|%b7944)1o}ehJOtxC^t%< z90-7?&f-r{_eCE!(+XrkqUdn;#tm`Wz2NOW>eX;s6g~i_`Vtez%I@4o?t_TX;#}<} zum+Wac_>3*I20qYHJX~#O-04->oMG6L=)(*CjRs;=&Es9{!WVR(++Ke10w^gkZB8# z2MunRqHg#0jt;u^W=mdh_Mqu^vO*(_hWrPqzOP?TnjP;V(ZZ|{BDn!caA@slX+1O& zc6$tQNLF?>)YW)kWg-^U5=}A8f5SnpQTOZy^+Swssk+?f1_uEH!hs`TxyHbViJQ~{ z9|=;FG9OB7&zN`Ur$W^up<9U2bw*z3(vh!U-#dU(2=esv2UXDWva!i!qaz{Jqkfk36nn72AWeSoQKnK@^Cf&OhQjytjU49H?tFRkml90LgbxXQ% z;DN$rPRtbtWWbbD-1C2tPsc861}wOIy_JJFf{+l}YL^z+ga`?>o!m*+t|zEuenk&- zJM?dUlwMe<8^+S$cDnrU^dZiezIYY5u~#!jXa#RY0*Z9aP}6QV)Rd1@av zH+1GOMXZ}om$0lZ9x!M=kkIY8!BhHh2_5L9+de)J1#Q~S1Il@BW+pQ=m9=FT0|WIn zbN^mS{+eee*y&O~^c);mlLhO$1J3h2YZ}g_E_O>LGE!Q})~!phUyGHJd;h|@a}`F2d3ju5 z(!>d1Y2Ze~I=#}7H}#r3V9~Nic(?cN_fOvTT)Vw)C)?9CI_ox9xXax5Z5^%NX@%{= z498>EZ1ZQ7QebDMRZ*RUt=XGm|7E6nGfKrf4hEN1*>00N&{)vy48O6QU~3mzO}jIC za%#C$LtRH*r%Syh8<9C&P>W|3+eKZZ^_69^b+-wD_8lz^`8)KqU~ZV1m01Fwh<=IV zI4R$caZb7QKW`b}dis7$v%VR`)p>iuCq5+P`{x%gp6k*wH|TVZX`>28SM6a~j2I3v zVt}SfYhM?{{m|;gob!b{<9isCp{OZj{Sd8l>colRFZ-lZnk}k_Gr^qU>vPK8zWYX3 zJ?GYqn1vmCH`R*wR4s0NCi5|kFE0|4kTiQnmZ<~I4O3gvwCvLeP1>{b$Q2B@Hjn@O z_J^F*^XTY{SXF2e!<-6_Gs?X#9r+>u4s-E>P&9QPKh{-gU7M@GD(Do;u`++6q*A%5 zEppagcxHwxEnQyo`?k~MZrS_?4m8%*@`{SuBW4heG0Kt3HEy(95hWmTD1OsA`Nn|i zA6k!YCy*9*BEhu}!8k)hZB5(OxD}pY71Y9~+M=Pcs0sDTQPC0gRVnm>z^ZHCXq9Bw zCo?KDJE)#d5c!{MQxFFr*T7re92;gq{1fGItq1!v7Fz?_Z@*N{vk3VpELxs7x`w1D zvf@taq|`~p0Rkx3hY9}D!K}()4z8kP0n%STaDR5`H%JxJ**zbl$~|c5!M(S)8IZbX z8$LnP`rn+WnHS#A8klY#W!E~TWA3&4kk0Pn6kkB({Cm;{vn-y{EGZW_y}Tlgo4&j< z&K0ZV;@D#(W2t)KLLySsSPHyaJwr(?{iVs`zV6xu&hm4W_%*k4gZ+a#_07!yL5z%y z9Xbz#f-V|-A8Ab&6BCn_m$y$|3vu6z6LI?PT@9V0?~(`!=hU?gYi z`4=xgZ0>*@!`#5&06-KtROIRI*LX=IEz8ybnJqBG;Y>^*hY5KGM>@`2RCM)c5guzfQ-YQotu(eMtvD&i>4YweR~Z@i z%MH46XS#PNdLhe-666EkW#cpwzp`d(>grsz$hS!}kz2A8V`QW437G)+3UHdBr_#cI&V03>pTG{9Pr@Q$CT?SnXcRLp{Y#gE zBO(;THn4)#iK#~8qj4>2!-o$aLMaCX1w$n08=S`2&^EB*$)lsA)7HZV=W`QP;Mufc zj7m4jW{6$#G8<>rLF7ytQ$G->$!V0G!~|%aEM&0mC*P(UO+A{IVq=we^7=X=znii^K zSiwOYAF}FOrz31Lq}sj~188NNaI2f;B2-Ym>&YVwNWF3z(vA$tjO8w?nx0d8Ki{;Y zMu=zi`}c4<&H85LM*DFhmT z!vTyy8z%s_AY?s$<$b6!utq_=EFw>u$QeSQTJf#OQ!iqOMk&T&=K|-9e)|R+C9;DR zvz;JOhu}4U`G_N8mf+k+AY3I8DFr4EXSi~t#94N__3PGU>?3;I;LLyxCGAEZ2u;G4 zw26`uNm(E~sHv!QbF2p-9XiyVf)t$*^wy=8=PzBn*oy!y(9ZB(zWTOIM_&QUB05-j zAO``$xUMc2q+;2;C`ZOCF5zM36lgYOWcKXdjg*$>z-GX5a1QRKaR7Rbi-UuSZ};vV zJUb{XXilk)OiDnQRpz_J$#L-LEs3k*LZO)89i{udG1wt@BvUOZfa7;905)xU28JEm zwlzqtUwh2lJlSRp&x1p111o5>r3Dx?(esDe083}|S)>wq*f?Tyf-sEOG#0o zIH9aOJ2O-J?j5?B=u)tHU~&p(WQ7~ePEB36x6e*X>j1Kg5H^KL5mr)yxqMy&Q9SWU z-@oU>T&(8 zHZ)Kk;?Bv;^rqa&M(5k{$;vqOJd9dUN8suZ^QnCgGmqmMz+;X9E-R2r#R`vKDJnCt zLfk2kJ2*r<)~!7zCszTQF>ie0;8``bMv%o=F~36LkSZi<-cdFC*XI+&eu6P zC*&m)u|HsT8>G;tsA)lAL6pP6QU2kB?YsMX$*CBJQkQOJ$909uXg~^eV$T4)4u@{H zH$LP?My+`(2>p}BXCM{1^>|Jl-?)$wHvX>s8^N5I1J>ErmeIM{ZqP!eZrJovKW=XH z4KQX8do$LxjdlX2A5~O%89_l2Yy={L-J_oVFrtK|eK2OVGp`(t%|UCsW>Lq1PTX6dkX~wMt?zbBSL)wX>!nV*9niVmzw(f2sC-m9S@bD6f zRJ<}8^8lLD>z)QLJq=brm!ew>>FeQ7;QRQz=rcG5NSr#sz~;q$$iza0$|QH!sA#*HT7G*gzP{>JHvs?8I03GYI#7-)A1qaqM-{va0|23`O328;I8bP^0il$mi`SQ zHXVfYpU3?kSP3neSxE8+)s3tkx~?a~BO=n;%_J<3;-Gv_?j?EReQ48$g9p=V!Y*^g zoQA#)>lUOz%GsoV++)C*A`c<sp$>or?+(DRQ&xb%gf>S1p4w@CWK^A^}D6=ffq8c#%pqp80p4Af_$ zQI{4DQHvO(auOZ3W22e}tPj-z#8l@5@rIV49!#$6CDEuYz0>eIaK8rbo&Cfhp zVuTFk6&MPl6A4^|;qpjl!M6!DLi<=FMwtKXukz7NQqeKy;^cI6ayq$kMokT=j3<<3Tuv(sj9t|GasOa#{TQ`O(^~j4kuZVd_X1A~;?|j&n37jRQ64~I|5M0r zKiKo-(e%2Z@7NKKvo&B5BS}$mzQ-L$78(}s=S<;eMj-^pRNbKKbc6is@KY2!Ma<5_ zS%O@~dFR5%k3S)V4^bDW%y7Ms5rH;{!k|aC#Pc!c&&c;nJ%1RN0Zle!s%M3LsSmSh zwjSz0*7Ag5S!HFlI#3R0R2@3!)1alWW8r0Elh6#gyy?EnQ%yy7I@{*Wjb~SfparrB z8pz^Ag)hKG?6*%3!a{i>U1QJ$(Kt3OAswyCmi^+9-sh?;w!?iUhH;0*_a{Bq@Q}A@ zwL8=bapKvtGhYU5Qm}g>@G~@YyNMze5!$wk9I$gG!F+2720GfEGi0t)|?IR(eJs@r?|K3BKDB=Qom+I z1P&L8j*gU?3+ft47>DU2k#U^MF+bA0N(FO;a7{$wx=v3jznyZ-Jco~?JGW>2H=w7C z$a{MR8I}^T4??eoy6wxrz%VijUca`nXNnfRnmsVB3A_Tu))AdeLu{U9qd}pI{aou= z4xPGW-_iY{xi#wTduEd=4Ou3VPlb3Hw4Jdj0)>ekI&_d|2*{}_Ae3hG5g!fXi9>vY zmR9?oQju6jpx2gy0uBOX~7g%0=S^r({kkS5J>9O3i)~z46j>FmM zX20uTMa2rw-jF>+38YtX=fP&Y!fJqV&1$l7G3Boxp4^@D2dc5T)q}~)kBVrY#(EI? zem4z#0_7bP-;Ov89qRN;7Je0MCTyFz^S4o(TO>uCNMN2&-00ixx^<&VzEL>+F1yF4 z9+o~H?qiI)kC!SqhOrljkh?j(OLvDRRle}Xo%dgwUIAvk$2C~IMl&)n%Kn(T`WEj} zt83S;*qb9*$+s#=udc7BhlYu%D|6%PygUT>+xNP&1rh!~EQ~bYU*XwUc$o~B&xl1c5IE95n__j#HS1cEX z#D&=Ph~mci6RqliYXyl7@`ZsduxB=|eE;Lh1z9tVu7>Izb{7aBbR1fZi->u)X-5`j zGUFKsPf7ap{=Pmiv4m*oTi5tHfTmffc02)bunA?O+z* zn1H4Thpq<=tUqW&tIwu`h(f3@dO;~XMANUl+z{`Qk;)jn6_5<0s0Aa24sPBY$AYzv z;uBAVO9|2J*YDq^IsUkG;a(!Lpat>XkW7i`=D-@>o}LI^dBA)XWg3T59m;RmFwi~TGs2`MS#`88f%WVen!dGP|5vKK`WKn>d6 z_E5AyZ(x4>csYVHp**g^0Tlb}8F+S_$VHQr-%!#}qG62(6C-2c&u@7!#iCu2-flMB zTGBs>XqcN&#KVIJssfpKkPka1z?6Wp)(Zk=8X7cd5PgbQLRo8IWEA@NF`tt>>B3{8 zn9OrsS@!2IFDtW&b_NgvZwdsr5Px)c8@EhHF4Sq+MNqrN0VWMGY&}4pOpXj_v^DM8 zUR}f;=y4ZWWq>h=p+QF)4q559L{}*|*u)5T2AU~&M=W*FxB zh4SX1yM6O!Ax={KACT8Xp=|t~(O343L2B-B+rjhyt`adU3Lr|S+S*eR-%r4#fO%Sr zC`wO;@?iUcU;s!R=nHXE;@5hP298nZ^NwK?LP>qn(HuRw9(-%rTE9(y@q7OKYG)=c zU?YS{eA?W+OXK|f$OXeP*dmQ@7mbJ$Td)NJb99W6^urPxJJuRw7zod{@& zY$goV5PD!i02Bf~RXCxsieS4Lup?X^H4(x|Q>;9w`5}CPo(EtmKKOu%;n2FHBE;~M zD-jnkF$VxTBV!u9#Izlg5OGbXkEh||fG33t5{X1mhoH7a7EF3oU;U3<_Qtg9MZ<42OhB9H%6QKD_p_*2IGhNB|@9v+G+YbC5v;Sv>@fWQRc zEcR`iS$P?mQo>FJ;DyhE@)-^XTiv&?bwMDHjb37JFiqp^E}g3rJtceO18_hf`vzDI z#T{@^q~){@+KT${j)n;f2_a&?s(%s%xR#;mq5F*^swfzU5`_gbU;c+BijMK0FHt@= zEcOCj4C^G|(`lSL*XU=0)h=-!@d5DYeF8a2SZJ{S{CKI87t2MC8v~zVtdIc&!3gAN z@zrCY?KeDO_{haZheeOXbRq@|5FD(hiM{N%$UdYhjE|2)C5&()5M$7@125wOsxH`K zeO9Xn;77ESpcMpVgLcT!I*y@I&?7eHCr)@@{&*j+ZwX8{pe#^)LG&W5pt; zq@qIfQ6#MYItWAO8#_BRq@3q|8OH(SmC)8eASo;;xKrqgD(WkIooZ_LM(u{Jp*Nnq zESvHn{$)BUCA2K#T=(tWi#9AfC#Q0R5N1i$R8^S_)kimG;&4gH4>@_zrZ>P6_3R1< z?lxqP!^vLp$9Ms0X3Fg`9KlYb!qohas*PMVrUni=|$ex`n0Tg^}mO!|*zZBRK?vau#gs z#e0(j%=5e@5L3;wJ#gni&O?W^G&HiOE>IQQFl9345@V>GcY>MtPM8I&2B(PRc+7=j zV0egac=$O0MElxKmkk~Y>>~*bu}PpdQA|o1l=|TPrK%2F;dN>WN0g(SWUm%C%t7oBU@-no0 ziCZ0oNXkr*y)3$soqMJ<_2gGIue7|1V5))BA0|;0sO9m-w_dfd;DfGlSkvvZtqj2! zqunW8QIRPmj2+K5BU3Lt{n^WNC)L$yZLeVbR&uaYf#tEzAVEilJMS;uyEnDXI`V$I zI54e*_?*kYeUOlH@I55oNXDmaG9%j5FCQWTsfUJ`*!C9>PUs43K- z?2x*tHj&ySWEa1rbIN7OcNEo$^YP|2J!9~w+FZY0U0bV@Zsebye$aivibQ`M%9rr> z?}yix1ZDJFFK>@$8zGsPMXU+LXn4%p94C2Vi6=(Uy-@aHTTu(q4DnxBwM@84QvRwz zeibCQb_#aZY~>$VgZiS&6lpW3ZB7ab53dT6?l%40{$(TYO6H}kr~8{%MTlGbiY2&gQ8=Zd+WO&46KTZVzww+9MH((56XxsOp@$#zWS(_{#}XA{izn_U?lv`RMve(@@{&i-YYM;-lew?KEFa;9Et>xztej9E1gJ zPA|H4ZL0d$FCVXjfx)_%YZHX!W{-l-^7->ummHag9us({(aoZnY+TgTEsubV%^BEZ zo~%HH5}dzl_wMi{RA5gL$T#U*Trj7x16ybK2QEM+L1EArQERF2=_x6pMH2?^v{bDC zo7s|{SrVLG>jv~(;B+5TGF_E(%w(B^W2S`R8LOmi>Q-|Va0X7Q77kRBI9}5m^!fN>mXvsFC$TDbv@J2FXrKsB<+R9cI)WVH#f>@h(qr^IDZ- zULs3$QB>N=ll3>|ZC}x_O5DFcF;#FXl%?Sv z_;pWSs}{WQe9`GMXJ$;BhN7NH53?TS4nk*~nAL`a+N`GeM+5M)+c|ijLH+v+Ng268 zshAZnUnT~c@8;{{Gjg`iskPn%WKJB5oL@<;vAV`~9<6E9juwig$>z;DNtbS&MCoB0 z4~P!@2%Q?G+l$R;IZhO{%ufc&P2Z8k?_BMH?U(+277Oi4j-vHh@4xO{?)?Kl#56gp z=Tb6sbWmB;vM^CeDgOBJomZ17yKg!=1YgkfRi$A^1XOQ zkj!7T8OsDvGGseFc^2kw_@@;W>}vZGd^&T2htxTyf8-yZNk8{FpGVxa83^Otz%HsE zX*vg?!$1FAU$j!P;h?1|;dncD?Gkda$%ZL+RRFrCS)Da<2`NxEHa`*Z$-zVJ)OS*{ zWfJj}ytm|k_`m@$!+PY2lsIe1Jf&>WjuCt4#E0J#Rn7Ee#@Y>HyY=++YHe%bsG#AH zBP+#JS;P_y8AysGBRBWZY5jWgpGgSs=6e!E1Ru-o$5P5TCL~wN2R3iK@-M;&uOjI_ zC~oY}`>4kh4IDIRy!Z*qDjZ?q;lh~1o(j%9CIu8(&5G#)HjF_6W{p0Dc>!oZ64o5+{Zy&v_w&RU(PP(?Lu1mFLu7&Zb0vKGG1a^doU~}$R zqm36+PMna~re?oGBUKe24U8LYy^@H9}ZW#M}z zMuoWlj}YV;bUD73bP%S8vEs!Crm`N2x8zQFxx?DE-r48Q^2&E{#*({;nr{OOO(EAo zu!>`KDkoakZmCKeIJ|C3O&Ps~qqSU{aqQVc6s5u`rLsp{Y|ptZXdbH_m=?hYC3n%U z7i&o$WJ;~yCe^RsM8C%1DHj`PY;V^%8ur8gIJGi%^XsKIsYrxCF@PiK=L*5CUEG^o0|g=Qv};LxHqc?641|FQH6 z=uFv`M;=v&ZW$A7FQ?4-x(d1D zytofu_;9YLWuD1-R@J`gJ(=}-8IWal=cSVI*Q?#M@kT{!Jpr`dpEA&e*V>~3_^{}tMK^3 z9teheZx{S3K8zT|G_9L4L}%vA@A10B(SZ?i+y?_U3>Z)+aj{Mf!QUvLb(g;R2p5*M zpWo~`&G?O|#Pt)1T8YcdW{!Npt-=%(eR&>O_wU z=!oa%&`$sqa`*NJ79dZ-GRDfQT=TWsYlDi`1Ojq#FLgYd%30Pp<;~mcwK&iz2u+V3 zVY_WZHpYs!_cLec;qjcn*>Y74JN`9wvg!7<5FFdrq$J2wAbNn{M$5LzoEs{PoKs5g zon_1ck>aeN1n{6n>3*ZH0gX~P%*gOrjWA4gzo3BwtL=nf!GF_s-YRuGQ#Vti$VOwscj8`6MM-?|zu?A1%o z$=G1tQt<5Kbbs7^M9qAEc8O}mt{-b1&YT5d4kwOM8VmvI4XFxpi2HOa58a^14c4l9 ze;!1)!UdtbSbrfL3#jB{{cD7YlMVk)xadJ-hbqi+b3isoJ>#9A7h_>!Kw6zmu9upE zrca&r?Z;0z;FyjHgTDz{2$r6)Wo+pMR+dKY-!I^jx^XOTJIgr?@ds!NbPIb;?o}Bx zt5G}W?rqQEh#-V*`AgHINs)`gB z;}r`G46Y4Rd1G(}2<1;k){igbk5gNzZM^_xo9C9f<}>5XRl=;7-nm*l4*B_&#qC6 z&&hdB#mk%kZ;w2JE7+KswnS1j>$If}ITmWx&nCk-@P0T&fMZMw?8`Yo!OT|SjYoy< znm)SC1Pz#7DyR`dL9+7)`zi;x&~{U=`yJFuPdrZPNmVY;(Q$5aV&ola5hQ6a2sxXj zX2PqXy)0J^#gFm+aTIyn#&eaG=fCb1!S5um+ppTSZ<(Qa0zaie+^$u*T|3KCwfd`& z`Y-=a<%U7^8?S{b@q&}n=)gq*iZ-%KJ~A+&sc&q7n1ZEhc5T z=jd%3)BOFRynGnMso%!OQ}6ntoGfu!x^(GC1%<|u0ll`mCWjs%@2qWSsJ3Fm72>UX>v1MZrnj&o$P{>InMX^p;qxJLM24%MHXx?){thChCZbV@0SH^vZ@MVMbS0oo2qNcaPX^mrxh!&Qgcv^1RlBnAp^M@Ls5y zN*@2bX(sI;Nn1x!QJd8_ZKGKLCbzPZm;|;pSmf4yiJLGupGG)uO1Kn!kSs0%R(~co zLMpjDJ{T9*Eg3k`BWX~*k#4}-?`d1??Cf$CQrRMs914hp(I9;p>I!N=-|cA5euVqj zng0u2d$=?hE>mRA@^LClSbdh2)ym#zPH8Jk%WIEXZEbAu&jBuTs-U-Z)BqTJ1}kv& zs?P_y9#x#mOeFDAfLYMuIg3Tr5*K+}O%09x!&I8JaclSjwi5XReaynfnk|1k#fEz6 zX4-~&;D#>r+Wr4S*ZFs5oM#ZDXdyg6!oey`XrLLd=XN5*BSc~3zyfM+SI{}XP9d^| zplwhG(mG8=*NcigH*aR-y}xNDkUUcCv101^tr)G4%l8;q>0aln8$MN7_V*@B`!D_u zD_0{xwp$2X)%I+nKR`FZgEUZKqV>;5=51T29aR`%3X1$RMLv8#zAQBpvxR*oeav~yck{Ygbeq(z!9*FzPAY+-95?P) zhW~M5Rf5!t)*uyx@N#1cqQQKdsCr{FQIQkG*p9rYa$w)S+bWfifw= z98b(n;Qd9mxf7DmGi=ykg9g1@M)a|~qmlIrSJ$WQQ3Sx1TWE>=#pWvE*VHCG8vb== znkdA_YUKp8TQ^QL4ZZhi-5J}R2AKixd=Z%LG-!F->8=?g&_ZEG$WmB8wEjEzHzc^^ z;nBYei;U?r@#~92v5;}+2$9$g8aFzOO$9D@FUCq<(sme+4x8=&I$ZkmrAx=uEvFd4 zoW{)!tpDjJRqG&b{WZm}s0whRC29htG#fKaJri5wjCVNMS^P^S6)8cq2~?;^i90=b z=NQ6(n8Oki31}k;haQYC*dkmq3vPY%VGDaK79imQaea3Kc@yeGNPgBdjM8OP2$e`{ zFC~npq}};wD)|ZPS_yxvKYuRym6S|sUE~*NL`6h0t+tES_B`<1`=ho& zAt50fmM{lG-a^L^W}+CPwXk=y7B3}P2i=Ka86IeMhWJ-2s-m{T;5|AF;$KKkcXj?U7mJ@l2N=5LXV_!AQhc!+=>%aKgfGIE5S#^#3Q);ipik|c&l>_2 zoSd4vXyaZ%h6vV@7uYk%!5t;YC+;`-59Qy4{E>Oy7Jsb#W0Oq!GKu8XDQkbzp7E?C zG70jhw(N*m)A@%4A=?H_-~g{144QhheQ<1E#SH+t#Mp&KM%Nr=%X-Vo$SmzB`?u}eP7&1~Pkfo<{~WZG7w-N{oSmb* zV0EjIvk{PBpVvL>XOwgRL3F%C9|Jis3jwd-zG5`cj3E+cj(RutzlM~6RH^6V$o$rH z{mDok65=XaMVe!(iBX=-amih`)Qvxsm-D(0mywZ@*IfKhI_>ze$kh%GW7hViM`zM2 zd$`=0Xe3hbXj<)4mxH4a%1RD4?*=V%;w3b9#wGnP2V+>i|HrO-+j!bsLtQImryo2Pv#1LFX+F zg*Z#IIx`W5vFOC0?t6bmmxUfqWpw$AjG3H)BN~{Z1M2fNQ4g^jr{NDdvkPZU4ZS(D zYOPSLEnKjGG&EvlDz02{0F(zW;|>9fWY#7Yg6b9Q|MbH0I5~N<#f#h8+atSufV_Zrk+EG68KXMm+ zCLS|(tnngtKt%;ea((AEX!>s$Xf>V}lKWYI{*Y^BSy>ANH1dQShbY)CPENj)B7BF# zq(g?MEJJpg_I9jz`}WdVx{L`t)*iZYZD0S#K#*QE?~7|y-$y19FqgJh(mu((qWq52 zdM89~E~PsaH?FnLV6-`2r=BiBbthvV>s`6^gO6&KTn}sA6R;E_nm30hu(hQHw2vMt zCwFg&{Fz!eUo}WE%FN;PPdvhK&KND~da?i8kl3WtI?d0VnjV`utPRP2FiDWo**iES z#Kc%rXaq5MY^-cKn-i32f{Q)8;Dz=kC`7fMo!MhPGDC~Pd5$~@2B??+e*IMv#D(M2 z(>r>)c-Qhj@8W$XV5mR;*lx?fnmtFYcO5sLQukN7`0#d*p|tP5BQ$#aL+`yrm!0nx zcQuyF{^mUWdGfB*2`8}i?WLQt;$Z_mLy-yGzq zC9&w%&}!Yjea1%_S=<}4Y!U;z$T_6rZZ4Jcj43H8(dwMZ8>tt^zjH?_c{n{kX5XG_ z{sHimcgXVOTH#gIyUQ?8aeJAMvJjj@dt84?`X^x%@t3{-U?Bge0a5rb9U(M_vLi;c zd8p;2(;?>WqWN*=h)OTZ=`WlF(g;a}tBr}$Cu4GgZZQ*J7y32hYlJ4a8Sw#f0q#fCuz4zDa6 z8fhPij4M_w`hpT^XQyb(M!^Ou)$~!-rnN%)eizm1y)Qvb4ObSZR4TroCzO=tMI+M%-m*q*w@;9sV(mAxSv)96*|f2_a$qs}srl!m4z%^5Rdp{0>Q zaxH!@e%U_V+!QyJsi_O=(uAPa7e35N_F>cFNd*4mgPw%AEV$4ZVz`o`6PXG z$?!Aua8}W);K$jZ6%{ga*0j}ErFdGHegHQ~7KwKK>MM09LNdjfZ-7KguF$x1+jftd zYiAV>K1WDg!@&>KTtFl|B4|wSl6$-Tsg?BzL6I(uo!zgxyYBMaX`;R1RU#UkwStko z)3{;qyP?u8_%Quhhm7Y6-v898P`lmW@yAhQqBl)?c*^o*<22lECmNywOm`Hm;VLAl z7#sthRH;LQ>F)>%GW7F1Phvy*JKkN4o3B`2V*CZ>^Ti@rGMWu*;Wl);%7A4OwqE%9 z6PvXFUu2>U?YEuhTpTK_BxC5_wP%ll#+u!6aief10guv{S*l{ujAr(O!r@Jm9Wvzd zy-ksmZGAzTd}U3Z^$QuH{);(5I%=Q2&VqnI?r2;6mS>%>Uq^@({ zsyV%s^2YJIZQHiCVw!M7miUoD36Pw-LkHt-7i_p81%(}MUv;G2v(w5+} zD7zQlIz1o$i?%kh*;%4k_g<$<*5HNn=ZQ@iH!L{ugBaZuQxSyd7%_yu@v>!ttcrCH z8a#=C@1|(STE}xt+;47Hkdbj%zg`wOCJQjYrOMHVZb6 zv+MD^cue#he#%mnYXw$3?Egs>u$kjpA1H$-^%txUS7w1+EHyMtuIpTvk(!fZ#o&RN zrYQJm!?U&Contis>UlbaS8{82sL>dh_&K)0>GIPYjJ${D-`kXvn&K)xEkxb^=8OOH zf;IawIxne9_h0zt^qWIsQ?Datd|Fm!EAE+|srjTa!?#!cHfml;ZRx8c_W~$hy?*bB z|D8epFaAM=G2^9&0^jS121g$BpozeV!#exsOyh4$E^;CX5@h~n4Y6yS*>BKJrw}O9 qXa$CSshfSUn9$ Date: Thu, 22 May 2025 19:22:14 -0700 Subject: [PATCH 12/18] chore(docs): Add `extension mechanics` and `notes` sections --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ docsource/akamai.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 297fa9d..3661d60 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,21 @@ To use the Akamai Certificate Provisioning System (CPS) Universal Orchestrator e +> :warning: +> If creating the Certificate Store Type manually, be aware that you will need to save the store-type configuration without entering the custom fields and entry parameters. This is due to a UI limitation. After saving the store type, you will need to run [this SQL script](akamai-cps-orchestrator/jobproperties.sql) on the Keyfactor database to generate all the fields and parameters needed for Akamai CPS. +#### Notes: +> :note: +> To set the `default` values for the Entry Parameters, you will need to re-open the Certificate Store Type configuration after saving and running [this SQL script](akamai-cps-orchestrator/jobproperties.sql). This is due to a UI limitation. + +> :note: +> The `Contract ID` should be set to the default contract to be used for new Enrollments. + +> :note: +> All address information should be filled out with default expected values, as they are required fields for **each** enrollment created and should not be entered manually unless they need to be overwritten for a specific Enrollment in Akamai. + +> :note: +> The Tech contact information should be your Akamai company contact. It must be an Akamai email address (`@akamai.com`). The contact's organization name must be set to `Akamai`. #### Supported Operations @@ -348,6 +362,33 @@ Please refer to the **Universal Orchestrator (remote)** usage section ([PAM prov > The content in this section can be supplemented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). +### Extension Mechanics + +Adding new certificates to Akamai requires generating a key in Akamai CPS via the Reenrollment process in Keyfactor. +To start this process, go to the Certificate Store that the certificate should be added to. Select the certificate +store, and click the `Reenrollment` button to bring up the reenrollment dialog. + +Change any `default` values as needed, and enter an `Enrollment ID` if an existing enrollment needs to be updated instead +of creating a new Enrollment. This is different from the `Slot ID` - the `Enrollment ID` is found by clicking on an +Active certificate in Akamai CPS, and looking at the `ID` value. The SAN entry needs to be filled out with the DNS value +you are using for the certificate's CN. If there are multiple DNS SANs, they should be separated with an ampersand (`&`). +Example: `www.example01.com&www.example02.com` + +#### Configure Renewal of Certificates using a Workflow +Akamai does not support traditional certificate Renewal or one-click Renewal done in the Keyfactor Command platform. +The Renewal process creates Certificates with outside keys which are not allowed to be imported into Akamai CPS. As a +result, the Reenrollment Job must be used in order to renew existing certificates that reside on the Akamai system. +Reenrollment is required as opposed to the Renewal process as it allows Akamai to generate the keys on their platform, +which are used to create a certificate in Keyfactor. +Renewing existing certificates in Akamai means running a Reenrollment Job with the same `Enrollment ID` that was used +for an existing Certificate Enrollment. This can be done manually through the Reenrollment prompt, but an automated +process can also be configured using a Keyfactor Workflow. The Workflow should be configured to target a Keyfactor +Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query +targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to +`Production` or `Staging`. A sample workflow for ODKG / Reenrollment scheduling for renewals can be viewed in the +[kf-workflow-samples repo](https://github.com/Keyfactor/kf-workflow-samples). When running the sample workflow, it will +assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. + diff --git a/docsource/akamai.md b/docsource/akamai.md index 0aa5d83..45e9908 100644 --- a/docsource/akamai.md +++ b/docsource/akamai.md @@ -1 +1,44 @@ -## Overview \ No newline at end of file +## Overview + +> :warning: +> If creating the Certificate Store Type manually, be aware that you will need to save the store-type configuration without entering the custom fields and entry parameters. This is due to a UI limitation. After saving the store type, you will need to run [this SQL script](akamai-cps-orchestrator/jobproperties.sql) on the Keyfactor database to generate all the fields and parameters needed for Akamai CPS. + +### Notes: +> :note: +> To set the `default` values for the Entry Parameters, you will need to re-open the Certificate Store Type configuration after saving and running [this SQL script](akamai-cps-orchestrator/jobproperties.sql). This is due to a UI limitation. + +> :note: +> The `Contract ID` should be set to the default contract to be used for new Enrollments. + +> :note: +> All address information should be filled out with default expected values, as they are required fields for **each** enrollment created and should not be entered manually unless they need to be overwritten for a specific Enrollment in Akamai. + +> :note: +> The Tech contact information should be your Akamai company contact. It must be an Akamai email address (`@akamai.com`). The contact's organization name must be set to `Akamai`. + +## Extension Mechanics + +Adding new certificates to Akamai requires generating a key in Akamai CPS via the Reenrollment process in Keyfactor. +To start this process, go to the Certificate Store that the certificate should be added to. Select the certificate +store, and click the `Reenrollment` button to bring up the reenrollment dialog. + +Change any `default` values as needed, and enter an `Enrollment ID` if an existing enrollment needs to be updated instead +of creating a new Enrollment. This is different from the `Slot ID` - the `Enrollment ID` is found by clicking on an +Active certificate in Akamai CPS, and looking at the `ID` value. The SAN entry needs to be filled out with the DNS value +you are using for the certificate's CN. If there are multiple DNS SANs, they should be separated with an ampersand (`&`). +Example: `www.example01.com&www.example02.com` + +### Configure Renewal of Certificates using a Workflow +Akamai does not support traditional certificate Renewal or one-click Renewal done in the Keyfactor Command platform. +The Renewal process creates Certificates with outside keys which are not allowed to be imported into Akamai CPS. As a +result, the Reenrollment Job must be used in order to renew existing certificates that reside on the Akamai system. +Reenrollment is required as opposed to the Renewal process as it allows Akamai to generate the keys on their platform, +which are used to create a certificate in Keyfactor. +Renewing existing certificates in Akamai means running a Reenrollment Job with the same `Enrollment ID` that was used +for an existing Certificate Enrollment. This can be done manually through the Reenrollment prompt, but an automated +process can also be configured using a Keyfactor Workflow. The Workflow should be configured to target a Keyfactor +Collection of certificates that includes the Akamai certificates that need to be renewed. This can be done with a query +targeting the `CertStoreFQDN` containing `Akamai` and can be further restricted with the `CertStorePath` being equal to +`Production` or `Staging`. A sample workflow for ODKG / Reenrollment scheduling for renewals can be viewed in the +[kf-workflow-samples repo](https://github.com/Keyfactor/kf-workflow-samples). When running the sample workflow, it will +assume that all certs passed to the script should schedule a Reenrollment job with their existing parameters in Akamai. \ No newline at end of file From cdd038a5f900640f6b032db11b57d619cb002ba1 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 22 May 2025 19:28:48 -0700 Subject: [PATCH 13/18] chore(docs): Note styling --- README.md | 12 ++++++------ docsource/akamai.md | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3661d60..e16500a 100644 --- a/README.md +++ b/README.md @@ -78,20 +78,20 @@ To use the Akamai Certificate Provisioning System (CPS) Universal Orchestrator e -> :warning: +#### Notes: +> [!WARNING] > If creating the Certificate Store Type manually, be aware that you will need to save the store-type configuration without entering the custom fields and entry parameters. This is due to a UI limitation. After saving the store type, you will need to run [this SQL script](akamai-cps-orchestrator/jobproperties.sql) on the Keyfactor database to generate all the fields and parameters needed for Akamai CPS. -#### Notes: -> :note: +> [!IMPORTANT] > To set the `default` values for the Entry Parameters, you will need to re-open the Certificate Store Type configuration after saving and running [this SQL script](akamai-cps-orchestrator/jobproperties.sql). This is due to a UI limitation. -> :note: +> [!IMPORTANT] > The `Contract ID` should be set to the default contract to be used for new Enrollments. -> :note: +> [!IMPORTANT] > All address information should be filled out with default expected values, as they are required fields for **each** enrollment created and should not be entered manually unless they need to be overwritten for a specific Enrollment in Akamai. -> :note: +> [!IMPORTANT] > The Tech contact information should be your Akamai company contact. It must be an Akamai email address (`@akamai.com`). The contact's organization name must be set to `Akamai`. diff --git a/docsource/akamai.md b/docsource/akamai.md index 45e9908..b4db9df 100644 --- a/docsource/akamai.md +++ b/docsource/akamai.md @@ -1,19 +1,19 @@ ## Overview -> :warning: +### Notes: +> [!WARNING] > If creating the Certificate Store Type manually, be aware that you will need to save the store-type configuration without entering the custom fields and entry parameters. This is due to a UI limitation. After saving the store type, you will need to run [this SQL script](akamai-cps-orchestrator/jobproperties.sql) on the Keyfactor database to generate all the fields and parameters needed for Akamai CPS. -### Notes: -> :note: +> [!IMPORTANT] > To set the `default` values for the Entry Parameters, you will need to re-open the Certificate Store Type configuration after saving and running [this SQL script](akamai-cps-orchestrator/jobproperties.sql). This is due to a UI limitation. -> :note: +> [!IMPORTANT] > The `Contract ID` should be set to the default contract to be used for new Enrollments. -> :note: +> [!IMPORTANT] > All address information should be filled out with default expected values, as they are required fields for **each** enrollment created and should not be entered manually unless they need to be overwritten for a specific Enrollment in Akamai. -> :note: +> [!IMPORTANT] > The Tech contact information should be your Akamai company contact. It must be an Akamai email address (`@akamai.com`). The contact's organization name must be set to `Akamai`. ## Extension Mechanics From 580721e81b736dfe6add4c6dd619fd8674bb31ad Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 22 May 2025 19:30:27 -0700 Subject: [PATCH 14/18] chore(docs): Remove `notes` header --- docsource/akamai.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docsource/akamai.md b/docsource/akamai.md index b4db9df..98ce7e9 100644 --- a/docsource/akamai.md +++ b/docsource/akamai.md @@ -1,6 +1,5 @@ ## Overview -### Notes: > [!WARNING] > If creating the Certificate Store Type manually, be aware that you will need to save the store-type configuration without entering the custom fields and entry parameters. This is due to a UI limitation. After saving the store type, you will need to run [this SQL script](akamai-cps-orchestrator/jobproperties.sql) on the Keyfactor database to generate all the fields and parameters needed for Akamai CPS. From c9779f2d12e0a311dfadd65f4f66ee9bd63066db Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Fri, 23 May 2025 02:32:10 +0000 Subject: [PATCH 15/18] Update generated docs --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index e16500a..a9235c9 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,6 @@ To use the Akamai Certificate Provisioning System (CPS) Universal Orchestrator e -#### Notes: > [!WARNING] > If creating the Certificate Store Type manually, be aware that you will need to save the store-type configuration without entering the custom fields and entry parameters. This is due to a UI limitation. After saving the store type, you will need to run [this SQL script](akamai-cps-orchestrator/jobproperties.sql) on the Keyfactor database to generate all the fields and parameters needed for Akamai CPS. From ddecc304a1435cf5d281a9eb257dff16821b592b Mon Sep 17 00:00:00 2001 From: "Matthew H. Irby" Date: Fri, 23 May 2025 12:18:44 -0400 Subject: [PATCH 16/18] chore(docs): Update Client Machine and Store Path descriptions --- docsource/content.md | 3 ++- integration-manifest.json | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docsource/content.md b/docsource/content.md index 4bd675c..2e22e22 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -30,6 +30,7 @@ already have the needed permissions to access CPS. The access of the API Client but the API Client should have `READ-WRITE` access. With the API Client created, a new credential should be generated by clicking `Create credential`. The contents of the -credential should be downloaded or saved temporarily to use for configuring the Keyfactor Certificate Store. Afterward, +credential should be downloaded or saved temporarily to use for configuring the Keyfactor Certificate Store. The credentials file +will contain the `client_secret`, `host`, `access_token`, and `client_token` for the API client. Afterward, it should be deleted as the credential file serves as authentication for accessing APIs, and should be treated as a plaintext password and not saved long-term. \ No newline at end of file diff --git a/integration-manifest.json b/integration-manifest.json index 51bc801..76a0f44 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -539,7 +539,9 @@ "ServerRequired": false, "PowerShell": false, "BlueprintAllowed": false, - "CustomAliasAllowed": "Forbidden" + "CustomAliasAllowed": "Forbidden", + "ClientMachineDescription": "The Client Machine field is the Akamai REST API URL. This should be equal to the the \"host\" value from the API credentials file.", + "StorePathDescription": "The Akamai network the certificate will be managed from. Value can be either \"Production\" or \"Staging\"." } ] } From cbee9b07a388d9ff940da712f885ce43f6e2cb2d Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Fri, 23 May 2025 16:20:26 +0000 Subject: [PATCH 17/18] Update generated docs --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a9235c9..c9d8003 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,8 @@ already have the needed permissions to access CPS. The access of the API Client but the API Client should have `READ-WRITE` access. With the API Client created, a new credential should be generated by clicking `Create credential`. The contents of the -credential should be downloaded or saved temporarily to use for configuring the Keyfactor Certificate Store. Afterward, +credential should be downloaded or saved temporarily to use for configuring the Keyfactor Certificate Store. The credentials file +will contain the `client_secret`, `host`, `access_token`, and `client_token` for the API client. Afterward, it should be deleted as the credential file serves as authentication for accessing APIs, and should be treated as a plaintext password and not saved long-term. @@ -296,8 +297,8 @@ the Keyfactor Command Portal | --------- | ----------- | | Category | Select "Akamai Certificate Provisioning Service" or the customized certificate store name from the previous step. | | Container | Optional container to associate certificate store with. | - | Client Machine | | - | Store Path | | + | Client Machine | The Client Machine field is the Akamai REST API URL. This should be equal to the the "host" value from the API credentials file. | + | Store Path | The Akamai network the certificate will be managed from. Value can be either "Production" or "Staging". | | Orchestrator | Select an approved orchestrator capable of managing `Akamai` certificates. Specifically, one with the `Akamai` capability. | | access_token | The Akamai access_token for authentication. | | client_token | The Akamai client_token for authentication. | @@ -324,8 +325,8 @@ the Keyfactor Command Portal | --------- | ----------- | | Category | Select "Akamai Certificate Provisioning Service" or the customized certificate store name from the previous step. | | Container | Optional container to associate certificate store with. | - | Client Machine | | - | Store Path | | + | Client Machine | The Client Machine field is the Akamai REST API URL. This should be equal to the the "host" value from the API credentials file. | + | Store Path | The Akamai network the certificate will be managed from. Value can be either "Production" or "Staging". | | Orchestrator | Select an approved orchestrator capable of managing `Akamai` certificates. Specifically, one with the `Akamai` capability. | | Properties.access_token | The Akamai access_token for authentication. | | Properties.client_token | The Akamai client_token for authentication. | From 254c125cbc33585a832036a25afda0716e989469 Mon Sep 17 00:00:00 2001 From: Macey <11599974+doebrowsk@users.noreply.github.com> Date: Wed, 28 May 2025 11:36:49 -0400 Subject: [PATCH 18/18] set workflow version to 3.2.0 for correct build process --- .github/workflows/keyfactor-starter-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml index d92bd57..bd11ad0 100644 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ b/.github/workflows/keyfactor-starter-workflow.yml @@ -11,7 +11,7 @@ on: jobs: call-starter-workflow: - uses: keyfactor/actions/.github/workflows/starter.yml@v3 + uses: keyfactor/actions/.github/workflows/starter.yml@3.2.0 secrets: token: ${{ secrets.V2BUILDTOKEN}} APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}