2015-08-25 33 views
0

我有多個腳本正在導出相同的界面,並且它們在絕緣範圍內使用execfile()執行。使用已實現的權限規則共享插件資源

事情是,我希望他們共享一些資源,以便每個新腳本不必從頭開始重新加載它們,從而失去啓動速度並使用不必要的RAM數量。

腳本實際上比下面的例子更好地封裝和防止惡意插件,這是我開始遇到的問題。

問題是,我想創建一個資源的腳本能夠用數據填充它,刪除數據或刪除資源,並且當然可以訪問它的數據。

但是其他腳本不應該能夠改變他人的腳本資源,只需要閱讀它。我想確保新安裝的插件不會通過濫用共享資源干擾已加載和正在運行的插件。

例子:

class SharedResources: 
    # Here should be a shared resource manager that I tried to write 
    # but got stuck. That's why I ask this long and convoluted question! 
    # Some beginning: 
    def __init__ (self, owner): 
     self.owner = owner 

    def __call__ (self): 
     # Here we should return some object that will do 
     # required stuff. Read more for details. 
     pass 

class plugin (dict): 
    def __init__ (self, filename): 
     dict.__init__(self) 
     # Here some checks and filling with secure versions of __builtins__ etc. 
     # ... 
     self["__name__"] = "__main__" 
     self["__file__"] = filename 
     # Add a shared resources manager to this plugin 
     self["SharedResources"] = SharedResources(filename) 
     # And then: 
     execfile(filename, self, self) 

    # Expose the plug-in interface to outside world: 
    def __getattr__ (self, a): 
     return self[a] 
    def __setattr__ (self, a, v): 
     self[a] = v 
    def __delattr__ (self, a): 
     del self[a] 
    # Note: I didn't use self.__dict__ because this makes encapsulation easier. 
    # In future I won't use object itself at all but separate dict to do it. For now let it be 

---------------------------------------- 
# An example of two scripts that would use shared resource and be run with plugins["name"] = plugin("<filename>"): 
# Presented code is same in both scripts, what comes after will be different. 

def loadSomeResource(): 
    # Do it here... 
    return loadedresource 

# Then Load this resource if it's not already loaded in shared resources, if it isn't then add loaded resource to shared resources: 
shr = SharedResources() # This would be an instance allowing access to shared resources 
if not shr.has_key("Default Resources"): 
    shr.create("Default Resources") 
if not shr["Default Resources"].has_key("SomeResource"): 
    shr["Default Resources"].add("SomeResource", loadSomeResource()) 
resource = shr["Default Resources"]["SomeResource"] 
# And then we use normally resource variable that can be any object. 
# Here I Used category "Default Resources" to add and/or retrieve a resource named "SomeResource". 
# I want more categories so that plugins that deal with audio aren't mixed with plug-ins that deal with video for instance. But this is not strictly needed. 
# Here comes code specific for each plug-in that will use shared resource named "SomeResource" from category "Default Resources". 
... 
# And end of plugin script! 
---------------------------------------- 

# And then, in main program we load plug-ins: 
import os 
plugins = {} # Here we store all loaded plugins 
for x in os.listdir("plugins"): 
    plugins[x] = plugin(x) 

讓我們說,我們的兩個腳本存儲在plugins目錄下,並且使用加載到內存中的一些WAVE文件兩者。 首先加載的插件將加載WAVE並將其放入RAM中。 另一個插件將能夠訪問已經加載的WAVE,但不能替換或刪除它,從而搞亂了其他插件。

現在,我希望每個資源都擁有所有者,插件腳本的某個ID或文件名,並且該資源只能由其所有者寫入。

沒有調整或解決方法應該使其他插件訪問第一個。

我幾乎做到了,然後被卡住了,我的腦袋裏扎滿了實施時做的事情,但只是部分的概念。 這吃了我,所以我不能再集中精力了。任何建議都比歡迎!

添加:

這是我現在使用沒有任何安全包括:

# Dict that will hold a category of resources (should implement some security): 
class ResourceCategory (dict): 
    def __getattr__ (self, i): return self[i] 
    def __setattr__ (self, i, v): self[i] = v 
    def __delattr__ (self, i): del self[i] 

SharedResources = {} # Resource pool 

class ResourceManager: 
    def __init__ (self, owner): 
     self.owner = owner 

    def add (self, category, name, value): 
     if not SharedResources.has_key(category): 
      SharedResources[category] = ResourceCategory() 
     SharedResources[category][name] = value 

    def get (self, category, name): 
     return SharedResources[category][name] 

    def rem (self, category, name=None): 
     if name==None: del SharedResources[category] 
     else: del SharedResources[category][name] 

    def __call__ (self, category): 
     if not SharedResources.has_key(category): 
      SharedResources[category] = ResourceCategory() 
     return SharedResources[category] 

    __getattr__ = __getitem__ = __call__ 

    # When securing, this must not be left as this, it is unsecure, can provide a way back to SharedResources pool: 
    has_category = has_key = SharedResources.has_key 

現在插件膠囊:

#----------------------------------- 
# Get a category we want. (Using __call__()) Note: If a category doesn't exist, it is created automatically. 
AudioResource = SharedResources("Audio") 
# Use an MP3 resource (let say a bytestring): 
if not AudioResource.has_key("Beep"): 
    f = open("./sounds/beep.mp3", "rb") 
    Audio.Beep = f.read() 
    f.close() 
# Take a reference out for fast access and nicer look: 
beep = Audio.Beep # BTW, immutables doesn't propagate as references by themselves, doesn't they? A copy will be returned, so the RAM space usage will increase instead. Immutables shall be wrapped in a composed data type. 
:插件腳本的

class plugin(dict): 
    def __init__ (self, path, owner): 
     dict.__init__() 
     self["__name__"] = "__main__" 
     # etc. etc. 
     # And when adding resource manager to the plugin, register it with this plugin as an owner 
     self["SharedResources"] = ResourceManager(owner) 
     # ... 
     execfile(path, self, self) 
     # ... 

這很好,但正如我所說的,在這裏搞亂資源太容易了。

我想要一個ResourceManager()的實例來負責誰返回什麼版本的存儲數據。

+0

你相信插件的作者不是惡意的嗎?如果你不能相信作者,那麼已經證明,對eval/exec/execfile進行安全處理是不可能的。看到這裏:http://programmers.stackexchange.com/a/191628和/或谷歌「python exec untrusted」。如果您相信插件作者不想嘗試繞過沙箱,那麼您可能會想出一個系統,以防止人們意外地破壞共享資源和/或執行任意操作。 –

+0

我不信任他們,但我可以忽略人們對自己的計算機做什麼。至於共享,我會在將所有新插件提供給插件管理器從服務器安裝之前檢查它們。而且,不,不可能製造完全安全的沙箱。已經存在rexec模塊,儘可能安全,但它不再被開發,現在被認爲是不安全的。但是概念是可以的,並且可以改進以確保完全安全。 – Dalen

+0

在禁止插件編寫者訪問可能影響用戶數據等的模塊並給予他/她的受限制版本之後,您可以監視執行是否存在任何內存或CPU濫用,並且您可以檢查源代碼以查看是否存在是任何後向調用來訪問主要作用域,你不能通過限制來控制。例如,你可以簡單地禁止課堂創作。但是,不,現在這不是必要的。這取決於應用程序的普及程度。但是,由於某些人需要它,我相信它會被使用。 – Dalen

回答

1

所以,我的一般方法就是這樣。

  1. 有一箇中央共享資源池。通過這個池訪問對每個人來說都是隻讀的。將所有數據包裝在共享池中,以便沒有人「按照規則玩耍」可以編輯任何內容。

  2. 每個代理(插件)在加載它時都保留它「擁有」的知識。它爲自己保留讀/寫引用,並將對資源的引用註冊到集中式只讀池。

  3. 當一個插件被加載時,它得到一個對中央只讀池的引用,它可以註冊新的資源。

因此,只解決python原生數據結構(而不​​是自定義類的實例)的問題,一個相當鎖定的只讀實現系統如下。請注意,用於鎖定它們的技巧與人們可以用來繞開鎖定的技巧是相同的,因此如果有一些Python知識的人正在積極嘗試破解它,那麼沙盒非常脆弱。

import collections as _col 
import sys 

if sys.version_info >= (3, 0): 
    immutable_scalar_types = (bytes, complex, float, int, str) 
else: 
    immutable_scalar_types = (basestring, complex, float, int, long) 

# calling this will circumvent any control an object has on its own attribute lookup 
getattribute = object.__getattribute__ 

# types that will be safe to return without wrapping them in a proxy 
immutable_safe = immutable_scalar_types 

def add_immutable_safe(cls): 
    # decorator for adding a new class to the immutable_safe collection 
    # Note: only ImmutableProxyContainer uses it in this initial 
    # implementation 
    global immutable_safe 
    immutable_safe += (cls,) 
    return cls 

def get_proxied(proxy): 
    # circumvent normal object attribute lookup 
    return getattribute(proxy, "_proxied") 

def set_proxied(proxy, proxied): 
    # circumvent normal object attribute setting 
    object.__setattr__(proxy, "_proxied", proxied) 

def immutable_proxy_for(value): 
    # Proxy for known container types, reject all others 
    if isinstance(value, _col.Sequence): 
     return ImmutableProxySequence(value) 
    elif isinstance(value, _col.Mapping): 
     return ImmutableProxyMapping(value) 
    elif isinstance(value, _col.Set): 
     return ImmutableProxySet(value) 
    else: 
     raise NotImplementedError(
      "Return type {} from an ImmutableProxyContainer not supported".format(
       type(value))) 

@add_immutable_safe 
class ImmutableProxyContainer(object): 

    # the only names that are allowed to be looked up on an instance through 
    # normal attribute lookup 
    _allowed_getattr_fields =() 

    def __init__(self, proxied): 
     set_proxied(self, proxied) 

    def __setattr__(self, name, value): 
     # never allow attribute setting through normal mechanism 
     raise AttributeError(
      "Cannot set attributes on an ImmutableProxyContainer") 

    def __getattribute__(self, name): 
     # enforce attribute lookup policy 
     allowed_fields = getattribute(self, "_allowed_getattr_fields") 
     if name in allowed_fields: 
      return getattribute(self, name) 
     raise AttributeError(
      "Cannot get attribute {} on an ImmutableProxyContainer".format(name)) 

    def __repr__(self): 
     proxied = get_proxied(self) 
     return "{}({})".format(type(self).__name__, repr(proxied)) 

    def __len__(self): 
     # works for all currently supported subclasses 
     return len(get_proxied(self)) 

    def __hash__(self): 
     # will error out if proxied object is unhashable 
     proxied = getattribute(self, "_proxied") 
     return hash(proxied) 

    def __eq__(self, other): 
     proxied = get_proxied(self) 
     if isinstance(other, ImmutableProxyContainer): 
      other = get_proxied(other) 
     return proxied == other 


class ImmutableProxySequence(ImmutableProxyContainer, _col.Sequence): 

    _allowed_getattr_fields = ("count", "index") 

    def __getitem__(self, index): 
     proxied = get_proxied(self) 
     value = proxied[index] 
     if isinstance(value, immutable_safe): 
      return value 
     return immutable_proxy_for(value) 


class ImmutableProxyMapping(ImmutableProxyContainer, _col.Mapping): 

    _allowed_getattr_fields = ("get", "keys", "values", "items") 

    def __getitem__(self, key): 
     proxied = get_proxied(self) 
     value = proxied[key] 
     if isinstance(value, immutable_safe): 
      return value 
     return immutable_proxy_for(value) 

    def __iter__(self): 
     proxied = get_proxied(self) 
     for key in proxied: 
      if not isinstance(key, immutable_scalar_types): 
       # If mutable keys are used, returning them could be dangerous. 
       # If owner never puts a mutable key in, then integrity should 
       # be okay. tuples and frozensets should be okay as keys, but 
       # are not supported in this implementation for simplicity. 
       raise NotImplementedError(
        "keys of type {} not supported in " 
        "ImmutableProxyMapping".format(type(key))) 
      yield key 


class ImmutableProxySet(ImmutableProxyContainer, _col.Set): 

    _allowed_getattr_fields = ("isdisjoint", "_from_iterable") 

    def __contains__(self, value): 
     return value in get_proxied(self) 

    def __iter__(self): 
     proxied = get_proxied(self) 
     for value in proxied: 
      if isinstance(value, immutable_safe): 
       yield value 
      yield immutable_proxy_for(value) 

    @classmethod 
    def _from_iterable(cls, it): 
     return set(it) 

注意:這僅僅是Python的3.4測試過,但我試着寫它能夠同時與Python 2和3

兼容充分利用共享資源的根字典。將該字典的ImmutableProxyMapping添加到插件中。

private_shared_root = {} 
public_shared_root = ImmutableProxyMapping(private_shared_root) 

創建的API,其中插件可以提供新資源的註冊public_shared_root,大概就先來先服務的基礎(如果它已經存在,你不能註冊的話)。預先填充private_shared_root與任何你知道你將需要的容器,或任何你想要與所有插件共享的數據,但你知道你想要是隻讀的。

如果共享根映射中的密鑰約定都是字符串,如文件系統路徑(/home/dalen/local/python)或像Python對象(os.path.expanduser)這樣的點狀路徑,則可能會很方便。這樣,如果插件嘗試將相同的資源添加到池中,碰撞檢測是直接的和微不足道的/顯而易見的。

+0

非常好!現在,我將我現有的解決方案(無安全保障)添加到Q.也許您可以建議將解決方案集成到我的最佳方法。爲了簡單起見,讓數據類型爲(int,float,bool,long,str,unicode,tuple,list,dict,set和只讀文件)。由於我的資源只是數據,沒有訪問驅動程序或類似的東西。請參閱如果您可以消除兩個資源池,它會很好。否則它並不重要。 – Dalen

+0

而且,出於好奇,你將如何着手破解已經具有__getattribute __()和__setattr __()定義的對象?嗯,沒有使用它的課程來重新初始化我目前沒有看到任何其他方式。 – Dalen

+0

噢,是的,我完全忘記了,我使用Python 2.5到2.7,但不要打擾自己,只需少量修改就足以在2.x中使用3.4。這不是一個問題。 – Dalen