315 lines
11 KiB
C#
Executable File
315 lines
11 KiB
C#
Executable File
using MarketAlly.AIPlugin;
|
|
using MarketAlly.AIPlugin.DevOps.Performance;
|
|
using MarketAlly.AIPlugin.DevOps.Security;
|
|
using Microsoft.Extensions.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MarketAlly.AIPlugin.DevOps.Core
|
|
{
|
|
public abstract class BaseDevOpsPlugin : IAIPlugin
|
|
{
|
|
protected readonly ILogger _logger;
|
|
protected readonly AnalysisCache _cache;
|
|
protected readonly AuditLogger _auditLogger;
|
|
protected readonly RateLimiter _rateLimiter;
|
|
protected readonly CryptographicValidator _cryptoValidator;
|
|
|
|
protected BaseDevOpsPlugin(ILogger logger = null)
|
|
{
|
|
_logger = logger;
|
|
_cache = new AnalysisCache(logger as ILogger<AnalysisCache>);
|
|
_auditLogger = new AuditLogger(logger as ILogger<AuditLogger>);
|
|
_rateLimiter = new RateLimiter(_auditLogger);
|
|
_cryptoValidator = new CryptographicValidator(_auditLogger);
|
|
}
|
|
|
|
public abstract IReadOnlyDictionary<string, Type> SupportedParameters { get; }
|
|
|
|
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
var stopwatch = Stopwatch.StartNew();
|
|
var pluginName = GetType().Name;
|
|
|
|
try
|
|
{
|
|
// Rate limiting check
|
|
var clientId = ExtractClientId(parameters);
|
|
if (!await _rateLimiter.TryExecuteAsync(clientId))
|
|
{
|
|
await _auditLogger.LogSecurityEventAsync(new SecurityAuditEvent
|
|
{
|
|
EventType = SecurityEventType.PermissionChecked,
|
|
Severity = SecuritySeverity.Medium,
|
|
Source = pluginName,
|
|
UserId = clientId,
|
|
Details = "Rate limit exceeded"
|
|
});
|
|
|
|
return new AIPluginResult(
|
|
new InvalidOperationException("Rate limit exceeded. Please try again later."),
|
|
"Rate limit exceeded"
|
|
);
|
|
}
|
|
|
|
// Log analysis start
|
|
await _auditLogger.LogSecurityEventAsync(new SecurityAuditEvent
|
|
{
|
|
EventType = SecurityEventType.AnalysisStarted,
|
|
Severity = SecuritySeverity.Low,
|
|
Source = pluginName,
|
|
UserId = clientId,
|
|
Details = "Analysis started"
|
|
});
|
|
|
|
// Validate parameters
|
|
var validationResult = await ValidateParametersAsync(parameters);
|
|
if (!validationResult.IsValid)
|
|
{
|
|
return new AIPluginResult(
|
|
new ArgumentException($"Parameter validation failed: {string.Join(", ", validationResult.Errors)}"),
|
|
"Parameter validation failed"
|
|
);
|
|
}
|
|
|
|
// Execute the actual plugin logic
|
|
var result = await ExecuteInternalAsync(parameters);
|
|
|
|
stopwatch.Stop();
|
|
|
|
// Log completion
|
|
var issuesFound = CountIssuesInResult(result);
|
|
await _auditLogger.LogAnalysisEventAsync(pluginName, GetPrimaryFilePath(parameters), issuesFound, stopwatch.Elapsed);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
stopwatch.Stop();
|
|
|
|
_logger?.LogError(ex, "Plugin execution failed: {PluginName}", pluginName);
|
|
|
|
await _auditLogger.LogSecurityEventAsync(new SecurityAuditEvent
|
|
{
|
|
EventType = SecurityEventType.AnalysisCompleted,
|
|
Severity = SecuritySeverity.High,
|
|
Source = pluginName,
|
|
Details = $"Analysis failed: {ex.Message}",
|
|
Metadata = new Dictionary<string, object>
|
|
{
|
|
["error"] = ex.Message,
|
|
["executionTimeMs"] = stopwatch.ElapsedMilliseconds
|
|
}
|
|
});
|
|
|
|
return new AIPluginResult(ex, $"Failed to execute {pluginName}");
|
|
}
|
|
}
|
|
|
|
protected abstract Task<AIPluginResult> ExecuteInternalAsync(IReadOnlyDictionary<string, object> parameters);
|
|
|
|
protected virtual async Task<ValidationResult> ValidateParametersAsync(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
var result = new ValidationResult();
|
|
|
|
// Basic parameter validation
|
|
foreach (var supportedParam in SupportedParameters)
|
|
{
|
|
if (!parameters.ContainsKey(supportedParam.Key))
|
|
{
|
|
// Check if parameter is required (this would need to be enhanced with attribute checking)
|
|
continue;
|
|
}
|
|
|
|
var value = parameters[supportedParam.Key];
|
|
if (value != null && !supportedParam.Value.IsAssignableFrom(value.GetType()))
|
|
{
|
|
result.AddError(supportedParam.Key,
|
|
$"Parameter '{supportedParam.Key}' must be of type {supportedParam.Value.Name}",
|
|
"TYPE_MISMATCH");
|
|
}
|
|
}
|
|
|
|
return await Task.FromResult(result);
|
|
}
|
|
|
|
protected virtual string ExtractClientId(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
// In a real implementation, this would extract from authentication context
|
|
return Environment.UserName ?? "anonymous";
|
|
}
|
|
|
|
protected virtual string GetPrimaryFilePath(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
// Try common parameter names for file paths
|
|
var pathKeys = new[] { "filePath", "path", "dockerfilePath", "pipelinePath", "configDirectory" };
|
|
|
|
foreach (var key in pathKeys)
|
|
{
|
|
if (parameters.TryGetValue(key, out var value))
|
|
{
|
|
return value?.ToString() ?? "unknown";
|
|
}
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
protected virtual int CountIssuesInResult(AIPluginResult result)
|
|
{
|
|
if (!result.Success || result.Data == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// This is a simplified implementation - would need to be enhanced
|
|
// based on the actual result structure of each plugin
|
|
var data = result.Data;
|
|
var dataType = data.GetType();
|
|
|
|
// Try to find properties that might contain issues/violations
|
|
var issueProperties = new[] { "SecurityIssues", "BestPracticeViolations", "ConfigurationIssues" };
|
|
var totalIssues = 0;
|
|
|
|
foreach (var prop in issueProperties)
|
|
{
|
|
var property = dataType.GetProperty(prop);
|
|
if (property?.GetValue(data) is System.Collections.ICollection collection)
|
|
{
|
|
totalIssues += collection.Count;
|
|
}
|
|
}
|
|
|
|
return totalIssues;
|
|
}
|
|
|
|
protected async Task<T> GetOrSetCacheAsync<T>(string cacheKey, Func<Task<T>> factory, TimeSpan? expiry = null) where T : class
|
|
{
|
|
return await _cache.GetOrSetAsync(cacheKey, factory, expiry);
|
|
}
|
|
|
|
protected async Task<bool> ValidateFileIntegrityAsync(string filePath, string expectedHash = null)
|
|
{
|
|
return await _cryptoValidator.ValidateFileIntegrityAsync(filePath, expectedHash);
|
|
}
|
|
|
|
protected async Task LogSecurityIssueAsync(string issueType, string severity, string details = null)
|
|
{
|
|
await _auditLogger.LogSecurityIssueAsync(GetType().Name, GetType().Name, issueType, severity);
|
|
}
|
|
|
|
protected bool IsFilePathSafe(string filePath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(filePath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Check for path traversal attempts
|
|
var fullPath = Path.GetFullPath(filePath);
|
|
var fileName = Path.GetFileName(fullPath);
|
|
|
|
// Basic safety checks
|
|
if (fileName.StartsWith("..") ||
|
|
filePath.Contains("..") ||
|
|
Path.IsPathRooted(filePath) && !Path.IsPathFullyQualified(filePath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected async Task<bool> EnsureFileExistsAsync(string filePath)
|
|
{
|
|
if (!IsFilePathSafe(filePath))
|
|
{
|
|
await LogSecurityIssueAsync("UnsafeFilePath", "High", $"Unsafe file path detected: {filePath}");
|
|
return false;
|
|
}
|
|
|
|
if (!File.Exists(filePath))
|
|
{
|
|
_logger?.LogWarning("File not found: {FilePath}", filePath);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected async Task<bool> EnsureDirectoryExistsAsync(string directoryPath)
|
|
{
|
|
if (!IsFilePathSafe(directoryPath))
|
|
{
|
|
await LogSecurityIssueAsync("UnsafeDirectoryPath", "High", $"Unsafe directory path detected: {directoryPath}");
|
|
return false;
|
|
}
|
|
|
|
if (!Directory.Exists(directoryPath))
|
|
{
|
|
_logger?.LogWarning("Directory not found: {DirectoryPath}", directoryPath);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
// Cleanup resources if needed
|
|
_cache?.Clear();
|
|
}
|
|
}
|
|
|
|
public class ValidationResult
|
|
{
|
|
public List<ValidationError> Errors { get; } = new();
|
|
public List<ValidationWarning> Warnings { get; } = new();
|
|
public bool IsValid => !Errors.Any();
|
|
|
|
public void AddError(string parameter, string message, string errorCode)
|
|
{
|
|
Errors.Add(new ValidationError
|
|
{
|
|
Parameter = parameter,
|
|
Message = message,
|
|
ErrorCode = errorCode
|
|
});
|
|
}
|
|
|
|
public void AddWarning(string parameter, string message, string warningCode)
|
|
{
|
|
Warnings.Add(new ValidationWarning
|
|
{
|
|
Parameter = parameter,
|
|
Message = message,
|
|
WarningCode = warningCode
|
|
});
|
|
}
|
|
}
|
|
|
|
public class ValidationError
|
|
{
|
|
public string Parameter { get; set; }
|
|
public string Message { get; set; }
|
|
public string ErrorCode { get; set; }
|
|
}
|
|
|
|
public class ValidationWarning
|
|
{
|
|
public string Parameter { get; set; }
|
|
public string Message { get; set; }
|
|
public string WarningCode { get; set; }
|
|
}
|
|
} |