Skip to content

This is a comprehensive, phased implementation plan to refactor the Deployment Automation system. It addresses the critical security vulnerabilities from the previous review and integrates your new requirements for Configuration Management, mTLS, Key Vault / Windows Cert Store integration, and Azure Entra ID (JWT).


Implementation Plan: Secure Deployment Automation#

To ensure a smooth transition without breaking existing workflows, the implementation is broken down into four logical phases.

Phase 1: Core Security & Stability Fixes#

Address critical vulnerabilities related to input validation, resource exhaustion, and state integrity.

1.1 Path Traversal & Integrity Checks (Agent)#

  • Update DeploymentPayload.cs (Contracts): Add a PackageSha256 property. The Orchestrator will calculate the file hash before uploading, and the Agent will verify it before extraction to ensure supply-chain integrity.
  • Update FileExtractor.cs:
    • Validate job.Config.Version using Regex (^[0-9a-zA-Z\.\-_]+$).
    • Implement Path.GetFullPath boundary validation to guarantee extraction stays within the configured BasePath.
    • Compute the SHA-256 hash of the uploaded TempPackagePath and compare it against PackageSha256. Throw a SecurityException if they mismatch.

1.2 Bounded Queue & Graceful Degradation (Agent)#

  • Update Program.cs: Replace Channel.CreateUnbounded with Channel.CreateBounded<DeploymentJob>. Set capacity to a sensible limit (e.g., 10) and use BoundedChannelFullMode.Wait.
  • Update DeployEndpoints.cs: Wrap the channel.Writer.WriteAsync in a cancellation token aware block. If the queue is full and times out, return 503 Service Unavailable.
  • Temp File Cleanup: In DeployEndpoints.cs, wrap the file upload stream in a try/catch. If anything fails before the job is queued, immediately delete the temporary file to prevent disk exhaustion.

1.3 Cancellation Token Propagation#

  • Update IFileExtractor, IIisConfigurator, IConfigFileUpdater, and IWindowsServiceManager to accept a CancellationToken.
  • Pass the stoppingToken from DeploymentWorkerService down the chain so that if the Agent service is stopped, local operations cancel gracefully rather than leaving orphaned processes.

Phase 2: Configuration Refactoring#

Eliminate hardcoded secrets and environment strings.

2.1 Refactor Orchestrator Connection Strings#

  • Update OrchestratorOptions.cs: Remove the hardcoded GetConnectionString switch statement.
  • Update appsettings.json: Leverage standard nested connection strings mapped by environment:
    "ConnectionStrings": {
      "CommandCenter_Acceptatie": "Data Source=vmbenefitsdbap1;Initial Catalog=CommandCenter;Integrated Security=True;Encrypt=True;TrustServerCertificate=False",
      "CommandCenter_Productie": "Data Source=vmbenefitsdbp1;Initial Catalog=CommandCenter;Integrated Security=True;Encrypt=True;TrustServerCertificate=False"
    }
    
  • Update SqlFinalizationService & SqlPreCheckService: Inject IConfiguration and dynamically resolve the connection string using configuration.GetConnectionString($"CommandCenter_{environment}").

Phase 3: HTTPS, mTLS & Certificate Management#

Replace X-Api-Key with Mutual TLS. Support loading certificates from Windows Certificate Store and Azure Key Vault.

3.1 Agent Certificate Loading (Kestrel Server Cert)#

  • Create a CertificateLoaderService in the Agent that reads from appsettings.json:
    "ServerCertificate": {
      "Source": "KeyVault", // or "Store"
      "KeyVaultUrl": "https://kv-benefits.vault.azure.net/",
      "CertificateName": "AgentTlsCert",
      "StoreLocation": "LocalMachine",
      "StoreName": "My",
      "Thumbprint": "ABC123..."
    }
    
  • If Store: Load using X509Store.
  • If KeyVault: Use SecretClient (Azure.Security.KeyVault.Secrets) to download the PFX secret and instantiate an X509Certificate2.
  • Configure Kestrel in Program.cs to use this certificate for HTTPS binding.

3.2 Kestrel mTLS Configuration (Agent)#

  • Install Microsoft.AspNetCore.Authentication.Certificate.
  • In Program.cs, configure Kestrel to allow client certificates (do not require them at the Kestrel level if JWT is also going to be supported):
    builder.WebHost.ConfigureKestrel(options =>
    {
        options.ConfigureHttpsDefaults(httpsOptions =>
        {
            httpsOptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
        });
    });
    
  • Register Certificate Authentication:
    builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate(options => {
            options.AllowedCertificateTypes = CertificateTypes.All;
            options.Events = new CertificateAuthenticationEvents {
                OnCertificateValidated = context => {
                    // Validate Thumbprint or Subject against allowed list in config
                    return Task.CompletedTask;
                }
            };
        });
    

3.3 Orchestrator Client Certificate Management#

  • Update the Orchestrator to load a Client Certificate. Create an OrchestratorCertificateLoader supporting File (for Linux CI/CD runners), Store (for local Windows execution), and KeyVault.
  • Update Program.cs where the HttpClient is registered:
    services.AddHttpClient<IAgentClient, AgentClient>((sp, client) => {
        var opts = sp.GetRequiredService<IOptions<OrchestratorOptions>>().Value;
        client.BaseAddress = new Uri(opts.AgentBaseUrl); // Must be HTTPS now
    })
    .ConfigurePrimaryHttpMessageHandler(sp => {
        var certLoader = sp.GetRequiredService<ICertificateLoader>();
        var handler = new HttpClientHandler();
    
        var clientCert = certLoader.GetClientCertificate();
        if (clientCert != null) {
            handler.ClientCertificates.Add(clientCert);
        }
        return handler;
    });
    

Phase 4: Azure Entra ID (JWT) Integration#

Provide optional fallback/alternative authentication via Entra ID (OIDC/OAuth2).

4.1 Agent Multi-Scheme Authentication#

  • Install Microsoft.Identity.Web.
  • Update Program.cs in the Agent to support both schemes:
    builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate(...)
        .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
    
  • Crucial Step - Authorization Policy: Create a default authorization policy that accepts either scheme. If a request comes in with a valid Client Certificate, it passes. If it comes in with a valid Authorization: Bearer <token>, it passes.
    builder.Services.AddAuthorization(options =>
    {
        var defaultPolicy = new AuthorizationPolicyBuilder(
            CertificateAuthenticationDefaults.AuthenticationScheme,
            JwtBearerDefaults.AuthenticationScheme)
            .RequireAuthenticatedUser()
            .Build();
    
        options.DefaultPolicy = defaultPolicy;
    });
    

4.2 Orchestrator Entra ID Support#

  • Install Azure.Identity.
  • Update the Orchestrator's AgentClient.cs (or via a DelegatingHandler) to attach a JWT if configured.
  • If the configuration specifies AuthMode: EntraId, use DefaultAzureCredential to acquire a token scoped to the Agent's App Registration Client ID, and attach it to the HttpClient:
    // Inside a DelegatingHandler or AgentClient
    var credential = new DefaultAzureCredential();
    var token = await credential.GetTokenAsync(
        new TokenRequestContext(new[] { "api://<Agent-App-Client-Id>/.default" }), 
        cancellationToken);
    
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
    

Summary of Configuration Changes Required#

You will need to update the appsettings.json schemas for both projects.

Agent appsettings.json Additions:

{
  "ServerCertificate": {
    "Source": "Store",
    "StoreLocation": "LocalMachine",
    "StoreName": "My",
    "Thumbprint": "YOUR_SERVER_CERT_THUMBPRINT"
  },
  "ClientAuthentication": {
    "AllowedThumbprints": ["ALLOWED_CLIENT_CERT_THUMBPRINT"]
  },
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "yourtenant.onmicrosoft.com",
    "TenantId": "YOUR_TENANT_ID",
    "ClientId": "AGENT_APP_REGISTRATION_CLIENT_ID"
  }
}

Orchestrator appsettings.json Additions:

{
  "ConnectionStrings": {
    "CommandCenter_Acceptatie": "...",
    "CommandCenter_Productie": "..."
  },
  "Orchestrator": {
    "AgentBaseUrl": "https://vmbenefitsbmp1:5100",
    "AuthMode": "mTLS", // or "EntraId"
    "ClientCertificate": {
      "Source": "KeyVault",
      "KeyVaultUrl": "https://kv-orchestrator.vault.azure.net/",
      "CertificateName": "OrchestratorClientCert"
    },
    "EntraId": {
      "TargetScope": "api://<Agent-App-Client-Id>/.default"
    }
  }
}

  1. Phase 1 & 2 can be done immediately. They remove critical vulnerabilities and fix technical debt (hardcoded strings).
  2. Phase 3 (mTLS) should be implemented and tested locally using Self-Signed certificates placed in the Windows Store before wiring up Azure Key Vault.
  3. Phase 4 (Entra ID) can be added as a final enhancement, giving you maximum flexibility for environments (like pure cloud CI/CD runners) where managing a Client Certificate file is less desirable than using Azure Managed Identities (which DefaultAzureCredential supports out of the box).