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
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4

# Setup dotnet 6.0
# Setup dotnet 8.0
# https://github.com/actions/setup-dotnet
- uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.x'
dotnet-version: '8.x'

# Setup nuget
- name: Setup build environment
Expand Down
24 changes: 13 additions & 11 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
- 1.0.0
- First production release of the GoDaddy AnyCA Gateway REST plugin that implements:
- CA Sync
- Download all issued certificates
- Certificate enrollment for all published GoDaddy Certificate SKUs
- Support certificate enrollment (new keys/certificate)
- Support certificate renewal (extend the life of a previously issued certificate with the same or different domain names)
- Support certificate re-issuance (new public/private keys with the same or different domain names)
- Certificate revocation
- Request revocation of a previously issued certificate
# v1.2.0
- Add special condition to handle status 409 when downloading certificates from GoDaddy. 409 indicates that the certificate state does not allow download.

- 1.1.0
# v1.1.0
- chore(docs): Upgrade GitHub Actions to use Bootstrap Workflow v3 to support Doctool

# v1.0.0
- First production release of the GoDaddy AnyCA Gateway REST plugin that implements:
- CA Sync
- Download all issued certificates
- Certificate enrollment for all published GoDaddy Certificate SKUs
- Support certificate enrollment (new keys/certificate)
- Support certificate renewal (extend the life of a previously issued certificate with the same or different domain names)
- Support certificate re-issuance (new public/private keys with the same or different domain names)
- Certificate revocation
- Request revocation of a previously issued certificate
33 changes: 33 additions & 0 deletions GoDaddy/Client/DownloadNotAllowed.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2024 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;

public class DownloadNotAllowed : Exception
{
public DownloadNotAllowed()
{
}

public DownloadNotAllowed(string message)
: base(message)
{
}

public DownloadNotAllowed(string message, Exception inner)
: base(message, inner)
{
}
}

74 changes: 51 additions & 23 deletions GoDaddy/Client/GoDaddyClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,27 @@

namespace Keyfactor.Extensions.CAPlugin.GoDaddy.Client;

public class GoDaddyAuthenticator : AuthenticatorBase {
public class GoDaddyAuthenticator : AuthenticatorBase
{
readonly string _baseUrl;

Check warning on line 36 in GoDaddy/Client/GoDaddyClient.cs

View workflow job for this annotation

GitHub Actions / Build and Test dotnet project

The field 'GoDaddyAuthenticator._baseUrl' is never used

Check warning on line 36 in GoDaddy/Client/GoDaddyClient.cs

View workflow job for this annotation

GitHub Actions / Build and Test dotnet project

The field 'GoDaddyAuthenticator._baseUrl' is never used

Check warning on line 36 in GoDaddy/Client/GoDaddyClient.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-generate-readme-workflow / Use private doctool action in public repository

The field 'GoDaddyAuthenticator._baseUrl' is never used

Check warning on line 36 in GoDaddy/Client/GoDaddyClient.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

The field 'GoDaddyAuthenticator._baseUrl' is never used

Check warning on line 36 in GoDaddy/Client/GoDaddyClient.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

The field 'GoDaddyAuthenticator._baseUrl' is never used

Check warning on line 36 in GoDaddy/Client/GoDaddyClient.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

The field 'GoDaddyAuthenticator._baseUrl' is never used
readonly string _apiKey;
readonly string _apiSecret;

public GoDaddyAuthenticator(string apiToken, string apiSecret) : base("") {
public GoDaddyAuthenticator(string apiToken, string apiSecret) : base("")
{
_apiKey = apiToken;
_apiSecret = apiSecret;
}

protected override ValueTask<RestSharp.Parameter> GetAuthenticationParameter(string accessToken) {
protected override ValueTask<RestSharp.Parameter> GetAuthenticationParameter(string accessToken)
{
var parameter = new HeaderParameter(KnownHeaders.Authorization, $"sso-key {_apiKey}:{_apiSecret}");
return new ValueTask<RestSharp.Parameter>(parameter);
}
}

public class GoDaddyClient : IGoDaddyClient, IDisposable {
public class GoDaddyClient : IGoDaddyClient, IDisposable
{
private ILogger _logger;
readonly RestClient _client;

Expand All @@ -67,15 +71,15 @@
private string _apiKey { get; set; }
private string _apiSecret { get; set; }
private string _shopperId { get; set; }

public IGoDaddyClientBuilder WithApiKey(string apiToken)
{
_apiKey = apiToken;
return this;
}

public IGoDaddyClientBuilder WithApiSecret(string apiSecret)
{
{
_apiSecret = apiSecret;
return this;
}
Expand All @@ -86,7 +90,8 @@
return this;
}

public IGoDaddyClientBuilder WithShopperId(string shopperId) {
public IGoDaddyClientBuilder WithShopperId(string shopperId)
{
_shopperId = shopperId;
return this;
}
Expand All @@ -99,12 +104,14 @@
}
}

public GoDaddyClient(string apiUrl, string apiKey, string apiSecret, string shopperId) {
public GoDaddyClient(string apiUrl, string apiKey, string apiSecret, string shopperId)
{
_logger = LogHandler.GetClassLogger<GoDaddyClient>();

_logger.LogDebug($"Creating GoDaddyClient with API URL: {apiUrl}, API Key: {apiKey}, Shopper ID: {shopperId}");

var options = new RestClientOptions(apiUrl){
var options = new RestClientOptions(apiUrl)
{
Authenticator = new GoDaddyAuthenticator(apiKey, apiSecret),
};

Expand Down Expand Up @@ -154,7 +161,7 @@
_logger.LogDebug("Validating GoDaddy API connection");

string path = $"/v1/shoppers/{_shopperId}";
IDictionary <string, string> query = new Dictionary<string, string> {
IDictionary<string, string> query = new Dictionary<string, string> {
{ "includes", "customerId" }
};

Expand All @@ -170,22 +177,25 @@
}
}

private string GetCustomerId() {
private string GetCustomerId()
{
EnsureClientIsEnabled();
if (string.IsNullOrEmpty(_shopperId)) {
if (string.IsNullOrEmpty(_shopperId))
{
_logger.LogError("Shopper ID is required to get customer ID");
throw new ArgumentNullException(nameof(_shopperId));
}

if (!string.IsNullOrEmpty(_customerId)) {
if (!string.IsNullOrEmpty(_customerId))
{
_logger.LogTrace($"Returning cached customer ID: {_customerId}");
return _customerId;
}

_logger.LogDebug($"Getting customer ID for shopper ID: {_shopperId}");

string path = $"/v1/shoppers/{_shopperId}";
IDictionary <string, string> query = new Dictionary<string, string> {
IDictionary<string, string> query = new Dictionary<string, string> {
{ "includes", "customerId" }
};

Expand All @@ -196,7 +206,8 @@
return _customerId;
}

public async Task<AnyCAPluginCertificate> DownloadCertificate(string certificateId) {
public async Task<AnyCAPluginCertificate> DownloadCertificate(string certificateId)
{
EnsureClientIsEnabled();
_logger.LogDebug($"Downloading certificate with ID: {certificateId}");

Expand All @@ -214,8 +225,9 @@
RevocationDate = details.revokedAt
};
}

public async Task<string> DownloadCertificatePem(string certificateId) {

public async Task<string> DownloadCertificatePem(string certificateId)
{
EnsureClientIsEnabled();
_logger.LogDebug($"Downloading certificate with ID: {certificateId}");

Expand Down Expand Up @@ -267,7 +279,7 @@
foreach (CertificateDetail certificateDetail in certificatesWithPagination.certificates)
{
string debugMessage = "Downloading certificate ";
if (!string.IsNullOrEmpty(certificateDetail.commonName))
if (!string.IsNullOrEmpty(certificateDetail.commonName))
debugMessage += $"with CN {certificateDetail.commonName} ";
if (!string.IsNullOrEmpty(certificateDetail.validStartAt))
debugMessage += $"[issued at {certificateDetail.validStartAt}] ";
Expand All @@ -277,7 +289,17 @@
debugMessage += $"[revoked at {certificateDetail.revokedAt}]";
_logger.LogDebug(debugMessage);

string certificatePemString = await DownloadCertificatePem(certificateDetail.certificateId);
string certificatePemString;
try
{
certificatePemString = await DownloadCertificatePem(certificateDetail.certificateId);
}
catch (DownloadNotAllowed)
{
_logger.LogWarning($"Certificate with request ID {certificateDetail.certificateId} cannot be downloaded (status 409)");
continue;
}

certificatesBuffer.Add(new AnyCAPluginCertificate()
{
CARequestID = certificateDetail.certificateId,
Expand Down Expand Up @@ -538,6 +560,11 @@
}

_logger.LogError($"Received response with unexpected status code [{response.StatusCode}]");
if (response.StatusCode == HttpStatusCode.Conflict)
{
throw new DownloadNotAllowed($"Conflict error occurred: {response.Content}");
}

if (response.Content == null)
{
throw new Exception("Response was not successful and no content was returned.");
Expand All @@ -552,7 +579,7 @@
try
{
_logger.LogTrace("Serializing response content to error object");

JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Expand Down Expand Up @@ -589,7 +616,7 @@

throw new Exception("Failed to GET request after all retries");
}

public async Task<TResponse> PostAsync<TRequest, TResponse>(string endpoint, TRequest body, IDictionary<string, string> query = null)
where TRequest : class
where TResponse : class
Expand Down Expand Up @@ -656,7 +683,7 @@
try
{
_logger.LogTrace("Serializing response content to error object");

JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Expand Down Expand Up @@ -699,7 +726,8 @@

record GoDaddySingleObject<T>(T Data);

public void Dispose() {
public void Dispose()
{
_client?.Dispose();
GC.SuppressFinalize(this);
}
Expand Down
2 changes: 0 additions & 2 deletions GoDaddy/GoDaddyCAPluginBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public GoDaddyCAPluginBuilder<TBuilder> WithConfigProvider(IAnyCAPluginConfigPro
GoDaddyCAPluginConfig.Config properties = JsonConvert.DeserializeObject<GoDaddyCAPluginConfig.Config>(rawConfig);

_logger.LogTrace($"Builder - ApiKey: {properties.ApiKey}");
_logger.LogTrace($"Builder - ApiSecret: {properties.ApiSecret}");
_logger.LogTrace($"Builder - BaseUrl: {properties.BaseUrl}");
_logger.LogTrace($"Builder - ShopperId: {properties.ShopperId}");
_logger.LogTrace($"Builder - Enabled: {properties.Enabled}");
Expand All @@ -59,7 +58,6 @@ public GoDaddyCAPluginBuilder<TBuilder> WithConnectionInformation(Dictionary<str
GoDaddyCAPluginConfig.Config properties = JsonConvert.DeserializeObject<GoDaddyCAPluginConfig.Config>(rawConfig);

_logger.LogTrace($"Builder - ApiKey: {properties.ApiKey}");
_logger.LogTrace($"Builder - ApiSecret: {properties.ApiSecret}");
_logger.LogTrace($"Builder - BaseUrl: {properties.BaseUrl}");
_logger.LogTrace($"Builder - ShopperId: {properties.ShopperId}");
_logger.LogTrace($"Builder - Enabled: {properties.Enabled}");
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,16 @@ The GoDaddy AnyCA Gateway REST plugin is supported by Keyfactor for Keyfactor cu

2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [GoDaddy AnyCA Gateway REST plugin](https://github.com/Keyfactor/godaddy-caplugin/releases/latest) from GitHub.

3. Copy the unzipped directory (usually called `net6.0`) to the Extensions directory:
3. Copy the unzipped directory (usually called `net6.0` or `net8.0`) to the Extensions directory:


```shell
Depending on your AnyCA Gateway REST version, copy the unzipped directory to one of the following locations:
Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net6.0\Extensions
Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net8.0\Extensions
```

> The directory containing the GoDaddy AnyCA Gateway REST plugin DLLs (`net6.0`) can be named anything, as long as it is unique within the `Extensions` directory.
> The directory containing the GoDaddy AnyCA Gateway REST plugin DLLs (`net6.0` or `net8.0`) can be named anything, as long as it is unique within the `Extensions` directory.

4. Restart the AnyCA Gateway REST service.

Expand Down Expand Up @@ -185,8 +188,6 @@ The GoDaddy AnyCA Gateway REST plugin is supported by Keyfactor for Keyfactor cu





## License

Apache License 2.0, see [LICENSE](LICENSE).
Expand Down
5 changes: 3 additions & 2 deletions integration-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"link_github": true,
"update_catalog": true,
"gateway_framework": "24.2",
"release_dir": "GoDaddy/bin/Release/net6.0",
"release_dir": "GoDaddy/bin/Release",
"release_project": "GoDaddy/GoDaddy.csproj",
"about": {
"carest": {
"ca_plugin_config": [
Expand Down Expand Up @@ -113,4 +114,4 @@
]
}
}
}
}
Loading