我正在寻找一种简单和安全的方式来访问插件从.NET应用程序。虽然我认为这是一个非常常见的需求,但我正在努力寻找任何满足我所有需求的东西:
的形式交换数据
我已经研究了MEF和MAF,但我正在努力寻找它们中的任何一个如何才能符合要求。
假设我的理解是正确的,MAF无法支持跨其隔离边界传递泛型类型,而这对于我的应用程序来说是必不可少的。(MAF的实现也非常复杂,但如果我能解决泛型类型问题,我会准备好使用它)。
MEF几乎是一个完美的解决方案,但似乎没有达到安全要求,因为它在与宿主相同的AppDomain中加载其扩展程序集,因此显然阻止了沙箱。
我看过this question,它谈到了在沙盒模式下运行MEF,但没有描述如何运行。This post声明“当使用MEF时,你必须信任扩展不会运行恶意代码,或者通过代码访问安全提供保护”,但它再次没有描述如何运行。最后是this post,它描述了如何防止加载未知的插件,但这并不适合我的情况,因为即使是合法的插件也是未知的。
我已经成功地将.NET 4.0安全属性应用于我的程序集,并且它们得到了MEF4.0的正确尊重,但我看不出这如何帮助我锁定恶意代码,因为许多可能存在安全威胁的框架方法(如System.IO.File的方法)被标记为SecuritySafeCritical,这意味着它们可以从SecurityTransparent程序集访问。我是不是漏掉了什么?我可以采取什么额外的步骤来告诉MEF它应该为插件程序集提供互联网特权吗?
最后,我还介绍了如何使用here中描述的独立AppDomain创建自己的简单沙盒插件体系结构。但是,据我所知,这种技术只允许我使用后期绑定来调用不受信任的程序集中的类的静态方法。当我尝试扩展这种方法来创建我的一个插件类的实例时,返回的实例不能强制转换为公共插件接口,这意味着宿主应用程序不可能调用它。有没有什么技术可以用来获得跨AppDomain边界的强类型代理访问?
我为这个问题的冗长道歉;原因是为了展示我已经调查过的所有途径,希望有人能提出一些新的尝试。
非常感谢你的想法,蒂姆
发布于 2010-11-11 02:19:45
因为您在不同的AppDomains中,所以不能简单地传递实例。
您将需要使您的插件可远程,并在您的主应用程序中创建一个代理。请看一下CreateInstanceAndUnWrap的文档,其中有一个示例,说明了所有这些如何在底层工作。
这也是另一个更广泛的overview by Jon Shemitz,我认为它是一个很好的读物。祝好运。
发布于 2010-11-12 01:24:13
我已经接受了Alastair Maw的回答,因为正是他的建议和链接让我找到了一个可行的解决方案,但我在这里发布了我所做的一些细节,供其他可能试图实现类似目标的人参考。
提醒一下,我的应用程序最简单的形式由三个程序集组成:
下面的代码是我的真实代码的简化版本,仅显示了发现和加载插件所需的内容,每个插件都在其自己的AppDomain中
从主应用程序程序集开始,主程序类使用一个名为PluginFinder的实用程序类来发现指定插件文件夹中任何程序集内的合格插件类型。然后,对于这些类型中的每一种,它都会创建一个sandox插件的实例(具有internet区域权限),并使用它来创建所发现的AppDomain类型的实例。
创建具有有限权限的AppDomain时,可以指定一个或多个不受这些权限约束的受信任程序集。若要在此处提供的方案中完成此操作,必须对主应用程序程序集及其依赖项(互操作程序集)进行签名。
对于每个加载的插件实例,插件内的自定义方法可以通过其已知接口调用,并且插件还可以通过其已知接口回调主机应用程序。最后,主机应用程序卸载每个沙箱域。
class Program
{
static void Main()
{
var domains = new List<AppDomain>();
var plugins = new List<PluginBase>();
var types = PluginFinder.FindPlugins();
var host = new Host();
foreach (var type in types)
{
var domain = CreateSandboxDomain("Sandbox Domain", PluginFinder.PluginPath, SecurityZone.Internet);
plugins.Add((PluginBase)domain.CreateInstanceAndUnwrap(type.AssemblyName, type.TypeName));
domains.Add(domain);
}
foreach (var plugin in plugins)
{
plugin.Initialize(host);
plugin.SaySomething();
plugin.CallBackToHost();
// To prove that the sandbox security is working we can call a plugin method that does something
// dangerous, which throws an exception because the plugin assembly has insufficient permissions.
//plugin.DoSomethingDangerous();
}
foreach (var domain in domains)
{
AppDomain.Unload(domain);
}
Console.ReadLine();
}
/// <summary>
/// Returns a new <see cref="AppDomain"/> according to the specified criteria.
/// </summary>
/// <param name="name">The name to be assigned to the new instance.</param>
/// <param name="path">The root folder path in which assemblies will be resolved.</param>
/// <param name="zone">A <see cref="SecurityZone"/> that determines the permission set to be assigned to this instance.</param>
/// <returns></returns>
public static AppDomain CreateSandboxDomain(
string name,
string path,
SecurityZone zone)
{
var setup = new AppDomainSetup { ApplicationBase = Path.GetFullPath(path) };
var evidence = new Evidence();
evidence.AddHostEvidence(new Zone(zone));
var permissions = SecurityManager.GetStandardSandbox(evidence);
var strongName = typeof(Program).Assembly.Evidence.GetHostEvidence<StrongName>();
return AppDomain.CreateDomain(name, null, setup, permissions, strongName);
}
}在此示例代码中,宿主应用程序类非常简单,只公开了一个可由插件调用的方法。但是,此类必须从MarshalByRefObject派生,以便可以在应用程序域之间引用它。
/// <summary>
/// The host class that exposes functionality that plugins may call.
/// </summary>
public class Host : MarshalByRefObject, IHost
{
public void SaySomething()
{
Console.WriteLine("This is the host executing a method invoked by a plugin");
}
}PluginFinder类只有一个返回已发现插件类型列表的公共方法。此发现过程加载它找到的每个程序集,并使用反射来标识其限定类型。由于此过程可能会加载许多程序集(其中一些程序集甚至不包含插件类型),因此它也在单独的应用程序域中执行,该应用程序域可能会被卸载。请注意,由于上述原因,该类也继承了MarshalByRefObject。由于Type的实例可能不会在应用程序域之间传递,因此此发现过程使用名为TypeLocator的自定义类型来存储每个发现的类型的字符串名称和程序集名称,然后可以将其安全地传递回主应用程序域。
/// <summary>
/// Safely identifies assemblies within a designated plugin directory that contain qualifying plugin types.
/// </summary>
internal class PluginFinder : MarshalByRefObject
{
internal const string PluginPath = @"..\..\..\Plugins\Output";
private readonly Type _pluginBaseType;
/// <summary>
/// Initializes a new instance of the <see cref="PluginFinder"/> class.
/// </summary>
public PluginFinder()
{
// For some reason, compile-time types are not reference equal to the corresponding types referenced
// in each plugin assembly, so equality must be tested by loading types by name from the Interop assembly.
var interopAssemblyFile = Path.GetFullPath(Path.Combine(PluginPath, typeof(PluginBase).Assembly.GetName().Name) + ".dll");
var interopAssembly = Assembly.LoadFrom(interopAssemblyFile);
_pluginBaseType = interopAssembly.GetType(typeof(PluginBase).FullName);
}
/// <summary>
/// Returns the name and assembly name of qualifying plugin classes found in assemblies within the designated plugin directory.
/// </summary>
/// <returns>An <see cref="IEnumerable{TypeLocator}"/> that represents the qualifying plugin types.</returns>
public static IEnumerable<TypeLocator> FindPlugins()
{
AppDomain domain = null;
try
{
domain = AppDomain.CreateDomain("Discovery Domain");
var finder = (PluginFinder)domain.CreateInstanceAndUnwrap(typeof(PluginFinder).Assembly.FullName, typeof(PluginFinder).FullName);
return finder.Find();
}
finally
{
if (domain != null)
{
AppDomain.Unload(domain);
}
}
}
/// <summary>
/// Surveys the configured plugin path and returns the the set of types that qualify as plugin classes.
/// </summary>
/// <remarks>
/// Since this method loads assemblies, it must be called from within a dedicated application domain that is subsequently unloaded.
/// </remarks>
private IEnumerable<TypeLocator> Find()
{
var result = new List<TypeLocator>();
foreach (var file in Directory.GetFiles(Path.GetFullPath(PluginPath), "*.dll"))
{
try
{
var assembly = Assembly.LoadFrom(file);
foreach (var type in assembly.GetExportedTypes())
{
if (!type.Equals(_pluginBaseType) &&
_pluginBaseType.IsAssignableFrom(type))
{
result.Add(new TypeLocator(assembly.FullName, type.FullName));
}
}
}
catch (Exception e)
{
// Ignore DLLs that are not .NET assemblies.
}
}
return result;
}
}
/// <summary>
/// Encapsulates the assembly name and type name for a <see cref="Type"/> in a serializable format.
/// </summary>
[Serializable]
internal class TypeLocator
{
/// <summary>
/// Initializes a new instance of the <see cref="TypeLocator"/> class.
/// </summary>
/// <param name="assemblyName">The name of the assembly containing the target type.</param>
/// <param name="typeName">The name of the target type.</param>
public TypeLocator(
string assemblyName,
string typeName)
{
if (string.IsNullOrEmpty(assemblyName)) throw new ArgumentNullException("assemblyName");
if (string.IsNullOrEmpty(typeName)) throw new ArgumentNullException("typeName");
AssemblyName = assemblyName;
TypeName = typeName;
}
/// <summary>
/// Gets the name of the assembly containing the target type.
/// </summary>
public string AssemblyName { get; private set; }
/// <summary>
/// Gets the name of the target type.
/// </summary>
public string TypeName { get; private set; }
}互操作程序集包含将实现插件功能的类的基类(请注意,它也是从MarshalByRefObject派生的。
此程序集还定义了允许插件回调到宿主应用程序的IHost接口。
/// <summary>
/// Defines the interface common to all untrusted plugins.
/// </summary>
public abstract class PluginBase : MarshalByRefObject
{
public abstract void Initialize(IHost host);
public abstract void SaySomething();
public abstract void DoSomethingDangerous();
public abstract void CallBackToHost();
}
/// <summary>
/// Defines the interface through which untrusted plugins automate the host.
/// </summary>
public interface IHost
{
void SaySomething();
}最后,每个插件都派生自互操作程序集中定义的基类,并实现其抽象方法。在任何插件程序集中可以有多个继承类,并且可以有多个插件程序集。
public class Plugin : PluginBase
{
private IHost _host;
public override void Initialize(
IHost host)
{
_host = host;
}
public override void SaySomething()
{
Console.WriteLine("This is a message issued by type: {0}", GetType().FullName);
}
public override void DoSomethingDangerous()
{
var x = File.ReadAllText(@"C:\Test.txt");
}
public override void CallBackToHost()
{
_host.SaySomething();
}
}发布于 2010-11-11 02:23:50
如果您需要您的第三方扩展加载的安全权限低于您的应用程序的其余部分,您应该创建一个新的AppDomain,在该应用程序域中为您的扩展创建一个MEF容器,然后将应用程序中的调用编组到沙箱应用程序域中的对象。沙箱发生在你如何创建应用程序域,它与MEF无关。
https://stackoverflow.com/questions/4145713
复制相似问题