implement remote assembly loading
This commit is contained in:
parent
2bbafbd8f0
commit
08676f1d1e
@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using IW4MAdmin.Application.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using RestEase;
|
||||
using SharedLibraryCore.Helpers;
|
||||
@ -35,6 +37,13 @@ namespace IW4MAdmin.Application.API.Master
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class PluginSubscriptionContent
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public PluginType Type { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defines the capabilities of the master API
|
||||
/// </summary>
|
||||
@ -63,5 +72,8 @@ namespace IW4MAdmin.Application.API.Master
|
||||
|
||||
[Get("localization/{languageTag}")]
|
||||
Task<SharedLibraryCore.Localization.Layout> GetLocalization([Path("languageTag")] string languageTag);
|
||||
|
||||
[Get("plugin_subscriptions")]
|
||||
Task<IEnumerable<PluginSubscriptionContent>> GetPluginSubscription([Query("instance_id")] Guid instanceId, [Query("subscription_id")] string subscription_id);
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ namespace IW4MAdmin.Application
|
||||
await ApplicationTask;
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
|
||||
Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}");
|
||||
@ -226,12 +226,17 @@ namespace IW4MAdmin.Application
|
||||
/// </summary>
|
||||
private static IServiceCollection ConfigureServices(string[] args)
|
||||
{
|
||||
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||
var appConfig = appConfigHandler.Configuration();
|
||||
var defaultLogger = new Logger("IW4MAdmin-Manager");
|
||||
var pluginImporter = new PluginImporter(defaultLogger);
|
||||
|
||||
var masterUri = Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") : appConfig?.MasterUrl ?? new ApplicationConfiguration().MasterUrl;
|
||||
var apiClient = RestClient.For<IMasterApi>(masterUri);
|
||||
var pluginImporter = new PluginImporter(defaultLogger, appConfig, apiClient, new RemoteAssemblyHandler(defaultLogger, appConfig));
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
|
||||
.AddSingleton(new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings") as IConfigurationHandler<ApplicationConfiguration>)
|
||||
.AddSingleton(appConfigHandler as IConfigurationHandler<ApplicationConfiguration>)
|
||||
.AddSingleton(new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as IConfigurationHandler<CommandConfiguration>)
|
||||
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration() ?? new ApplicationConfiguration())
|
||||
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>().Configuration() ?? new CommandConfiguration())
|
||||
@ -255,19 +260,17 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>, UpdatedAliasResourceQueryHelper>()
|
||||
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
|
||||
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
|
||||
.AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>()
|
||||
.AddSingleton<IMasterCommunication, MasterCommunication>()
|
||||
.AddSingleton<IManager, ApplicationManager>()
|
||||
.AddSingleton(apiClient)
|
||||
.AddSingleton(_serviceProvider =>
|
||||
{
|
||||
var config = _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration();
|
||||
return Localization.Configure.Initialize(useLocalTranslation: config?.UseLocalTranslations ?? false,
|
||||
apiInstance: _serviceProvider.GetRequiredService<IMasterApi>(),
|
||||
customLocale: config?.EnableCustomLocale ?? false ? (config.CustomLocale ?? "en-US") : "en-US");
|
||||
})
|
||||
.AddSingleton<IManager, ApplicationManager>()
|
||||
.AddSingleton(_serviceProvider => RestClient
|
||||
.For<IMasterApi>(Utilities.IsDevelopment ? new Uri("http://127.0.0.1:8080") : _serviceProvider
|
||||
.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration()?.MasterUrl ??
|
||||
new ApplicationConfiguration().MasterUrl))
|
||||
.AddSingleton<IMasterCommunication, MasterCommunication>();
|
||||
});
|
||||
|
||||
if (args.Contains("serialevents"))
|
||||
{
|
||||
|
@ -6,6 +6,8 @@ using SharedLibraryCore.Interfaces;
|
||||
using System.Linq;
|
||||
using SharedLibraryCore;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using SharedLibraryCore.Configuration;
|
||||
|
||||
namespace IW4MAdmin.Application.Helpers
|
||||
{
|
||||
@ -15,12 +17,19 @@ namespace IW4MAdmin.Application.Helpers
|
||||
/// </summary>
|
||||
public class PluginImporter : IPluginImporter
|
||||
{
|
||||
private IEnumerable<PluginSubscriptionContent> _pluginSubscription;
|
||||
private static readonly string PLUGIN_DIR = "Plugins";
|
||||
private readonly ILogger _logger;
|
||||
private readonly IRemoteAssemblyHandler _remoteAssemblyHandler;
|
||||
private readonly IMasterApi _masterApi;
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
|
||||
public PluginImporter(ILogger logger)
|
||||
public PluginImporter(ILogger logger, ApplicationConfiguration appConfig, IMasterApi masterApi, IRemoteAssemblyHandler remoteAssemblyHandler)
|
||||
{
|
||||
_logger = logger;
|
||||
_masterApi = masterApi;
|
||||
_remoteAssemblyHandler = remoteAssemblyHandler;
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -33,11 +42,11 @@ namespace IW4MAdmin.Application.Helpers
|
||||
|
||||
if (Directory.Exists(pluginDir))
|
||||
{
|
||||
string[] scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js");
|
||||
var scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts());
|
||||
|
||||
_logger.WriteInfo($"Discovered {scriptPluginFiles.Length} potential script plugins");
|
||||
_logger.WriteInfo($"Discovered {scriptPluginFiles.Count()} potential script plugins");
|
||||
|
||||
if (scriptPluginFiles.Length > 0)
|
||||
if (scriptPluginFiles.Count() > 0)
|
||||
{
|
||||
foreach (string fileName in scriptPluginFiles)
|
||||
{
|
||||
@ -66,7 +75,10 @@ namespace IW4MAdmin.Application.Helpers
|
||||
|
||||
if (dllFileNames.Length > 0)
|
||||
{
|
||||
var assemblies = dllFileNames.Select(_name => Assembly.LoadFrom(_name));
|
||||
// we only want to load the most recent assembly in case of duplicates
|
||||
var assemblies = dllFileNames.Select(_name => Assembly.LoadFrom(_name))
|
||||
.Union(GetRemoteAssemblies())
|
||||
.GroupBy(_assembly => _assembly.FullName).Select(_assembly => _assembly.OrderByDescending(_assembly => _assembly.GetName().Version).First());
|
||||
|
||||
pluginTypes = assemblies
|
||||
.SelectMany(_asm => _asm.GetTypes())
|
||||
@ -84,5 +96,47 @@ namespace IW4MAdmin.Application.Helpers
|
||||
|
||||
return (pluginTypes, commandTypes);
|
||||
}
|
||||
|
||||
private IEnumerable<Assembly> GetRemoteAssemblies()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginSubscription == null)
|
||||
_pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||
|
||||
return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray());
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.WriteWarning("Could not load remote assemblies");
|
||||
_logger.WriteDebug(ex.GetExceptionInfo());
|
||||
return Enumerable.Empty<Assembly>();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetRemoteScripts()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginSubscription == null)
|
||||
_pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
|
||||
|
||||
return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray());
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.WriteWarning("Could not load remote assemblies");
|
||||
_logger.WriteDebug(ex.GetExceptionInfo());
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PluginType
|
||||
{
|
||||
Binary,
|
||||
Script
|
||||
}
|
||||
}
|
||||
|
76
Application/Misc/RemoteAssemblyHandler.cs
Normal file
76
Application/Misc/RemoteAssemblyHandler.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
public class RemoteAssemblyHandler : IRemoteAssemblyHandler
|
||||
{
|
||||
private const int keyLength = 32;
|
||||
private const int tagLength = 16;
|
||||
private const int nonceLength = 12;
|
||||
private const int iterationCount = 10000;
|
||||
|
||||
private readonly ApplicationConfiguration _appconfig;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RemoteAssemblyHandler(ILogger logger, ApplicationConfiguration appconfig)
|
||||
{
|
||||
_appconfig = appconfig;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IEnumerable<Assembly> DecryptAssemblies(string[] encryptedAssemblies)
|
||||
{
|
||||
return DecryptContent(encryptedAssemblies)
|
||||
.Select(decryptedAssembly => Assembly.Load(decryptedAssembly));
|
||||
}
|
||||
|
||||
public IEnumerable<string> DecryptScripts(string[] encryptedScripts)
|
||||
{
|
||||
return DecryptContent(encryptedScripts).Select(decryptedScript => Encoding.UTF8.GetString(decryptedScript));
|
||||
}
|
||||
|
||||
private byte[][] DecryptContent(string[] content)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_appconfig.Id) || string.IsNullOrWhiteSpace(_appconfig.SubscriptionId))
|
||||
{
|
||||
_logger.WriteWarning($"{nameof(_appconfig.Id)} and {nameof(_appconfig.SubscriptionId)} must be provided to attempt loading remote assemblies/scripts");
|
||||
return new byte[0][];
|
||||
}
|
||||
|
||||
var assemblies = content.Select(piece =>
|
||||
{
|
||||
byte[] byteContent = Convert.FromBase64String(piece);
|
||||
byte[] encryptedContent = byteContent.Take(byteContent.Length - (tagLength + nonceLength)).ToArray();
|
||||
byte[] tag = byteContent.Skip(byteContent.Length - (tagLength + nonceLength)).Take(tagLength).ToArray();
|
||||
byte[] nonce = byteContent.Skip(byteContent.Length - nonceLength).Take(nonceLength).ToArray();
|
||||
byte[] decryptedContent = new byte[encryptedContent.Length];
|
||||
|
||||
var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id.ToString()), iterationCount, HashAlgorithmName.SHA512);
|
||||
var encryption = new AesGcm(keyGen.GetBytes(keyLength));
|
||||
|
||||
try
|
||||
{
|
||||
encryption.Decrypt(nonce, encryptedContent, tag, decryptedContent);
|
||||
}
|
||||
|
||||
catch (CryptographicException ex)
|
||||
{
|
||||
_logger.WriteError("Could not obtain remote plugin assemblies");
|
||||
_logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
return decryptedContent;
|
||||
});
|
||||
|
||||
return assemblies.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@ -99,6 +99,8 @@ namespace SharedLibraryCore.Configuration
|
||||
[ConfigurationIgnore]
|
||||
public string Id { get; set; }
|
||||
[ConfigurationIgnore]
|
||||
public string SubscriptionId { get; set; }
|
||||
[ConfigurationIgnore]
|
||||
public MapConfiguration[] Maps { get; set; }
|
||||
[ConfigurationIgnore]
|
||||
public QuickMessageConfiguration[] QuickMessages { get; set; }
|
||||
|
11
SharedLibraryCore/Interfaces/IRemoteAssemblyHandler.cs
Normal file
11
SharedLibraryCore/Interfaces/IRemoteAssemblyHandler.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
public interface IRemoteAssemblyHandler
|
||||
{
|
||||
IEnumerable<Assembly> DecryptAssemblies(string[] encryptedAssemblies);
|
||||
IEnumerable<string> DecryptScripts(string[] encryptedScripts);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user