139 lines
4.6 KiB
C#
139 lines
4.6 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Plugin;
|
|
using StellaOps.Plugin.Hosting;
|
|
using StellaOps.Scanner.Analyzers.OS.Abstractions;
|
|
using StellaOps.Scanner.Core.Security;
|
|
|
|
namespace StellaOps.Scanner.Analyzers.OS.Plugin;
|
|
|
|
public sealed class OsAnalyzerPluginCatalog
|
|
{
|
|
private readonly ILogger<OsAnalyzerPluginCatalog> _logger;
|
|
private readonly IPluginCatalogGuard _guard;
|
|
private readonly ConcurrentDictionary<string, Assembly> _assemblies = new(StringComparer.OrdinalIgnoreCase);
|
|
private IReadOnlyList<IOSAnalyzerPlugin> _plugins = Array.Empty<IOSAnalyzerPlugin>();
|
|
|
|
public OsAnalyzerPluginCatalog(IPluginCatalogGuard guard, ILogger<OsAnalyzerPluginCatalog> logger)
|
|
{
|
|
_guard = guard ?? throw new ArgumentNullException(nameof(guard));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
public IReadOnlyCollection<IOSAnalyzerPlugin> Plugins => _plugins;
|
|
|
|
public void LoadFromDirectory(string directory, bool seal = true)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(directory);
|
|
var fullDirectory = Path.GetFullPath(directory);
|
|
|
|
var options = new PluginHostOptions
|
|
{
|
|
PluginsDirectory = fullDirectory,
|
|
EnsureDirectoryExists = false,
|
|
RecursiveSearch = false,
|
|
};
|
|
options.SearchPatterns.Add("StellaOps.Scanner.Analyzers.*.dll");
|
|
|
|
var result = PluginHost.LoadPlugins(options, _logger);
|
|
if (result.Plugins.Count == 0)
|
|
{
|
|
_logger.LogWarning("No OS analyzer plug-ins discovered under '{Directory}'.", fullDirectory);
|
|
}
|
|
|
|
foreach (var descriptor in result.Plugins)
|
|
{
|
|
try
|
|
{
|
|
_guard.EnsureRegistrationAllowed(descriptor.AssemblyPath);
|
|
_assemblies[descriptor.AssemblyPath] = descriptor.Assembly;
|
|
_logger.LogInformation("Registered OS analyzer plug-in assembly '{Assembly}' from '{Path}'.",
|
|
descriptor.Assembly.FullName,
|
|
descriptor.AssemblyPath);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to register analyzer plug-in '{Path}'.", descriptor.AssemblyPath);
|
|
}
|
|
}
|
|
|
|
RefreshPluginList();
|
|
|
|
if (seal)
|
|
{
|
|
_guard.Seal();
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<IOSPackageAnalyzer> CreateAnalyzers(IServiceProvider services)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
if (_plugins.Count == 0)
|
|
{
|
|
_logger.LogWarning("No OS analyzer plug-ins available; scanning will skip OS package extraction.");
|
|
return Array.Empty<IOSPackageAnalyzer>();
|
|
}
|
|
|
|
var analyzers = new List<IOSPackageAnalyzer>(_plugins.Count);
|
|
foreach (var plugin in _plugins)
|
|
{
|
|
if (!IsPluginAvailable(plugin, services))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
var analyzer = plugin.CreateAnalyzer(services);
|
|
if (analyzer is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
analyzers.Add(analyzer);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Analyzer plug-in '{Plugin}' failed to create analyzer instance.", plugin.Name);
|
|
}
|
|
}
|
|
|
|
if (analyzers.Count == 0)
|
|
{
|
|
_logger.LogWarning("All OS analyzer plug-ins were unavailable.");
|
|
return Array.Empty<IOSPackageAnalyzer>();
|
|
}
|
|
|
|
analyzers.Sort(static (a, b) => string.CompareOrdinal(a.AnalyzerId, b.AnalyzerId));
|
|
return new ReadOnlyCollection<IOSPackageAnalyzer>(analyzers);
|
|
}
|
|
|
|
private void RefreshPluginList()
|
|
{
|
|
var assemblies = _assemblies.Values.ToArray();
|
|
var plugins = PluginLoader.LoadPlugins<IOSAnalyzerPlugin>(assemblies);
|
|
_plugins = plugins is IReadOnlyList<IOSAnalyzerPlugin> list
|
|
? list
|
|
: new ReadOnlyCollection<IOSAnalyzerPlugin>(plugins.ToArray());
|
|
}
|
|
|
|
private static bool IsPluginAvailable(IOSAnalyzerPlugin plugin, IServiceProvider services)
|
|
{
|
|
try
|
|
{
|
|
return plugin.IsAvailable(services);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|