Skip to content

Commit 12c9cec

Browse files
authored
Merge pull request #2 from Keyfactor/release-1.0
Merge 1.0.0 to main
2 parents 974e1b2 + 37a4b2b commit 12c9cec

16 files changed

+1437
-31
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Keyfactor Bootstrap Workflow
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
types: [opened, closed, synchronize, edited, reopened]
7+
push:
8+
create:
9+
branches:
10+
- 'release-*.*'
11+
12+
jobs:
13+
call-starter-workflow:
14+
uses: keyfactor/actions/.github/workflows/starter.yml@v3
15+
secrets:
16+
token: ${{ secrets.V2BUILDTOKEN}}
17+
APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
18+
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
19+
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
20+
scan_token: ${{ secrets.SAST_TOKEN }}

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
- 1.0.0
2+
- First production release of the GCP CAS AnyCA Gateway REST plugin that implements:
3+
* CA Sync:
4+
* Download all certificates issued by connected Enterprise tier CAs in GCP CAS (full sync).
5+
* Download all certificates issued by connected Enterprise tier CAs in GCP CAS issued after a specified time (incremental sync).
6+
* Certificate enrollment for all published GoDaddy Certificate SKUs:
7+
* Support certificate enrollment (new keys/certificate).
8+
* Certificate revocation:
9+
* Request revocation of a previously issued certificate.

GCPCAS.Tests/GCPCAS.Tests.csproj

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
<IsTestProject>true</IsTestProject>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="coverlet.collector" Version="6.0.0" />
14+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
15+
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.8" />
16+
<PackageReference Include="xunit" Version="2.5.3" />
17+
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<Using Include="Xunit" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<ProjectReference Include="..\GCPCAS\GCPCAS.csproj" />
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
30+
</ItemGroup>
31+
32+
</Project>

GCPCAS.Tests/GCPCASClient.cs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright 2024 Keyfactor
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Collections.Concurrent;
16+
using Keyfactor.Extensions.CAPlugin.GCPCAS.Client;
17+
using Keyfactor.AnyGateway.Extensions;
18+
using Keyfactor.Logging;
19+
using Microsoft.Extensions.Logging;
20+
using NLog.Extensions.Logging;
21+
using System.Security.Cryptography;
22+
using System.Security.Cryptography.X509Certificates;
23+
using Keyfactor.PKI.Enums.EJBCA;
24+
using Keyfactor.Extensions.CAPlugin.GCPCAS;
25+
using Google.Cloud.Security.PrivateCA.V1;
26+
27+
namespace Keyfactor.Extensions.CAPlugin.GCPCASTests;
28+
29+
public class ClientTests
30+
{
31+
ILogger _logger { get; set; }
32+
33+
public ClientTests()
34+
{
35+
ConfigureLogging();
36+
37+
_logger = LogHandler.GetClassLogger<ClientTests>();
38+
}
39+
40+
[IntegrationTestingFact]
41+
public void GCPCASClient_Integration_GetTemplates_ReturnSuccess()
42+
{
43+
// Arrange
44+
IntegrationTestingFact env = new();
45+
46+
IGCPCASClient client = new GCPCASClient(env.LocationId, env.ProjectId, env.CAPool, env.CAId);
47+
client.Enable();
48+
49+
// Act
50+
List<string> templates = client.GetTemplates();
51+
// There is never a case where there are zero templates - there's always the default "no template"
52+
Assert.NotEmpty(templates);
53+
_logger.LogInformation($"Found {templates.Count} templates: {string.Join(", ", templates)}");
54+
}
55+
56+
[IntegrationTestingFact]
57+
public void GCPCASClient_Integration_DownloadAllCertificates_ReturnSuccess()
58+
{
59+
// Arrange
60+
IntegrationTestingFact env = new();
61+
62+
IGCPCASClient client = new GCPCASClient(env.LocationId, env.ProjectId, env.CAPool, env.CAId);
63+
client.Enable();
64+
65+
BlockingCollection<AnyCAPluginCertificate> certificates = new();
66+
67+
// Act
68+
int numberOfDownloadedCerts = client.DownloadAllIssuedCertificates(certificates, CancellationToken.None).Result;
69+
_logger.LogInformation($"Number of downloaded certificates: {numberOfDownloadedCerts}");
70+
}
71+
72+
[IntegrationTestingFact]
73+
public void GCPCASClient_Integration_DownloadAllCertificatesAfter_ReturnSuccess()
74+
{
75+
// Arrange
76+
IntegrationTestingFact env = new();
77+
78+
IGCPCASClient client = new GCPCASClient(env.LocationId, env.ProjectId, env.CAPool, env.CAId);
79+
client.Enable();
80+
81+
BlockingCollection<AnyCAPluginCertificate> certificates = new();
82+
83+
DateTime after = DateTime.UtcNow.AddDays(-100);
84+
85+
// Act
86+
int numberOfDownloadedCerts = client.DownloadAllIssuedCertificates(certificates, CancellationToken.None, after).Result;
87+
_logger.LogInformation($"Number of downloaded certificates: {numberOfDownloadedCerts}");
88+
}
89+
90+
[IntegrationTestingFact]
91+
public void GCPCASClient_Integration_EnrollGetRevoke_ReturnSuccess()
92+
{
93+
// Arrange
94+
IntegrationTestingFact env = new();
95+
96+
GCPCASClient client = new GCPCASClient(env.LocationId, env.ProjectId, env.CAPool, env.CAId);
97+
client.Enable();
98+
99+
// Create a CSR
100+
string subject = "CN=Test Subject";
101+
string csrString = GenerateCSR(subject);
102+
103+
EnrollmentProductInfo productInfo = new EnrollmentProductInfo
104+
{
105+
ProductID = GCPCASPluginConfig.NoTemplateName,
106+
ProductParameters = new Dictionary<string, string>
107+
{
108+
{ GCPCASPluginConfig.EnrollmentParametersConstants.CertificateLifetimeDays, "200" }
109+
}
110+
};
111+
ICreateCertificateRequestBuilder builder = new CreateCertificateRequestBuilder()
112+
.WithCsr(csrString)
113+
.WithEnrollmentProductInfo(productInfo);
114+
115+
// Act
116+
_logger.LogInformation($"Enrolling test certificate with DN {subject} using GCP CAS CA called {env.CAId}");
117+
EnrollmentResult enrollResult = client.Enroll(builder, CancellationToken.None).Result;
118+
119+
// Assert
120+
Assert.Equal(enrollResult.Status, (int)EndEntityStatus.GENERATED);
121+
Assert.NotNull(enrollResult.CARequestID);
122+
_logger.LogInformation($"Certificate enrollment validated successfully");
123+
124+
// Act
125+
_logger.LogInformation($"Downloading test certificate identified as {enrollResult.CARequestID} from GCP CAS CA called {env.CAId}");
126+
AnyCAPluginCertificate downloadResult = client.DownloadCertificate(enrollResult.CARequestID).Result;
127+
128+
// Assert
129+
Assert.Equal(enrollResult.Status, downloadResult.Status);
130+
Assert.Equal(enrollResult.CARequestID, downloadResult.CARequestID);
131+
Assert.Equal(enrollResult.Certificate, downloadResult.Certificate);
132+
_logger.LogInformation($"Verified that the downloaded certificate identified as {downloadResult.CARequestID} is the same as the initially enrolled certificate");
133+
134+
// Act
135+
_logger.LogInformation($"Revoking test certificate identified as {enrollResult.CARequestID} issued by GCP CAS CA called {env.CAId}");
136+
client.RevokeCertificate(enrollResult.CARequestID, RevocationReason.CessationOfOperation).Wait();
137+
138+
_logger.LogInformation($"Downloading test certificate identified as {enrollResult.CARequestID} from GCP CAS CA called {env.CAId}");
139+
downloadResult = client.DownloadCertificate(enrollResult.CARequestID).Result;
140+
141+
// Assert
142+
Assert.Equal(enrollResult.CARequestID, downloadResult.CARequestID);
143+
Assert.Equal(enrollResult.Certificate, downloadResult.Certificate);
144+
Assert.Equal(downloadResult.Status, (int)EndEntityStatus.REVOKED);
145+
// Cecession of Operation should be reason 5
146+
Assert.Equal(downloadResult.RevocationReason, 5);
147+
148+
_logger.LogInformation("GCPCASClient_Integration_EnrollGetRevoke_ReturnSuccess was successful");
149+
}
150+
151+
static void ConfigureLogging()
152+
{
153+
var config = new NLog.Config.LoggingConfiguration();
154+
155+
// Targets where to log to: File and Console
156+
var logconsole = new NLog.Targets.ConsoleTarget("logconsole");
157+
logconsole.Layout = @"${date:format=HH\:mm\:ss} ${logger} [${level}] - ${message}";
158+
159+
// Rules for mapping loggers to targets
160+
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logconsole);
161+
162+
// Apply config
163+
NLog.LogManager.Configuration = config;
164+
165+
LogHandler.Factory = LoggerFactory.Create(builder =>
166+
{
167+
builder.AddNLog();
168+
});
169+
}
170+
171+
static string GenerateCSR(string subject)
172+
{
173+
using RSA rsa = RSA.Create(2048);
174+
X500DistinguishedName subjectName = new X500DistinguishedName(subject);
175+
CertificateRequest csr = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
176+
return csr.CreateSigningRequestPem();
177+
}
178+
}
179+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2024 Keyfactor
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace Keyfactor.Extensions.CAPlugin.GCPCASTests;
16+
17+
public sealed class IntegrationTestingFact : FactAttribute
18+
{
19+
public string ProjectId { get; private set; }
20+
public string LocationId { get; private set; }
21+
public string CAPool { get; private set; }
22+
public string CAId { get; private set; }
23+
24+
public IntegrationTestingFact()
25+
{
26+
ProjectId = Environment.GetEnvironmentVariable("GCP_PROJECT_ID") ?? string.Empty;
27+
LocationId = Environment.GetEnvironmentVariable("GCP_LOCATION_ID") ?? string.Empty;
28+
CAPool = Environment.GetEnvironmentVariable("GCP_CAS_CAPOOL") ?? string.Empty;
29+
CAId = Environment.GetEnvironmentVariable("GCP_CAS_CAID") ?? string.Empty;
30+
31+
if (string.IsNullOrEmpty(ProjectId) || string.IsNullOrEmpty(LocationId) || string.IsNullOrEmpty(CAPool) || string.IsNullOrEmpty(CAId))
32+
{
33+
Skip = "Integration testing environment variables are not set - Skipping test";
34+
}
35+
}
36+
}

GCPCAS.Tests/xunit.runner.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3+
"parallelizeAssembly": false,
4+
"parallelizeTestCollections": false
5+
}

GCPCAS.sln

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
1-
2-
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 17
4-
VisualStudioVersion = 17.0.31903.59
5-
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCPCAS", "GCPCAS\GCPCAS.csproj", "{CD27873D-BA79-4DCC-BBC7-011325493516}"
7-
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCPCAS.Tests", "GCPCAS.Tests\GCPCAS.Tests.csproj", "{8E00E94A-1A16-4D21-B015-53277D6F6016}"
9-
EndProject
10-
Global
11-
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12-
Debug|Any CPU = Debug|Any CPU
13-
Release|Any CPU = Release|Any CPU
14-
EndGlobalSection
15-
GlobalSection(SolutionProperties) = preSolution
16-
HideSolutionNode = FALSE
17-
EndGlobalSection
18-
GlobalSection(ProjectConfigurationPlatforms) = postSolution
19-
{CD27873D-BA79-4DCC-BBC7-011325493516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20-
{CD27873D-BA79-4DCC-BBC7-011325493516}.Debug|Any CPU.Build.0 = Debug|Any CPU
21-
{CD27873D-BA79-4DCC-BBC7-011325493516}.Release|Any CPU.ActiveCfg = Release|Any CPU
22-
{CD27873D-BA79-4DCC-BBC7-011325493516}.Release|Any CPU.Build.0 = Release|Any CPU
23-
{8E00E94A-1A16-4D21-B015-53277D6F6016}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24-
{8E00E94A-1A16-4D21-B015-53277D6F6016}.Debug|Any CPU.Build.0 = Debug|Any CPU
25-
{8E00E94A-1A16-4D21-B015-53277D6F6016}.Release|Any CPU.ActiveCfg = Release|Any CPU
26-
{8E00E94A-1A16-4D21-B015-53277D6F6016}.Release|Any CPU.Build.0 = Release|Any CPU
27-
EndGlobalSection
28-
EndGlobal
1+
Microsoft Visual Studio Solution File, Format Version 12.00
2+
# Visual Studio Version 17
3+
VisualStudioVersion = 17.0.31903.59
4+
MinimumVisualStudioVersion = 10.0.40219.1
5+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCPCAS", "GCPCAS\GCPCAS.csproj", "{CD27873D-BA79-4DCC-BBC7-011325493516}"
6+
EndProject
7+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCPCAS.Tests", "GCPCAS.Tests\GCPCAS.Tests.csproj", "{8E00E94A-1A16-4D21-B015-53277D6F6016}"
8+
EndProject
9+
Global
10+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
11+
Debug|Any CPU = Debug|Any CPU
12+
Release|Any CPU = Release|Any CPU
13+
EndGlobalSection
14+
GlobalSection(SolutionProperties) = preSolution
15+
HideSolutionNode = FALSE
16+
EndGlobalSection
17+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
18+
{CD27873D-BA79-4DCC-BBC7-011325493516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19+
{CD27873D-BA79-4DCC-BBC7-011325493516}.Debug|Any CPU.Build.0 = Debug|Any CPU
20+
{CD27873D-BA79-4DCC-BBC7-011325493516}.Release|Any CPU.ActiveCfg = Release|Any CPU
21+
{CD27873D-BA79-4DCC-BBC7-011325493516}.Release|Any CPU.Build.0 = Release|Any CPU
22+
{8E00E94A-1A16-4D21-B015-53277D6F6016}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23+
{8E00E94A-1A16-4D21-B015-53277D6F6016}.Debug|Any CPU.Build.0 = Debug|Any CPU
24+
{8E00E94A-1A16-4D21-B015-53277D6F6016}.Release|Any CPU.ActiveCfg = Release|Any CPU
25+
{8E00E94A-1A16-4D21-B015-53277D6F6016}.Release|Any CPU.Build.0 = Release|Any CPU
26+
EndGlobalSection
27+
EndGlobal

0 commit comments

Comments
 (0)