capability-mediated modules (was: python-dev Summary for 2003-03-01 through 2003-03-15)

brett@python.org wrote:
Security with capabilities is done based on possession; if you hold a reference to an object you can use that object.
Note that you can use capabilities as your sole access control mechanism if every resource that you want to protect is identifiable with a Python reference. For example, suppose you want to control the ability to listen on sockets for network traffic. If there is a reference (e.g., to an object) that represents the privilege of listening on sockets, then you can give such a reference to one object, allowing that object it to listen on sockets, while withholding it from another object, thus preventing that one from listening on sockets. The only part of Python that isn't already well-matched with capabilities is the way that authority is gained by importing modules, and you can load a module even when you weren't given a reference to it. Reconciling Python modules with capabilities would be the challenging part. Python objects, bound methods, functions, and suchlike are already well-matched to capabilities. In case that isn't clear, I'll write a quick example. Recalling my "tic-tac-toe game" example from [1], I wrote code which allowed or denied the tic-tac-toe game to paint a window on the screen and to write to a file. This "allow-or- deny" enforcement was unified with the designation of which window and which file. That is: by passing a reference to a certain window, I simultaneously told the tic-tac-toe game which window to draw in *and* extended to it the privilege of drawing to the screen. This is a central motto of the capability security crowd: "Unify designation with authority." In a capability system *all* authority -- everything that you could ever want to prevent -- is mediated by capabilities. Code that is loaded and run, but to which no capabilities are extended, must be incapable of doing anything dangerous. Now, what about modules? In current Python, some code can "import os" and gain all kinds of authority. In the rexec scheme, as I understand it, there was a handler function which could be overridden to determine what happens when I try "import os". This is effectively a "policy mini-language", such as in the hypothetical "restricted Python v2" [1]. (Guido has pointed out that this overridable policy handler could be used to implement capabilities as well as other regimes. I think I agree in principle, but what I am advocating here is having the core language implement capabilities so that the programmer-visible part is as minimal and unified as possible.) Now what I would *like* is that instead of doing "import os" to load code, instead the caller provides, or doesn't provide the os module as part of the construction/invocation of A. I don't have a clear idea yet of how that could be implemented in a Pythonic, compatible way. Just to help me think about it I'll suggest a non-Pythonic and incompatible way: there is no "import" keyword. When you invoke a constructor, function, method, etc., you have to pass as arguments references to everything that the code will need to do its job. So, assuming the tic-tac-toe game requires the "math" module and the "string" module, I would have to write: # restricted Python v3+modules game = TicTacToeGame() game.display(open("/tmp/tttgame.out","w"), math, string) The burden of typing in dozens of module names with each invocation can be eased by: 1. bundling modules together (put math, string, and some other stuff into one object/module/package named "standardstuff" and pass that as an argument), 2. "safe" modules that nobody could ever wish to prevent could be globally available (via the resurrected "import" keyword, I guess). If math and string are both "safe", then the example goes back to: # restricted Python v3+modules game = TicTacToeGame() # a game against itself that writes results to a file game.display(open("/tmp/tttgame.out","w")) # a game against remote, listening on a socket game.display(open("/tmp/tttgame.out","w"), socket) (Note that there is a bootstrapping problem -- *some* code has to receive a reference to the os module ex nihilo. That code should be "trusted" code -- the Python interpreter, basically.) Ah, but this last line shows another problem -- the game now has the socket module, and the ability to open sockets to remote hosts and more. I just wanted to allow it to listen on a particular port! The code would be safer if I didn't pass the large-grained module and instead passed a specific object: # a game against remote, listening on a socket listensocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listensocket.bind(('daring.cwi.nl', 8901,)) game.display(open("/tmp/tttgame.out","w"), listensocket) Regards, Zooko http://zooko.com/ ^-- under re-construction: some new stuff, some broken links [1] http://mail.python.org/pipermail/python-dev/2003-March/033938.html

Zooko <zooko@zooko.com>:
Maybe, instead of there being one ultra-global namespace for importing modules from, it should be part of a function's environment. By default a function invocation would inherit the "import environment" of it's caller, but the caller could override this to provide a more restricted environment. This would be equivalent to passing in a set of allowable modules as an implicit parameter to every call. Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg@cosc.canterbury.ac.nz +--------------------------------------+

Greg Ewing wrote:
Inheriting things is not the capability way. Passing capabilities that allow imports is, of course, but isn't very Pythonic. I'm not sure there's a neat way to fix this that keeps both camps happy.
This would be equivalent to passing in a set of allowable modules as an implicit parameter to every call.
Making it explicit would make me happy. Can you pass parameters to an import? Cheers, Ben. -- http://www.apache-ssl.org/ben.html http://www.thebunker.net/ "There is no limit to what a man can do or how far he can go if he doesn't mind who gets the credit." - Robert Woodruff

Making it explicit would make me happy. Can you pass parameters to an import?
not directly, an extension like import module(parmmod=....,...) would not seem totally unreasonable. The problem is that normally modules are uniquely globally identified singletons, but the very notion of parametrization implies instantiation and that breaks the singleton part. When to instatiate a new module and when not? a potential problem is not simply module specific global state but that the e.g. classes exported from two instances of the same module would be _distinct_ and so not interoperable. regards.

Samuele Pedroni wrote:
I don't think we'd want to change them from being singletons, just restrict access to them based on capabilities. So, I was more thinking of something like: import(capability) module where the capability conveys the authority to import the module. Oh. I see the problem: if module A imports module B, and then module A is imported in turn by C and D, with C having a capability to B that it hands to A, but D _not_ doing so, then where are we? I suppose we would say that the import of A into D failed in that case. Of course, this still leaves open the question of how we pass the authority to import into the module, so I guess it would look like: import(cap1) module(cap2,cap3,...) and cap2 etc. would have to only be used in the import statements in the module. And this is getting messy. OTOH, my original idea was that the only modules a capability-enabled module would be allowed to import would be ones that are either capability-safe, or modules in the same "package" (for some definition of package). Any other module would have to be imported by whoever initialised the capability environment, and appropriate capabilities handed in to the capability-enabled objects. This sounds cleaner to me, if somewhat nebulous at the moment. Cheers, Ben. -- http://www.apache-ssl.org/ben.html http://www.thebunker.net/ "There is no limit to what a man can do or how far he can go if he doesn't mind who gets the credit." - Robert Woodruff

Samuele Pedroni <pedronis@bluewin.ch>:
Python already has things you can instantiate -- they're called classes! Seems to me if you want instantiation, you should be using a class, not a module. Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg@cosc.canterbury.ac.nz +--------------------------------------+

to makes things clearer
Python already has things you can instantiate -- they're called classes!
Python already has things you can parametrize -- they're called classes!
Seems to me if you want instantiation, you should be using a class, not a module.
Seems to me if you want parametrization, you should be using a class, not a module. Maybe. [ what is sometimes called a "unit" that means a parametrizable and instatiable module can be a useful generic-programming construct. ] the underlying questions is how much cap-Python programming can be like/we want it like current Python programming? for example concretely, module and imports are often used to access "program-wide" factories. Do we want cap-confined client code to be rewritten in order to pass the factories or single factory-constructed objects otherwise: [Zooko]
or not. There are trade-offs in terms of necessary semantics changes/complexity vs. language overall feeling preservation and legacy code reuse and adaptation.

Zooko <zooko@zooko.com>:
Maybe, instead of there being one ultra-global namespace for importing modules from, it should be part of a function's environment. By default a function invocation would inherit the "import environment" of it's caller, but the caller could override this to provide a more restricted environment. This would be equivalent to passing in a set of allowable modules as an implicit parameter to every call. Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg@cosc.canterbury.ac.nz +--------------------------------------+

Greg Ewing wrote:
Inheriting things is not the capability way. Passing capabilities that allow imports is, of course, but isn't very Pythonic. I'm not sure there's a neat way to fix this that keeps both camps happy.
This would be equivalent to passing in a set of allowable modules as an implicit parameter to every call.
Making it explicit would make me happy. Can you pass parameters to an import? Cheers, Ben. -- http://www.apache-ssl.org/ben.html http://www.thebunker.net/ "There is no limit to what a man can do or how far he can go if he doesn't mind who gets the credit." - Robert Woodruff

Making it explicit would make me happy. Can you pass parameters to an import?
not directly, an extension like import module(parmmod=....,...) would not seem totally unreasonable. The problem is that normally modules are uniquely globally identified singletons, but the very notion of parametrization implies instantiation and that breaks the singleton part. When to instatiate a new module and when not? a potential problem is not simply module specific global state but that the e.g. classes exported from two instances of the same module would be _distinct_ and so not interoperable. regards.

Samuele Pedroni wrote:
I don't think we'd want to change them from being singletons, just restrict access to them based on capabilities. So, I was more thinking of something like: import(capability) module where the capability conveys the authority to import the module. Oh. I see the problem: if module A imports module B, and then module A is imported in turn by C and D, with C having a capability to B that it hands to A, but D _not_ doing so, then where are we? I suppose we would say that the import of A into D failed in that case. Of course, this still leaves open the question of how we pass the authority to import into the module, so I guess it would look like: import(cap1) module(cap2,cap3,...) and cap2 etc. would have to only be used in the import statements in the module. And this is getting messy. OTOH, my original idea was that the only modules a capability-enabled module would be allowed to import would be ones that are either capability-safe, or modules in the same "package" (for some definition of package). Any other module would have to be imported by whoever initialised the capability environment, and appropriate capabilities handed in to the capability-enabled objects. This sounds cleaner to me, if somewhat nebulous at the moment. Cheers, Ben. -- http://www.apache-ssl.org/ben.html http://www.thebunker.net/ "There is no limit to what a man can do or how far he can go if he doesn't mind who gets the credit." - Robert Woodruff

Samuele Pedroni <pedronis@bluewin.ch>:
Python already has things you can instantiate -- they're called classes! Seems to me if you want instantiation, you should be using a class, not a module. Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | A citizen of NewZealandCorp, a | Christchurch, New Zealand | wholly-owned subsidiary of USA Inc. | greg@cosc.canterbury.ac.nz +--------------------------------------+

to makes things clearer
Python already has things you can instantiate -- they're called classes!
Python already has things you can parametrize -- they're called classes!
Seems to me if you want instantiation, you should be using a class, not a module.
Seems to me if you want parametrization, you should be using a class, not a module. Maybe. [ what is sometimes called a "unit" that means a parametrizable and instatiable module can be a useful generic-programming construct. ] the underlying questions is how much cap-Python programming can be like/we want it like current Python programming? for example concretely, module and imports are often used to access "program-wide" factories. Do we want cap-confined client code to be rewritten in order to pass the factories or single factory-constructed objects otherwise: [Zooko]
or not. There are trade-offs in terms of necessary semantics changes/complexity vs. language overall feeling preservation and legacy code reuse and adaptation.
participants (4)
-
Ben Laurie
-
Greg Ewing
-
Samuele Pedroni
-
Zooko