Skip to content

Commit 44b94b2

Browse files
committed
feat: fetch certificate from api
Signed-off-by: Christopher Thomsen <christhomsen82@gmail.com>
1 parent 62ebb59 commit 44b94b2

6 files changed

Lines changed: 133 additions & 6 deletions

File tree

Models/Certificate.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace IamOrchestrator.Models;
2+
3+
public class CertificateResponse
4+
{
5+
public string CustomerName { get; set; } = string.Empty;
6+
public string CertificateData { get; set; } = string.Empty; // Base64 encoded PFX
7+
public string Thumbprint { get; set; } = string.Empty;
8+
public DateTime ExpiresAt { get; set; }
9+
public string Password { get; set; } = string.Empty;
10+
}

OrchestratorWorker.cs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,42 @@ private async Task ExecuteJobAsync(Job job)
171171
await _apiClient.UpdateJobStatusAsync(job.Id, JobStatus.Running);
172172
await SendLogAsync(job.Id, $"Job execution started", Models.LogLevel.Info);
173173

174-
// Execute the container
175-
var success = await _containerExecutor.ExecuteJobAsync(job,
174+
// Download certificate for customer if customer is specified
175+
byte[]? certificatePfx = null;
176+
string? certificatePassword = null;
177+
178+
if (!string.IsNullOrEmpty(job.Customer))
179+
{
180+
_logger.LogInformation("Downloading certificate for customer: {Customer}", job.Customer);
181+
var certificate = await _apiClient.GetCertificateAsync(job.Customer);
182+
183+
if (certificate != null)
184+
{
185+
certificatePfx = Convert.FromBase64String(certificate.CertificateData);
186+
certificatePassword = certificate.Password;
187+
188+
await SendLogAsync(job.Id,
189+
$"Certificate acquired for {job.Customer} (expires: {certificate.ExpiresAt:yyyy-MM-dd HH:mm:ss} UTC)",
190+
Models.LogLevel.Info);
191+
192+
_logger.LogInformation("Certificate downloaded - Thumbprint: {Thumbprint}, Expires: {ExpiresAt}",
193+
certificate.Thumbprint, certificate.ExpiresAt);
194+
}
195+
else
196+
{
197+
await SendLogAsync(job.Id,
198+
$"Warning: Could not obtain certificate for {job.Customer}",
199+
Models.LogLevel.Warning);
200+
}
201+
}
202+
203+
// Execute the container with certificate
204+
var success = await _containerExecutor.ExecuteJobAsync(
205+
job,
176206
async (logMessage) => await SendLogAsync(job.Id, logMessage, Models.LogLevel.Info, "orchestrator"),
177-
async (logMessage) => await SendLogAsync(job.Id, logMessage, Models.LogLevel.Info, "container"));
207+
async (logMessage) => await SendLogAsync(job.Id, logMessage, Models.LogLevel.Info, "container"),
208+
certificatePfx,
209+
certificatePassword);
178210

179211
// Update final status
180212
var finalStatus = success ? JobStatus.Completed : JobStatus.Failed;

Services/ApiClient.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,42 @@ public async Task SendHeartbeatAsync(Orchestrator orchestrator)
260260
throw;
261261
}
262262
}
263+
264+
public async Task<CertificateResponse?> GetCertificateAsync(string customerName)
265+
{
266+
try
267+
{
268+
if (!await EnsureAuthenticatedAsync())
269+
{
270+
_logger.LogWarning("Cannot get certificate: Not authenticated");
271+
return null;
272+
}
273+
274+
var response = await _httpClient.GetAsync($"/api/certificates/{Uri.EscapeDataString(customerName)}");
275+
276+
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
277+
{
278+
_logger.LogWarning("Received 401, re-authenticating...");
279+
_authToken = null;
280+
if (await EnsureAuthenticatedAsync())
281+
{
282+
response = await _httpClient.GetAsync($"/api/certificates/{Uri.EscapeDataString(customerName)}");
283+
}
284+
}
285+
286+
if (!response.IsSuccessStatusCode)
287+
{
288+
_logger.LogWarning("Failed to get certificate for {Customer}: {StatusCode}",
289+
customerName, response.StatusCode);
290+
return null;
291+
}
292+
293+
return await response.Content.ReadFromJsonAsync<CertificateResponse>(_jsonOptions);
294+
}
295+
catch (Exception ex)
296+
{
297+
_logger.LogError(ex, "Failed to get certificate for {Customer}", customerName);
298+
return null;
299+
}
300+
}
263301
}

Services/ContainerExecutor.cs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,15 @@ public ContainerExecutor(ILogger<ContainerExecutor> logger, IConfiguration confi
2020
_dockerClient = new DockerClientConfiguration(new Uri(dockerEndpoint)).CreateClient();
2121
}
2222

23-
public async Task<bool> ExecuteJobAsync(Job job, Func<string, Task> orchestratorLogCallback, Func<string, Task> containerOutputCallback)
23+
public async Task<bool> ExecuteJobAsync(
24+
Job job,
25+
Func<string, Task> orchestratorLogCallback,
26+
Func<string, Task> containerOutputCallback,
27+
byte[]? certificatePfx = null,
28+
string? certificatePassword = null)
2429
{
2530
string? containerId = null;
31+
string? tempCertPath = null;
2632

2733
try
2834
{
@@ -38,6 +44,26 @@ public async Task<bool> ExecuteJobAsync(Job job, Func<string, Task> orchestrator
3844
envVars.Add($"SCRIPT_PATH={job.ScriptPath}");
3945
envVars.Add($"WHAT_IF={job.IsWhatIf}");
4046

47+
// Handle certificate if provided
48+
var binds = new List<string>();
49+
if (certificatePfx != null && !string.IsNullOrEmpty(certificatePassword))
50+
{
51+
await orchestratorLogCallback("Certificate provided - preparing for container");
52+
53+
// Write certificate to temp file
54+
tempCertPath = Path.Combine(Path.GetTempPath(), $"cert-{job.Id}.pfx");
55+
await File.WriteAllBytesAsync(tempCertPath, certificatePfx);
56+
57+
// Mount certificate into container
58+
binds.Add($"{tempCertPath}:/tmp/client-cert.pfx:ro");
59+
60+
// Add certificate environment variables
61+
envVars.Add("CLIENT_CERT_PATH=/tmp/client-cert.pfx");
62+
envVars.Add($"CLIENT_CERT_PASSWORD={certificatePassword}");
63+
64+
_logger.LogInformation("Certificate mounted for job {JobId}", job.Id);
65+
}
66+
4167
await orchestratorLogCallback($"Pulling container image: {job.ContainerImage}");
4268

4369
// Debug: Log registry credential availability
@@ -140,7 +166,8 @@ await _dockerClient.Images.CreateImageAsync(
140166
Env = envVars,
141167
HostConfig = new HostConfig
142168
{
143-
AutoRemove = true
169+
AutoRemove = true,
170+
Binds = binds.Any() ? binds : null
144171
},
145172
Name = $"iam-job-{job.Id}"
146173
});
@@ -175,6 +202,20 @@ await _dockerClient.Images.CreateImageAsync(
175202
}
176203
finally
177204
{
205+
// Cleanup temp certificate file
206+
if (!string.IsNullOrEmpty(tempCertPath) && File.Exists(tempCertPath))
207+
{
208+
try
209+
{
210+
File.Delete(tempCertPath);
211+
_logger.LogDebug("Deleted temporary certificate file: {Path}", tempCertPath);
212+
}
213+
catch (Exception ex)
214+
{
215+
_logger.LogWarning(ex, "Failed to delete temporary certificate file: {Path}", tempCertPath);
216+
}
217+
}
218+
178219
// Cleanup
179220
if (containerId != null)
180221
{

Services/IApiClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ public interface IApiClient
99
Task<bool> UpdateJobStatusAsync(Guid jobId, JobStatus status);
1010
Task<bool> SendLogAsync(LogEntry logEntry);
1111
Task SendHeartbeatAsync(Orchestrator orchestrator);
12+
Task<CertificateResponse?> GetCertificateAsync(string customerName);
1213
}

Services/IContainerExecutor.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,10 @@ namespace IamOrchestrator.Services;
44

55
public interface IContainerExecutor
66
{
7-
Task<bool> ExecuteJobAsync(Job job, Func<string, Task> orchestratorLogCallback, Func<string, Task> containerOutputCallback);
7+
Task<bool> ExecuteJobAsync(
8+
Job job,
9+
Func<string, Task> orchestratorLogCallback,
10+
Func<string, Task> containerOutputCallback,
11+
byte[]? certificatePfx = null,
12+
string? certificatePassword = null);
813
}

0 commit comments

Comments
 (0)