Skip to content

Conversation

@nabsul
Copy link
Owner

@nabsul nabsul commented Oct 6, 2025

No description provided.

Copilot AI review requested due to automatic review settings October 6, 2025 19:23
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request implements Gateway API support for KCert by adding testing infrastructure and refactoring the codebase for improved configurability and maintainability.

  • Adds comprehensive testing setup with justfile automation for creating temporary clusters with DigitalOcean
  • Refactors Helm chart configuration to use environment variables for better flexibility
  • Simplifies service registration and removes attribute-based dependency injection

Reviewed Changes

Copilot reviewed 52 out of 53 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
testing/* New testing infrastructure including justfile automation, Gateway API configuration, and test app
charts/kcert/* Simplified Helm chart with environment variable-based configuration
Services/* Refactored services to remove attribute-based DI and improve async handling
Challenge/* Updated challenge providers with better error handling and simplified logic
Extensions.cs New service registration extension replacing attribute-based approach

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +13 to +19
public async ListMet<JsonObject> ListAsync(CancellationToken tok)
{
await foreach (var obj in k8s.listcustom
{
yield return obj;
}
}
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method return type 'ListMet' is invalid and the method body is incomplete. The return type should be 'IAsyncEnumerable' and the method body needs to be completed with proper Kubernetes API calls.

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +22
private static AcmeDirectoryResponse? _dir;
public static AcmeDirectoryResponse Dir => _dir ?? throw new Exception("ACME directory not initialized.");
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static field '_dir' is not thread-safe. Multiple threads could access and modify this field concurrently, leading to race conditions. Consider using proper synchronization or making this instance-based.

Copilot uses AI. Check for mistakes.
public Task<AcmeAuthzResponse> GetAuthzAsync(string key, Uri authzUri, string kid, CancellationToken tok) => PostAsync<AcmeAuthzResponse>(key, authzUri, null, kid, tok);
public Task<AcmeChallengeResponse> TriggerChallengeAsync(string key, Uri challengeUri, string kid, CancellationToken tok) => PostAsync<AcmeChallengeResponse>(key, challengeUri, new { }, kid, tok);

public static async Task ReadDirectoryAsync(KCertConfig cfg, CancellationToken tok)
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making this method static while the class has instance state (like '_nonce') creates inconsistent API design. The method should be instance-based to maintain proper state management.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +40
public string AcmeEabKeyId => GetRequiredString("Acme:EabKeyId");
public string AcmeHmacKey => GetRequiredString("Acme:EabHmacKey");
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UseEabKey property calls AcmeEabKeyId, which calls GetRequiredString and will throw an exception if the value is null or empty. This creates a circular dependency where checking if EAB is available throws an exception. Consider using GetString instead of GetRequiredString for the properties used in the UseEabKey check.

Suggested change
public string AcmeEabKeyId => GetRequiredString("Acme:EabKeyId");
public string AcmeHmacKey => GetRequiredString("Acme:EabHmacKey");
public string AcmeEabKeyId => GetString("Acme:EabKeyId");
public string AcmeHmacKey => GetString("Acme:EabHmacKey");

Copilot uses AI. Check for mistakes.

public string? SmtpEmailFrom => GetString("Smtp:EmailFrom");
public string? SmtpHost => GetString("Smtp:Host");
public bool SmtpEnabled => !string.IsNullOrEmpty(SmtpEmailFrom);
Copy link

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the EAB key issue, SmtpEnabled property calls SmtpEmailFrom which uses GetRequiredString and will throw an exception if the value is missing. This prevents checking if SMTP is enabled without throwing an exception. Consider using GetString for the check.

Suggested change
public bool SmtpEnabled => !string.IsNullOrEmpty(SmtpEmailFrom);
public bool SmtpEnabled => !string.IsNullOrEmpty(GetString("Smtp:EmailFrom"));

Copilot uses AI. Check for mistakes.

public string HandleChallenge(string token)
{
log.LogInformation("Received ACME Challenge: {token}", token);

Check failure

Code scanning / CodeQL

Log entries created from user input High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 3 months ago

To fix the issue, sanitize the token string before logging it. Because logs are typically viewed as text, the primary concern is controlling newline and control characters. The simplest approach is to replace all carriage returns (\r) and linefeeds (\n) in the token string with empty strings before logging. This should be done inline in the call to LogInformation in Challenge/HttpChallengeProvider.cs line 15. No additional dependencies are required; use string.Replace methods. Only the line logging the token needs changing. No logic changes are needed, and the return value remains unsanitized (as it is part of the ACME protocol).

Suggested changeset 1
Challenge/HttpChallengeProvider.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Challenge/HttpChallengeProvider.cs b/Challenge/HttpChallengeProvider.cs
--- a/Challenge/HttpChallengeProvider.cs
+++ b/Challenge/HttpChallengeProvider.cs
@@ -12,7 +12,7 @@
 
     public string HandleChallenge(string token)
     {
-        log.LogInformation("Received ACME Challenge: {token}", token);
+        log.LogInformation("Received ACME Challenge: {token}", token.Replace("\r", "").Replace("\n", ""));
         var thumbprint = cert.GetThumbprint();
         return $"{token}.{thumbprint}";
     }
EOF
@@ -12,7 +12,7 @@

public string HandleChallenge(string token)
{
log.LogInformation("Received ACME Challenge: {token}", token);
log.LogInformation("Received ACME Challenge: {token}", token.Replace("\r", "").Replace("\n", ""));
var thumbprint = cert.GetThumbprint();
return $"{token}.{thumbprint}";
}
Copilot is powered by AI and may make mistakes. Always verify output.
await SendAsync(subject, body, tok);
}

private async Task SendAsync(string subject, string text, CancellationToken tok)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

This information exposed to the user depends on
exception information
.
This information exposed to the user depends on exception information.
This information exposed to the user depends on exception information.

Copilot Autofix

AI 3 months ago

The best fix is to ensure that sensitive exception stack trace information is not sent via email to potentially untrusted recipients. Instead, log the full error message—including stack traces—securely on the server's logging infrastructure, and send only a generic message or, at most, the summary (ex.Message) in notification emails. For the affected areas:

  • In NotifyFailureAsync, replace the body construction to exclude ex.StackTrace, and include only a brief error message.
  • In RenewalMessage, remove or limit the exposure of exception information; exclude the stack trace and, optionally, the exception message, replacing with a generic error message.
  • Optionally, add a logging call so the stack trace is still preserved for server-side diagnostics (log.LogError(ex, "Unhandled exception in NotifyFailureAsync")).
  • Avoid modifying email formatting or other logic.
    Change lines 41, 72 (and possibly the logging around exception handling) in Services/EmailClient.cs.
Suggested changeset 1
Services/EmailClient.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Services/EmailClient.cs b/Services/EmailClient.cs
--- a/Services/EmailClient.cs
+++ b/Services/EmailClient.cs
@@ -38,7 +38,8 @@
         }
 
         var subject = "KCert encountered an unexpected error";
-        var body = $"{message}\n\n{ex.Message}\n\n{ex.StackTrace}";
+        log.LogError(ex, "Unhandled exception in NotifyFailureAsync: {Message}", message);
+        var body = $"{message}\n\nError: {ex.Message}";
         await SendAsync(subject, body, tok);
     }
 
@@ -69,7 +70,7 @@
         {
             lines.Add("\nLogs:\n");
             lines.Add(string.Join('\n', ex.Logs));
-            lines.Add($"Error:\n\n{ex.Message}\n\n{ex.StackTrace}");
+            lines.Add($"Error:\n\n{ex.Message}");
         }
 
         return string.Join('\n', lines);
EOF
@@ -38,7 +38,8 @@
}

var subject = "KCert encountered an unexpected error";
var body = $"{message}\n\n{ex.Message}\n\n{ex.StackTrace}";
log.LogError(ex, "Unhandled exception in NotifyFailureAsync: {Message}", message);
var body = $"{message}\n\nError: {ex.Message}";
await SendAsync(subject, body, tok);
}

@@ -69,7 +70,7 @@
{
lines.Add("\nLogs:\n");
lines.Add(string.Join('\n', ex.Logs));
lines.Add($"Error:\n\n{ex.Message}\n\n{ex.StackTrace}");
lines.Add($"Error:\n\n{ex.Message}");
}

return string.Join('\n', lines);
Copilot is powered by AI and may make mistakes. Always verify output.
{
await prev;
tok.ThrowIfCancellationRequested();
log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", ns, secretName, string.Join(", ", hosts));

Check failure

Code scanning / CodeQL

Log entries created from user input High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 3 months ago

To mitigate log forging, sanitize all user input before it is logged. In this code base, the problematic field is the ns parameter, received from the URL and used in log fields. We should ensure that, prior to any log call, the ns value is normalized to remove newline or other control characters that could corrupt the log structure.

The most robust approach is to use a utility method that strips or replaces dangerous characters (\r, \n, etc.) from the ns string. To avoid changing existing logic and maintain clarity, the sanitization should happen as close as possible to the logging call or before passing the value to the logger.

We should sanitize both ns and, for completeness, any other future user-provided values incorporated into logs as structured fields—here, the key focus is on ns. The best option is to have a local private static method (e.g., SanitizeForLog(string value)) to perform this, using String.Replace to remove or replace line endings.

Edit needed:

  • In Services/KCertClient.cs, before logging in StartRenewalProcessAsync, sanitize (assign to local) the ns value.
  • Add a helper method SanitizeForLog within KCertClient.
  • Use the sanitized ns in the logging call.

No added dependencies are required: just use basic C# string manipulation.


Suggested changeset 1
Services/KCertClient.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Services/KCertClient.cs b/Services/KCertClient.cs
--- a/Services/KCertClient.cs
+++ b/Services/KCertClient.cs
@@ -33,7 +33,8 @@
         await _semaphore.WaitAsync(tok);
         try
         {
-            log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", ns, secretName, string.Join(", ", hosts));
+            var sanitizedNs = SanitizeForLog(ns);
+            log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", sanitizedNs, secretName, string.Join(", ", hosts));
             await RenewCertAsync(ns, secretName, hosts, tok);
         }
         finally
@@ -65,4 +66,10 @@
             log.LogError(ex, "Unexpected renewal failure");
         }
     }
+    private static string SanitizeForLog(string value)
+    {
+        if (value == null) return null;
+        // Remove CR and LF to prevent log forging
+        return value.Replace("\r", "").Replace("\n", "");
+    }
 }
EOF
@@ -33,7 +33,8 @@
await _semaphore.WaitAsync(tok);
try
{
log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", ns, secretName, string.Join(", ", hosts));
var sanitizedNs = SanitizeForLog(ns);
log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", sanitizedNs, secretName, string.Join(", ", hosts));
await RenewCertAsync(ns, secretName, hosts, tok);
}
finally
@@ -65,4 +66,10 @@
log.LogError(ex, "Unexpected renewal failure");
}
}
private static string SanitizeForLog(string value)
{
if (value == null) return null;
// Remove CR and LF to prevent log forging
return value.Replace("\r", "").Replace("\n", "");
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
{
await prev;
tok.ThrowIfCancellationRequested();
log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", ns, secretName, string.Join(", ", hosts));

Check failure

Code scanning / CodeQL

Log entries created from user input High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 3 months ago

To fix the problem, before logging the potentially untrusted user input (secretName), sanitize it to remove newlines and carriage returns (and ideally any other problematic formatting). This is typically done by replacing \n and \r characters with empty strings to prevent log forging via multiline entries.

In this case, sanitize the secretName value just before logging, in StartRenewalProcessAsync, by using secretName.Replace("\r", "").Replace("\n", ""). Store the sanitized value in a local variable and pass that to log.LogInformation. Only the logging line (and potentially the lines immediately around it) need changing—other uses of secretName are likely not in log sinks and don't require modification.

No new dependencies are needed: we're using standard string methods. No other files or lines require modification.

Suggested changeset 1
Services/KCertClient.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Services/KCertClient.cs b/Services/KCertClient.cs
--- a/Services/KCertClient.cs
+++ b/Services/KCertClient.cs
@@ -33,7 +33,8 @@
         await _semaphore.WaitAsync(tok);
         try
         {
-            log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", ns, secretName, string.Join(", ", hosts));
+            var sanitizedSecretName = secretName.Replace("\r", "").Replace("\n", "");
+            log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", ns, sanitizedSecretName, string.Join(", ", hosts));
             await RenewCertAsync(ns, secretName, hosts, tok);
         }
         finally
EOF
@@ -33,7 +33,8 @@
await _semaphore.WaitAsync(tok);
try
{
log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", ns, secretName, string.Join(", ", hosts));
var sanitizedSecretName = secretName.Replace("\r", "").Replace("\n", "");
log.LogInformation("Starting renewal process for secret {ns}/{secretName} with hosts {hosts}", ns, sanitizedSecretName, string.Join(", ", hosts));
await RenewCertAsync(ns, secretName, hosts, tok);
}
finally
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants