A comprehensive .NET library for dynamic plugin loading, execution, and management with enhanced stability, type safety, and security features.
This guide provides detailed implementation specifications for building the Containerized RuntimePluggableClassFactory (CRPCF) based on the Technical Design Specification.
// File: CRPCF.Core/Interfaces/ISignedPackageManager.cs
namespace CRPCF.Core.Interfaces
{
public interface ISignedPackageManager
{
Task<PackageValidationResult> ValidatePackageAsync(
Stream packageStream,
PackageValidationOptions options);
Task<SignedPackageInfo> ExtractPackageInfoAsync(Stream packageStream);
Task<bool> IsSignerTrustedAsync(SignerInfo signer, string packageId);
Task<IEnumerable<TrustedSigner>> GetTrustedSignersAsync();
Task AddTrustedSignerAsync(TrustedSigner signer);
Task RemoveTrustedSignerAsync(string signerThumbprint);
}
public class PackageValidationResult
{
public bool IsValid { get; set; }
public SignedPackageInfo PackageInfo { get; set; }
public IEnumerable<ValidationError> Errors { get; set; }
public IEnumerable<ValidationWarning> Warnings { get; set; }
}
public class SignedPackageInfo
{
public string PackageId { get; set; }
public string Version { get; set; }
public SignerInfo SignerInfo { get; set; }
public DateTime SignedAt { get; set; }
public byte[] PackageHash { get; set; }
public long PackageSize { get; set; }
}
public class SignerInfo
{
public string SubjectName { get; set; }
public string CertificateThumbprint { get; set; }
public X509Certificate2 Certificate { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public bool IsExpired => DateTime.UtcNow > ValidTo;
}
}
// File: CRPCF.Core/Interfaces/IContainerOrchestrator.cs
namespace CRPCF.Core.Interfaces
{
public interface IContainerOrchestrator
{
string PlatformName { get; }
Task<ContainerInstance> CreateContainerAsync(
ContainerSpec spec,
CancellationToken cancellationToken = default);
Task<ExecutionResult> ExecuteAsync(
ContainerInstance container,
ExecutionRequest request,
CancellationToken cancellationToken = default);
Task<ContainerStatus> GetStatusAsync(
ContainerInstance container,
CancellationToken cancellationToken = default);
Task DestroyContainerAsync(
ContainerInstance container,
CancellationToken cancellationToken = default);
Task<IEnumerable<ContainerInstance>> ListContainersAsync(
ContainerListOptions options = null,
CancellationToken cancellationToken = default);
}
public class ContainerSpec
{
public string ImageName { get; set; }
public string ImageTag { get; set; }
public Dictionary<string, string> Labels { get; set; } = new();
public ContainerResources Resources { get; set; } = new();
public ContainerSecurity Security { get; set; } = new();
public Dictionary<string, string> EnvironmentVariables { get; set; } = new();
public TimeSpan ExecutionTimeout { get; set; } = TimeSpan.FromMinutes(5);
}
public class ContainerResources
{
public long MemoryLimitBytes { get; set; } = 512 * 1024 * 1024; // 512MB
public double CpuLimit { get; set; } = 0.5; // 0.5 CPU cores
public long DiskLimitBytes { get; set; } = 1024 * 1024 * 1024; // 1GB
}
public class ContainerSecurity
{
public bool ReadOnlyFileSystem { get; set; } = true;
public bool RunAsNonRoot { get; set; } = true;
public bool AllowPrivilegeEscalation { get; set; } = false;
public NetworkPolicy NetworkPolicy { get; set; } = NetworkPolicy.Isolated;
public IEnumerable<string> AllowedCapabilities { get; set; } = Array.Empty<string>();
}
public enum NetworkPolicy
{
Isolated, // No network access
Internal, // Internal cluster/network only
Restricted, // Limited external access
Unrestricted // Full network access
}
}
// File: CRPCF.Core/Interfaces/IPluginOrchestrator.cs
namespace CRPCF.Core.Interfaces
{
public interface IPluginOrchestrator
{
Task<PluginDeploymentResult> DeployPluginAsync(
DeploymentRequest request,
CancellationToken cancellationToken = default);
Task<PluginExecutionResult> ExecutePluginAsync(
PluginExecutionRequest request,
CancellationToken cancellationToken = default);
Task<PluginInfo> GetPluginInfoAsync(
PluginIdentifier pluginId,
CancellationToken cancellationToken = default);
Task<IEnumerable<PluginInfo>> ListPluginsAsync(
PluginListOptions options = null,
CancellationToken cancellationToken = default);
Task UndeployPluginAsync(
PluginIdentifier pluginId,
CancellationToken cancellationToken = default);
Task<PluginHealth> GetPluginHealthAsync(
PluginIdentifier pluginId,
CancellationToken cancellationToken = default);
event EventHandler<PluginEventArgs> PluginDeployed;
event EventHandler<PluginEventArgs> PluginExecuted;
event EventHandler<PluginEventArgs> PluginFailed;
event EventHandler<PluginEventArgs> PluginUndeployed;
}
public class DeploymentRequest
{
public Stream PackageStream { get; set; }
public string TargetPlatform { get; set; }
public DeploymentConfiguration Configuration { get; set; } = new();
public Dictionary<string, object> Metadata { get; set; } = new();
}
public class PluginExecutionRequest
{
public PluginIdentifier PluginId { get; set; }
public string InputData { get; set; }
public ExecutionConfiguration Configuration { get; set; } = new();
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1);
}
public class PluginIdentifier
{
public string Namespace { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public override string ToString() => $"{Namespace}.{Name}@{Version}";
}
}
// File: CRPCF.Orchestrators.Kubernetes/KubernetesOrchestrator.cs
using k8s;
using k8s.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace CRPCF.Orchestrators.Kubernetes
{
public class KubernetesOrchestrator : IContainerOrchestrator
{
private readonly IKubernetesClient _client;
private readonly KubernetesOptions _options;
private readonly ILogger<KubernetesOrchestrator> _logger;
public string PlatformName => "Kubernetes";
public KubernetesOrchestrator(
IKubernetesClient client,
IOptions<KubernetesOptions> options,
ILogger<KubernetesOrchestrator> logger)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<ContainerInstance> CreateContainerAsync(
ContainerSpec spec,
CancellationToken cancellationToken = default)
{
var podName = GeneratePodName(spec);
var pod = CreatePodDefinition(spec, podName);
try
{
var createdPod = await _client.CreateNamespacedPodAsync(
pod,
_options.Namespace,
cancellationToken: cancellationToken);
_logger.LogInformation("Created pod {PodName} in namespace {Namespace}",
podName, _options.Namespace);
// Wait for pod to be ready
await WaitForPodReadyAsync(podName, cancellationToken);
return new KubernetesContainerInstance
{
PodName = podName,
Namespace = _options.Namespace,
CreatedAt = DateTime.UtcNow,
Status = ContainerStatus.Running,
Labels = spec.Labels
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create pod {PodName}", podName);
throw new ContainerOrchestrationException(
$"Failed to create container: {ex.Message}", ex);
}
}
public async Task<ExecutionResult> ExecuteAsync(
ContainerInstance container,
ExecutionRequest request,
CancellationToken cancellationToken = default)
{
if (container is not KubernetesContainerInstance k8sContainer)
throw new ArgumentException("Invalid container instance type");
try
{
var execCommand = BuildExecutionCommand(request);
var execResult = await _client.NamespacedPodExecAsync(
k8sContainer.PodName,
k8sContainer.Namespace,
"plugin", // container name
execCommand,
tty: false,
cancellationToken: cancellationToken);
return new ExecutionResult
{
Success = execResult.ExitCode == 0,
Output = execResult.StandardOutput,
Error = execResult.StandardError,
ExitCode = execResult.ExitCode,
ExecutionTime = execResult.ExecutionTime
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to execute command in pod {PodName}",
k8sContainer.PodName);
return new ExecutionResult
{
Success = false,
Error = ex.Message,
ExitCode = -1
};
}
}
private V1Pod CreatePodDefinition(ContainerSpec spec, string podName)
{
return new V1Pod
{
Metadata = new V1ObjectMeta
{
Name = podName,
NamespaceProperty = _options.Namespace,
Labels = new Dictionary<string, string>(spec.Labels)
{
["crpcf.plugin"] = "true",
["crpcf.created-by"] = "crpcf-orchestrator"
}
},
Spec = new V1PodSpec
{
RestartPolicy = "Never",
ServiceAccountName = _options.ServiceAccount,
SecurityContext = new V1PodSecurityContext
{
RunAsNonRoot = spec.Security.RunAsNonRoot,
FsGroup = 65534 // nobody group
},
Containers = new List<V1Container>
{
new V1Container
{
Name = "plugin",
Image = $"{spec.ImageName}:{spec.ImageTag}",
ImagePullPolicy = "Always",
Env = spec.EnvironmentVariables.Select(kv =>
new V1EnvVar { Name = kv.Key, Value = kv.Value }).ToList(),
Resources = new V1ResourceRequirements
{
Limits = new Dictionary<string, ResourceQuantity>
{
["memory"] = new ResourceQuantity($"{spec.Resources.MemoryLimitBytes}"),
["cpu"] = new ResourceQuantity($"{spec.Resources.CpuLimit}")
},
Requests = new Dictionary<string, ResourceQuantity>
{
["memory"] = new ResourceQuantity($"{spec.Resources.MemoryLimitBytes / 2}"),
["cpu"] = new ResourceQuantity($"{spec.Resources.CpuLimit / 2}")
}
},
SecurityContext = new V1SecurityContext
{
ReadOnlyRootFilesystem = spec.Security.ReadOnlyFileSystem,
RunAsNonRoot = spec.Security.RunAsNonRoot,
AllowPrivilegeEscalation = spec.Security.AllowPrivilegeEscalation,
Capabilities = new V1Capabilities
{
Drop = new List<string> { "ALL" },
Add = spec.Security.AllowedCapabilities.ToList()
}
}
}
}
}
};
}
private async Task WaitForPodReadyAsync(string podName, CancellationToken cancellationToken)
{
const int maxAttempts = 30;
const int delayMs = 2000;
for (int attempt = 0; attempt < maxAttempts; attempt++)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
var pod = await _client.ReadNamespacedPodAsync(
podName,
_options.Namespace,
cancellationToken: cancellationToken);
if (IsPodReady(pod))
{
_logger.LogInformation("Pod {PodName} is ready", podName);
return;
}
if (IsPodFailed(pod))
{
throw new ContainerOrchestrationException(
$"Pod {podName} failed to start: {pod.Status.Phase}");
}
}
catch (Exception ex) when (!(ex is ContainerOrchestrationException))
{
_logger.LogWarning(ex, "Error checking pod status, attempt {Attempt}", attempt + 1);
}
await Task.Delay(delayMs, cancellationToken);
}
throw new ContainerOrchestrationException(
$"Timeout waiting for pod {podName} to become ready");
}
private static bool IsPodReady(V1Pod pod)
{
return pod.Status?.Conditions?.Any(c =>
c.Type == "Ready" && c.Status == "True") == true;
}
private static bool IsPodFailed(V1Pod pod)
{
return pod.Status?.Phase == "Failed";
}
private static string GeneratePodName(ContainerSpec spec)
{
var prefix = spec.Labels.TryGetValue("crpcf.plugin-name", out var pluginName)
? $"plugin-{pluginName}"
: "plugin";
return $"{prefix}-{Guid.NewGuid():N}"[..63]; // K8s name limit
}
private static string[] BuildExecutionCommand(ExecutionRequest request)
{
// This would be customized based on the plugin execution protocol
return new[]
{
"/bin/sh",
"-c",
$"echo '{request.InputData}' | /app/plugin-executor"
};
}
}
public class KubernetesOptions
{
public string Namespace { get; set; } = "crpcf-plugins";
public string ServiceAccount { get; set; } = "crpcf-sa";
public string ImagePullSecret { get; set; }
public Dictionary<string, string> NodeSelector { get; set; } = new();
public Dictionary<string, string> Tolerations { get; set; } = new();
}
public class KubernetesContainerInstance : ContainerInstance
{
public string PodName { get; set; }
public string Namespace { get; set; }
}
}
// File: CRPCF.Security/NuGetSignatureValidator.cs
using System.Security.Cryptography.X509Certificates;
using NuGet.Common;
using NuGet.Packaging;
using NuGet.Packaging.Signing;
namespace CRPCF.Security
{
public class NuGetSignatureValidator : ISignedPackageManager
{
private readonly ILogger<NuGetSignatureValidator> _logger;
private readonly ITrustedSignersRepository _trustedSignersRepo;
private readonly SignatureValidationSettings _settings;
public NuGetSignatureValidator(
ILogger<NuGetSignatureValidator> logger,
ITrustedSignersRepository trustedSignersRepo,
IOptions<SignatureValidationSettings> settings)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_trustedSignersRepo = trustedSignersRepo ?? throw new ArgumentNullException(nameof(trustedSignersRepo));
_settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings));
}
public async Task<PackageValidationResult> ValidatePackageAsync(
Stream packageStream,
PackageValidationOptions options)
{
var result = new PackageValidationResult();
try
{
// Reset stream position
packageStream.Seek(0, SeekOrigin.Begin);
using var package = new PackageArchiveReader(packageStream);
// Extract package information
result.PackageInfo = await ExtractPackageInfoInternalAsync(package);
// Validate package signature
var signatureValidation = await ValidateSignatureAsync(package);
if (!signatureValidation.IsValid)
{
result.IsValid = false;
result.Errors = signatureValidation.Errors;
return result;
}
// Check if signer is trusted
var signerValidation = await ValidateSignerTrustAsync(
signatureValidation.SignerInfo,
result.PackageInfo.PackageId);
if (!signerValidation.IsValid)
{
result.IsValid = false;
result.Errors = result.Errors.Concat(signerValidation.Errors);
return result;
}
// Validate package integrity
var integrityValidation = await ValidatePackageIntegrityAsync(package);
if (!integrityValidation.IsValid)
{
result.IsValid = false;
result.Errors = result.Errors.Concat(integrityValidation.Errors);
return result;
}
result.IsValid = true;
result.Warnings = signatureValidation.Warnings
.Concat(signerValidation.Warnings)
.Concat(integrityValidation.Warnings);
_logger.LogInformation("Package {PackageId} v{Version} validated successfully",
result.PackageInfo.PackageId, result.PackageInfo.Version);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error validating package");
return new PackageValidationResult
{
IsValid = false,
Errors = new[] { new ValidationError($"Validation error: {ex.Message}") }
};
}
}
public async Task<SignedPackageInfo> ExtractPackageInfoAsync(Stream packageStream)
{
packageStream.Seek(0, SeekOrigin.Begin);
using var package = new PackageArchiveReader(packageStream);
return await ExtractPackageInfoInternalAsync(package);
}
private async Task<SignedPackageInfo> ExtractPackageInfoInternalAsync(PackageArchiveReader package)
{
var identity = package.GetIdentity();
var nuspec = await package.GetNuspecAsync(CancellationToken.None);
// Calculate package hash
var packageBytes = await ReadAllBytesAsync(package);
var hash = SHA256.HashData(packageBytes);
return new SignedPackageInfo
{
PackageId = identity.Id,
Version = identity.Version.ToString(),
PackageHash = hash,
PackageSize = packageBytes.Length,
// SignerInfo will be populated during signature validation
};
}
private async Task<SignatureValidationResult> ValidateSignatureAsync(PackageArchiveReader package)
{
try
{
var signatures = await package.GetSignaturesAsync(CancellationToken.None);
if (!signatures.Any())
{
return new SignatureValidationResult
{
IsValid = false,
Errors = new[] { new ValidationError("Package is not signed") }
};
}
foreach (var signature in signatures)
{
var validationResult = await ValidateIndividualSignatureAsync(signature);
if (validationResult.IsValid)
{
return validationResult; // First valid signature wins
}
}
return new SignatureValidationResult
{
IsValid = false,
Errors = new[] { new ValidationError("No valid signatures found") }
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during signature validation");
return new SignatureValidationResult
{
IsValid = false,
Errors = new[] { new ValidationError($"Signature validation error: {ex.Message}") }
};
}
}
private async Task<SignatureValidationResult> ValidateIndividualSignatureAsync(Signature signature)
{
var result = new SignatureValidationResult();
try
{
// Validate signature integrity
var verificationResult = signature.Verify();
if (verificationResult.Status != SignatureVerificationStatus.Valid)
{
result.IsValid = false;
result.Errors = new[] { new ValidationError($"Signature verification failed: {verificationResult.Status}") };
return result;
}
// Extract signer information
var primarySignature = signature.PrimarySignature;
var certificate = primarySignature.SignerInfo.Certificate;
result.SignerInfo = new SignerInfo
{
SubjectName = certificate.Subject,
CertificateThumbprint = certificate.Thumbprint,
Certificate = certificate,
ValidFrom = certificate.NotBefore,
ValidTo = certificate.NotAfter
};
// Check certificate validity
if (result.SignerInfo.IsExpired)
{
result.IsValid = false;
result.Errors = new[] { new ValidationError("Signing certificate has expired") };
return result;
}
// Additional certificate chain validation
if (_settings.ValidateCertificateChain)
{
var chainValidation = ValidateCertificateChain(certificate);
if (!chainValidation.IsValid)
{
result.Warnings = chainValidation.Warnings;
if (_settings.RequireValidCertificateChain)
{
result.IsValid = false;
result.Errors = chainValidation.Errors;
return result;
}
}
}
result.IsValid = true;
return result;
}
catch (Exception ex)
{
return new SignatureValidationResult
{
IsValid = false,
Errors = new[] { new ValidationError($"Signature validation error: {ex.Message}") }
};
}
}
public async Task<bool> IsSignerTrustedAsync(SignerInfo signer, string packageId)
{
var trustedSigners = await _trustedSignersRepo.GetTrustedSignersAsync();
var matchingSigner = trustedSigners.FirstOrDefault(ts =>
ts.CertificateThumbprint.Equals(signer.CertificateThumbprint, StringComparison.OrdinalIgnoreCase));
if (matchingSigner == null)
{
_logger.LogWarning("Signer {SubjectName} ({Thumbprint}) not found in trusted signers list",
signer.SubjectName, signer.CertificateThumbprint);
return false;
}
// Check if package matches allowed patterns
if (matchingSigner.AllowedPackagePatterns?.Any() == true)
{
var isPackageAllowed = matchingSigner.AllowedPackagePatterns.Any(pattern =>
IsPackageMatchingPattern(packageId, pattern));
if (!isPackageAllowed)
{
_logger.LogWarning("Package {PackageId} not allowed for signer {SignerName}",
packageId, matchingSigner.SignerName);
return false;
}
}
_logger.LogInformation("Signer {SignerName} is trusted for package {PackageId}",
matchingSigner.SignerName, packageId);
return true;
}
private static bool IsPackageMatchingPattern(string packageId, string pattern)
{
// Convert wildcard pattern to regex
var regexPattern = "^" + pattern.Replace("*", ".*").Replace("?", ".") + "$";
return Regex.IsMatch(packageId, regexPattern, RegexOptions.IgnoreCase);
}
// Additional helper methods...
}
public class SignatureValidationSettings
{
public bool ValidateCertificateChain { get; set; } = true;
public bool RequireValidCertificateChain { get; set; } = false;
public bool CheckCertificateRevocation { get; set; } = true;
public TimeSpan ValidationTimeout { get; set; } = TimeSpan.FromSeconds(30);
public bool AllowSelfSignedCertificates { get; set; } = false;
}
}
// File: CRPCF.Core/PluginOrchestrator.cs
namespace CRPCF.Core
{
public class PluginOrchestrator : IPluginOrchestrator
{
private readonly ISignedPackageManager _packageManager;
private readonly IEnumerable<IContainerOrchestrator> _orchestrators;
private readonly IPluginRepository _pluginRepository;
private readonly ILogger<PluginOrchestrator> _logger;
private readonly OrchestratorOptions _options;
private readonly ConcurrentDictionary<string, PluginDeployment> _deployments = new();
public event EventHandler<PluginEventArgs> PluginDeployed;
public event EventHandler<PluginEventArgs> PluginExecuted;
public event EventHandler<PluginEventArgs> PluginFailed;
public event EventHandler<PluginEventArgs> PluginUndeployed;
public PluginOrchestrator(
ISignedPackageManager packageManager,
IEnumerable<IContainerOrchestrator> orchestrators,
IPluginRepository pluginRepository,
ILogger<PluginOrchestrator> logger,
IOptions<OrchestratorOptions> options)
{
_packageManager = packageManager ?? throw new ArgumentNullException(nameof(packageManager));
_orchestrators = orchestrators ?? throw new ArgumentNullException(nameof(orchestrators));
_pluginRepository = pluginRepository ?? throw new ArgumentNullException(nameof(pluginRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public async Task<PluginDeploymentResult> DeployPluginAsync(
DeploymentRequest request,
CancellationToken cancellationToken = default)
{
var deploymentId = Guid.NewGuid().ToString();
try
{
_logger.LogInformation("Starting plugin deployment {DeploymentId}", deploymentId);
// Step 1: Validate package signature
var validationResult = await _packageManager.ValidatePackageAsync(
request.PackageStream,
new PackageValidationOptions());
if (!validationResult.IsValid)
{
var errorMessage = string.Join("; ", validationResult.Errors.Select(e => e.Message));
_logger.LogError("Package validation failed for deployment {DeploymentId}: {Errors}",
deploymentId, errorMessage);
return new PluginDeploymentResult
{
Success = false,
DeploymentId = deploymentId,
Errors = validationResult.Errors.Select(e => e.Message).ToList()
};
}
var packageInfo = validationResult.PackageInfo;
var pluginId = new PluginIdentifier
{
Namespace = packageInfo.PackageId.Split('.')[0],
Name = packageInfo.PackageId,
Version = packageInfo.Version
};
// Step 2: Store package
await _pluginRepository.StorePackageAsync(request.PackageStream, packageInfo);
// Step 3: Select container orchestrator
var orchestrator = GetOrchestrator(request.TargetPlatform);
// Step 4: Build container specification
var containerSpec = BuildContainerSpec(packageInfo, request.Configuration);
// Step 5: Deploy container
var container = await orchestrator.CreateContainerAsync(containerSpec, cancellationToken);
// Step 6: Store deployment information
var deployment = new PluginDeployment
{
DeploymentId = deploymentId,
PluginId = pluginId,
PackageInfo = packageInfo,
Container = container,
Orchestrator = orchestrator,
DeployedAt = DateTime.UtcNow,
Status = PluginDeploymentStatus.Deployed
};
_deployments[pluginId.ToString()] = deployment;
await _pluginRepository.SaveDeploymentAsync(deployment);
_logger.LogInformation("Plugin {PluginId} deployed successfully as {DeploymentId}",
pluginId, deploymentId);
// Raise event
PluginDeployed?.Invoke(this, new PluginEventArgs { PluginId = pluginId, DeploymentId = deploymentId });
return new PluginDeploymentResult
{
Success = true,
DeploymentId = deploymentId,
PluginId = pluginId,
ContainerInfo = new ContainerInfo
{
Platform = orchestrator.PlatformName,
InstanceId = container.Id,
Status = container.Status.ToString()
}
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to deploy plugin for deployment {DeploymentId}", deploymentId);
PluginFailed?.Invoke(this, new PluginEventArgs
{
DeploymentId = deploymentId,
Error = ex.Message
});
return new PluginDeploymentResult
{
Success = false,
DeploymentId = deploymentId,
Errors = new[] { ex.Message }
};
}
}
public async Task<PluginExecutionResult> ExecutePluginAsync(
PluginExecutionRequest request,
CancellationToken cancellationToken = default)
{
var executionId = Guid.NewGuid().ToString();
try
{
_logger.LogInformation("Starting plugin execution {ExecutionId} for plugin {PluginId}",
executionId, request.PluginId);
// Find deployment
if (!_deployments.TryGetValue(request.PluginId.ToString(), out var deployment))
{
return new PluginExecutionResult
{
Success = false,
ExecutionId = executionId,
Error = $"Plugin {request.PluginId} is not deployed"
};
}
// Check container health
var containerStatus = await deployment.Orchestrator.GetStatusAsync(
deployment.Container, cancellationToken);
if (containerStatus.Status != ContainerStatus.Running)
{
return new PluginExecutionResult
{
Success = false,
ExecutionId = executionId,
Error = $"Container is not running. Status: {containerStatus.Status}"
};
}
// Execute plugin
var executionRequest = new ExecutionRequest
{
InputData = request.InputData,
Configuration = request.Configuration,
Timeout = request.Timeout
};
var startTime = DateTime.UtcNow;
var executionResult = await deployment.Orchestrator.ExecuteAsync(
deployment.Container,
executionRequest,
cancellationToken);
var endTime = DateTime.UtcNow;
var result = new PluginExecutionResult
{
Success = executionResult.Success,
ExecutionId = executionId,
Output = executionResult.Output,
Error = executionResult.Error,
ExecutionTime = endTime - startTime,
ExitCode = executionResult.ExitCode
};
_logger.LogInformation("Plugin execution {ExecutionId} completed. Success: {Success}, Duration: {Duration}ms",
executionId, result.Success, result.ExecutionTime.TotalMilliseconds);
// Raise event
PluginExecuted?.Invoke(this, new PluginEventArgs
{
PluginId = request.PluginId,
ExecutionId = executionId,
Success = result.Success
});
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to execute plugin {PluginId} for execution {ExecutionId}",
request.PluginId, executionId);
PluginFailed?.Invoke(this, new PluginEventArgs
{
PluginId = request.PluginId,
ExecutionId = executionId,
Error = ex.Message
});
return new PluginExecutionResult
{
Success = false,
ExecutionId = executionId,
Error = ex.Message
};
}
}
private IContainerOrchestrator GetOrchestrator(string targetPlatform)
{
var orchestrator = _orchestrators.FirstOrDefault(o =>
o.PlatformName.Equals(targetPlatform, StringComparison.OrdinalIgnoreCase));
if (orchestrator == null)
{
orchestrator = _orchestrators.FirstOrDefault(o =>
o.PlatformName.Equals(_options.DefaultPlatform, StringComparison.OrdinalIgnoreCase));
}
return orchestrator ?? _orchestrators.First();
}
private ContainerSpec BuildContainerSpec(SignedPackageInfo packageInfo, DeploymentConfiguration config)
{
return new ContainerSpec
{
ImageName = _options.BaseImageName,
ImageTag = _options.BaseImageTag,
Labels = new Dictionary<string, string>
{
["crpcf.plugin-id"] = packageInfo.PackageId,
["crpcf.plugin-version"] = packageInfo.Version,
["crpcf.deployment-time"] = DateTime.UtcNow.ToString("O")
},
Resources = new ContainerResources
{
MemoryLimitBytes = config.MemoryLimitMB * 1024 * 1024,
CpuLimit = config.CpuLimit,
DiskLimitBytes = config.DiskLimitMB * 1024 * 1024
},
Security = new ContainerSecurity
{
ReadOnlyFileSystem = config.ReadOnlyFileSystem,
RunAsNonRoot = true,
AllowPrivilegeEscalation = false,
NetworkPolicy = config.NetworkPolicy
},
EnvironmentVariables = config.EnvironmentVariables ?? new Dictionary<string, string>(),
ExecutionTimeout = TimeSpan.FromMinutes(config.ExecutionTimeoutMinutes)
};
}
}
public class OrchestratorOptions
{
public string DefaultPlatform { get; set; } = "Kubernetes";
public string BaseImageName { get; set; } = "crpcf/plugin-runtime";
public string BaseImageTag { get; set; } = "latest";
}
}
// File: CRPCF.Core/Configuration/CRPCFConfiguration.cs
namespace CRPCF.Core.Configuration
{
public class CRPCFConfiguration
{
public OrchestratorConfiguration Orchestrator { get; set; } = new();
public SecurityConfiguration Security { get; set; } = new();
public PluginStoreConfiguration PluginStore { get; set; } = new();
public ContainerOrchestrationConfiguration ContainerOrchestration { get; set; } = new();
public MonitoringConfiguration Monitoring { get; set; } = new();
}
public class SecurityConfiguration
{
public bool RequireSignedPackages { get; set; } = true;
public TimeSpan SignatureValidationTimeout { get; set; } = TimeSpan.FromSeconds(30);
public string TrustedSignersConfigPath { get; set; } = "/config/trusted-signers.json";
public bool ValidateCertificateChain { get; set; } = true;
public bool RequireValidCertificateChain { get; set; } = false;
public bool CheckCertificateRevocation { get; set; } = true;
}
public class PluginStoreConfiguration
{
public string Type { get; set; } = "filesystem";
public string Path { get; set; } = "/data/plugins";
public int MaxSizeGB { get; set; } = 100;
public CleanupPolicyConfiguration CleanupPolicy { get; set; } = new();
}
public class CleanupPolicyConfiguration
{
public int MaxAgeDays { get; set; } = 30;
public int MaxVersionsPerPlugin { get; set; } = 5;
}
}
This implementation guide provides a solid foundation for building the Containerized RuntimePluggableClassFactory with all the security, orchestration, and management capabilities outlined in the Technical Design Specification.