-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Socket #2618
Add Socket #2618
Conversation
Can one of the admins please verify this patch? |
@GooeyHub: ok to test @SoniEx2: good to see some actual code! Hoping to see some good review and discussion here, especially for the change to |
Hooray Jenkins reported success with all tests good! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of having an option to open up connections (something similar for a database backend would be awesome too ;) ).
The injection with one instance per module is also a handy feature but I would prefer a different way of registration, e.g. something like @In(scope=Scope.MODULE)
with a registration of a dynamic instance provider (interface is fine IMHO) in the registry or a similar place. We can then check in the injection helper if a provider for the module is registered (i like the access to the module name in this place) and then we can ask the provider for an instance for the current module (or throw an exception for whatever reasons).
* @param port The target port. | ||
* @return The socket. | ||
* @throws IOException If an I/O error occurs. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to return the actual socket and leave the in/out-handling to the caller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think exposing java.net to the sandbox is very, uh, sandboxed... It'd allow the sandbox to make its own sockets, defeating the purpose of the sandbox.
/** | ||
* @author soniex2 | ||
*/ | ||
public class InternetManagerImpl implements InternetManager, DynamicInstanceProvider<InternetManager> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please separate the provider from the implementation (i will add an additional comment about the per-module instance on the review)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are 2 1 reasons why I did it this way.
- I'm lazy
Good luck doing it any other way.Uh I forgot the other reason but this feels more natural. We still want a global instance that everyone can use, even if that global instance does nothing, because other parts of the engine can see and access it, and we need something to return from CoreRegistry.get().
@oniatus This way is user-friendly and backwards compatible as we can just add permission sets (#1703) without modules needing to know or care about it. It's less things for the programmer to worry about ("does this thing I wanna use have permissions so I need to use that 2nd variant of the dependency injection? or can I use it directly?"). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't there be only one instance of the provider, managing the cache? What prevents creating multiple providers?
Imho @oniatus comment to separate provider and implementation is sensible in this context.
@skaldarnar You're meant to have a provider for the client and a provider for the (internal/integrated) server (in the same JVM), but that's not fully implemented yet (part of another issue). And in practice there really is only one instance of the provider: the I designed this to be future-proof in a way compatible with the current implementation. |
Hooray Jenkins reported success with all tests good! |
@oniatus Still not gonna do that |
@SoniEx2 I'm fine with that, but we should definitley document the feature somewhere after the PR is merged 😃 Maybe you want to have a look at the checkstyle report in the meantime, there is probably something more to clean up. Two more open questions from my side:
Just some short ideas about the extensibility of the new feature. Maybe @skaldarnar or @Cervator are the better candidates for such questions, as I do not have the architectual farsight in this scope 😉 In any case I think that both (Internet Access and general Sandboxing of some API) are very valuable features, keep going 👍 |
|
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
@oniatus How do the configs look? |
import org.terasology.internet.InternetManager; | ||
import org.terasology.internet.TCPSocket; | ||
import org.terasology.naming.Name; | ||
import org.terasology.registry.CoreRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Disregard that, I accidentally commented on the previous commit)
// TODO We have many options when both a wildcard entry and a specific entry exist. Which one should we pick? | ||
// 1. Merge allowed ports. | ||
// 2. Wildcard overrides specific hostname. | ||
// 3. Specific hostname overrides wildcard. This option pleases me the most. (It also enables blacklists.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I'm reading this correctly, in a whitelist containing *.example.com
foo.example.com
will no longer be whitelisted after bar.example.com
is added to the config? That doesn't sound too intuitive - the second option makes more sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, wildcards don't actually work (yet). *.example.com
would only match a literal *.example.com
. I only added *
as an option so a dedicated "HTTP" module (not an engine subsystem) is a possibility.
But if you had *
set to all ports allowed, and you added example.com
with only port 80 allowed, then example.com
would match first and override the *
match - only port 80 would be valid for example.com
.
If I do get around to implementing wildcards, then *.example.com
will match both foo.example.com
and bar.example.com
. If bar.example.com
exists on the whitelist, then bar.example.com
would only match against it, and foo.example.com
would still use the *.example.com
entry.
This allows the "whitelist" to act as both a whitelist and a blacklist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like a far better idea, thanks for the detailed explanation 🙂
Map<String, TIntSet> allowedHosts = this.allowedHosts; | ||
if (!(allowedHosts.containsKey(hostname) || allowedHosts.containsKey("*"))) { | ||
// neither hostname nor wildcard in allowedHosts | ||
// TODO maybe use a different exception type? (SandboxException or something?) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good - IllegalArgumentException
is IMO best used in Preconditions
-esque checks
public int foo (int a) {
if (a % 2 == 2) {
throw new IllegalArgumentException ("wow you messed up");
}
/* ... */
}
and I can't find a good default exception for this usecase.
/** | ||
* All the TCP whitelists. Keyed by module ID. | ||
*/ | ||
private Map<Name, Hosts> tcpWhitelists = Maps.newTreeMap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK there's some sort of TObjectObjectMap
class. Might be worth using alongside TIntSet
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no benefit in this case.
Socket is more accurate.
Hooray Jenkins reported success with all tests good! |
Hooray Jenkins reported success with all tests good! |
@SoniEx2: looks like the TODO list in the description is fully finished - is this ready for a final review? |
@rzats Not yet, looks like I overlooked something. (Sorry!) |
@rzats Fixed. |
Hooray Jenkins reported success with all tests good! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I first was very doubtful when I saw the PR originally that we want to do this at all.Since it is not really that an important feature and it adds a risk. I also know that I am not the only one thinking that way. However you put good effort in thinking of a way to make it secure. So I went ahead and checked if this can be already merged or not.
I think some additional work is required before this can be merged, but I am no longer that sceptical as I was first.
To sum it up for others, the basic idea to make it secure is that at injection time the module of the class that gets injected gets determined. Via that class a module specific instance will be injected. So the way for creating a module specific instance can be protected against access. So as long as the module keeps it's field private no other module will be able to call the module specific instance. The module specific instance can then do permission checks and allow for example only the access to certain hosts for that module.
|
||
@Override | ||
public void initialise(GameEngine engine, Context rootContext) { | ||
rootContext.putInstanceProvider(SocketManager.class, new SocketManagerProvider()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Root context is wrong place to put something that stores something for modules. e.g. At the end of the game, everything that belonged to modules used in that game should be gone.
Something like sockets should also be closed implicitly at the end of the game.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where should it go? We don't have separate contexts yet...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if sockets are implicitly closed, how do we give modules enough time to gracefully shutdown?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually there are 2 Context
objects usually. One engine context that exists until the program terminates. Besides that there is the context that lives for the duration of the game that inherits from the engine context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you explicitly want to give modules a chance to do something before the disconnect you can send an event (e.g. to world entity) which they can then listen to.
* @return A Context object representing this class. | ||
*/ | ||
public static Context asContext() { | ||
return CoreRegistryContext.INSTANCE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method isn't needed. CoreRegistory.get(Context.class) can be used if really needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns a Context that behaves like CoreRegistry - if you change the CoreRegistry backing context, it reflects on the CoreRegistryContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As suggested above, I recommend that the thing you register at the context should have the same live span as the context. Thus using something that lives over multiple context lifespans does not make sense.
* Used when a resource access has been blocked by the sandbox. | ||
*/ | ||
@API | ||
public class SandboxException extends Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked exceptions are good for things where you want to force each caller to implement a handling. e.g. typically when something can happen even if the program is written correctly.
If something wrong gets detected that is caused by a programming mistake, then it should be a RuntimeException as it is really not something that must be mentioned in each signature till there is somewhere a handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, SandboxException is to be used when the user chooses to restrict what a module can do. So, e.g. a clipboard API would throw SandboxException instead of returning null, if the user disallows clipboard access.
* @param type The interface which the system fulfils | ||
* @param object The system itself | ||
* @param <T> | ||
* @param type The interface which the system fulfils. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not necessarly an interface. You could put a class in it.
* @param object The system itself | ||
* @param <T> | ||
* @param type The interface which the system fulfils. | ||
* @param object The system itself. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not necessarily a system
return CoreRegistryContext.INSTANCE; | ||
} | ||
|
||
private static final class CoreRegistryContext implements Context { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class can then also be removed as it is not needed for asContext
public <T> DynamicInstanceProvider<T> getInstanceProvider(Class<? extends T> type) { | ||
if (System.getSecurityManager() != null) { | ||
System.getSecurityManager().checkPermission(new RuntimePermission("permGetInstanceProvider")); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to keep the Context interface and the injection concept as simple as possible.
Some idea I could think of to simplify it a little bit would be to have Context
have a inject
method so that we don't need to have a method that we must protect against all but one caller. Although I am not that happy with that idea.
/** | ||
* A DynamicInstanceProvider provides an instance dynamically. | ||
*/ | ||
public interface DynamicInstanceProvider<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DynamicInstanceProvider
is kind of a rendundant name as a Provider
is already someting that provides you dyamically with instances. Also the javadoc currently is kind of redundant to the class name.
When you just look at this class as another developer you would do very hard to guess why we need it and what to do with it.
I think the idea behind it should be described at one of the classes like this one and other API parts should refer to this class for an explanation. Also the class name should give a better indication what the class is good for. For example the class could be called ModuleSecurityBoundInstanceProvider
.
Example for javadoc:
Some managers need to be able to check, if a using module has actually the permission to use a certain feature. The idea is to do this check at injection time. At this point of time the module of the class that gets injected can be determined. With this information it is possible to inject a module specific instance to the class. The creation or determination of this module specific instance gets done via this interface. Please note that implementors of this class must make sure that there is no way for other modules to obtain a instance of this interface and by pass thus the injection.
Of course modules that let a instance be injected should ensure that there is no way to access this instance from outside. This should be document in each type that can be obtained via this interface.
* @param context The current context. | ||
* @return An instance. | ||
*/ | ||
T getInstanceForModule(Name moduleId, Context context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Context should not be an argument, but something that gets passed to the constructor when the object gets created. Reasoning: The provider will live for the same duration like the objects it provides.
/** | ||
* The allowed hosts. | ||
*/ | ||
//@Nonnull |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment and redundant javadoc (to name) can be removed.
Lots of the javadocs were added so my IDE wouldn't complain about missing javadocs. |
Can one of the admins please verify this patch? |
Hooray Jenkins reported success with all tests good! |
@SoniEx2 This has been open for some time now, and some of the comments have not been addressed yet (neither have been changed nor commented on further discussion). What is the current state of this PR? @flo @rzats @oniatus Any comments on whether we can get this merged _soonish_™️ or if it needs more work and/or discussion. |
@skaldarnar The PR is uh... dead, mostly. It needs to be rewritten from scratch and we need a better system than |
Closing in favor of (hopefully soonish) new PR on the matter. We still made good way with this one and all the discussion around it. @SoniEx2 Thanks for checking back and letting me know. Feel free to open a new PR whenever ready. |
For the interested: The discussion point on the forums would be http://forum.terasology.org/threads/config-context-and-security.1761/ |
Contains
Adds Sockets. Use
In a System to get access to sockets. It has TCP clients, but can be expanded later.
How to test
This is a badly written IRC client that just barely works: https://github.com/SoniEx2/TeraIRC
Outstanding before merging
Move DynamicInstanceProvider to a separate PR.Easier to just keep it here.Disallow internet access on the client.HARD. Depends on the whole CoreRegistry deprecation/removal thing and splitting the internal server from the client. Not high-priority.HTTP, with separate whitelists. Anything with TCP access also has HTTP access, but HTTP access does not give you TCP access.Should be an external "HTTP" module, not part of this PR.Global whitelist that any module can use?Would avoid some duplication, but can easily be misused.It is deliberately allowed to hand your SocketManager (and thus your whitelist) to another module. After all, it's trivial to wrap SocketManager for cross-module use, with or without being able to share SocketManager itself.
Also, if I knew how, I'd use Netty for this. The idea was to make an implementation-agnostic API, maybe with events, but I'm not very good at that.