Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 17 additions & 30 deletions .github/workflows/keyfactor-starter-workflow.yml
Original file line number Diff line number Diff line change
@@ -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@3.2.0
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 }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
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.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
Expand Down
428 changes: 348 additions & 80 deletions README.md

Large diffs are not rendered by default.

134 changes: 134 additions & 0 deletions TestConsole/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// 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;

namespace TestConsole;

class Program
{
private static ILogger<Program> _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<string, string> 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<Program>();

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");
}
}
19 changes: 19 additions & 0 deletions TestConsole/TestConsole.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\akamai-cps-orchestrator\akamai-cps-orchestrator.csproj" />
<ProjectReference Include="..\http-interface\http-interface.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.4" />
</ItemGroup>

</Project>
17 changes: 17 additions & 0 deletions akamai-cps-orchestrator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -21,11 +27,22 @@ 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
EndGlobalSection
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
Loading
Loading