Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[orion-dev] How to get Orion to follow symbolic links for files and directories? (a preliminary investigation)

Hi,

Orion is in the right track overall and I love it!

That being said, I have encountered an issue (described below) that makes me even consider putting it aside, at least for the moment, and looking for other options :-(

Here is a bug-report, which started as a couple of questions (that matter a lot in my particular case) and turned out to be a comprehensive bug-report/feature-request.

=============================================
VERY BRIEFLY:
=============================================
1) Is there any way to get Orion to follow symbolic links while browsing a file system location on the server side (attached to the user's workspace via New>Link to Server) and for editing files that happen to be aliases (symlinks)?
(I am otherwise able to browse that location and edit files within it, as long as any of the directories and files involved are not symlinks).

2) Alternatively, is there any way to influence the configuration of the embedded Jetty web server in a standard Orion distribution? 

UPDATE:  My further investigation has shown that, even if that is possible, it would not really help for the question at hand, because Orion seems to use an Orion-specific servlet (NewFileServlet) to serve up the "/file" URL space.
NewFileServlet, which does not seem to even inherit from the Jetty DefaultServlet, appears to have a pretty hard coded "Forbid" approach when it comes to symlinks. Bummer.

However, this question (2) still remains to be an interesting in general for other potential Orion server administration use cases.

Please see below for a detailed description of the issue and the test environment, as well as the results of preliminary investigation that suggests that the resolution of the issue requires a code-change (which could be quite minor)
at least in the NewFileServlet.java source file on the Orion 5.0 code branch (relevant code snippet given below).

Since this topic could have some pretty serious impact on security at least on some OS platforms and/or file systems, I thought it might be worthwhile to see how this is handled in Jetty, which is a more mature project, 
and found the relevant code snippets from Jetty 8.x code line (which happens to be the Jetty release that Orion 5.x currently embeds). Those relevant code snippets from Jetty 8.x  can also be seen down below in this message.

Finally, it might be worthwhile to note that, according to Jetty documentation, Jetty 9 makes an attempt to handle the question in a completely different way, 
allowing for much more fine grained control over permissions on file and directory "aliases" (in Jetty parlance).


P.S. : I do realise this is rather extremely long message. 
         However, I do think it is useful information constituting something like a preliminary investigation on the question, even providing hints on how to solve the issue.
 I think of it as a bug report that comes already with some investigation that could be useful for the folks who will get around fixing it in the future (perhaps even myself, if I get around some of the Orion internals).
   






##############################################################################################
# THE GORY DETAILS :
##############################################################################################

My Orion 5.0 installation seems to be working just fine except this issue.
 
I have successfully attached "/home/example" as a new location (named "example") in my workspace, doing the necessary changes in "orion.conf" like so:
orion.file.allowedPaths=/home/ide/serverworkspace,/home/example

I am able to browse the attached location and edit files within it, after tweaking file permissions on the server filesystem for everything under "/home/example".

So far, so good.

The only catch: Orion refuses to follow symlinks within the "example" location, displaying a message that goes "Forbidden: ....".
Observing the OSGI console on the server side suggests that it is indeed the orion server that responds with an HTTP error condition (403 Forbidden).

According to Jetty documentation, by default, Jetty will refuse to follow file and directory aliases (which includes symlinks among other similar strange things).
This seems to be construed as a "security feature" of Jetty, according to their documentation.

Luckily, Jetty seems to allow overriding that paranoid behaviour by setting and init-param ("aliases") to "true" on the "DefaultServlet" (org.eclipse.jetty.servlet.DefaultServlet) in "jetty.xml".
Therefore, I figured: if I can somehow influence the configuration of the embedded Jetty in Orion,  it could be a pretty happy end to the story.

After quite a bit of searching, I stumbled upon the "plugin.xml" file within the "eclipse/plugin/org.eclipse.orion.server.configurator_1.0.0.v20140221-1503" directory, that appeared quite promising.
Browsing through the various "servlet" configurations in "plugin.xml", I found the servlet that is aliased as "/file" within the Orion URL space.

However, it looks like the "/file" URL space is served up by an Orion-specific servlet (org.eclipse.orion.internal.server.servlets.file.NewFileServlet), 
and not the DefaultServlet (org.eclipse.jetty.servlet.DefaultServlet) from Jetty.

That's a bummer, because Jetty seems to allow overriding the paranoid behaviour of forbidding to follow symlinks altogether by setting an init-param ("aliases") to "true" on the "DefaultServlet" (org.eclipse.jetty.servlet.DefaultServlet) in "jetty.xml".
That kind of configuration option would have saved my day today :-(

First, I naively tried setting the "aliases" <init-parameter> to "true" in the servlet configuration for the "/file" space in "plugin.xml" anyway, similar to the way one would do in jetty.xml, just to see what happens.
Naturally, that did not have any effect on the problem at hand, since, as I found out later, Orion's "NewFileServlet" doesn't even inherit from Jetty's "DefaultServlet" (and for good reasons).

As a final ditch, I dug up the source code of Orion's "NewFileServlet" to see if there was still any hope to find any administrator configurable behaviour in there concerning the question.
==> Nope. It turns out, that particular behaviour (of forbidding symlinks) is pretty hard-coded into the "doGet()" method of "NewFileServlet", and the other doXXX() methods just call the "doGet()" method
to do their job.

Then, by curiosity, I dug up Jetty 8.x sources to see how this question is handled there. It turns out Jetty 8.x handles this in a pretty straight forward way.
In Jetty parlance, we are talking about a file or directory "alias" here (which includes symlinks, but also other stuff like NFS strangenesses and so on).

- Jetty determines whether a file resource is an "alias" (symlink, etc) by simply comparing its initial path to its canonical path.  
           If those do not match, then the resource is considered an "alias"

- Then, Jetty checks if the configuration setting (which happens to be the "aliases" init-param) allows accessing/following such a resource.
*) If so, Jetty just serves up the "alias". BINGO.     (presumably, this gets ultimately handled by low level Java IO calls via the system specific calls on the particular OS platform for reading/writing/.. files)
*) If not (which is the default), then Jetty behaves in pretty much the same way as Orion, forbidding the action.

Naturally, there is some pretty nice plumbing code around that as well (obtaining the relevant configuration option as well the alias checking code within all encapsulated the ContextHandler).

I thought this could be helpful for the folk(s) who will actually get around implementing something similar (at least in terms of capabilities) within Orion server. 
That could even include me  -- if I ever get around really learning the internals of Orion, Equinox, Eclipse, ... (and start coding in Java)

In any case, I could probably help in specifying the behaviour and testing the actual implementation, and even take a stab at documenting it when done.

If you have actually continued reading this text up to this point, please let me know, and I'll buy you beer if you are in Paris (France) some day :-)

If you also go ahead and skim through the appendices below (i..e. yes, even the gorier details),  then it will have to be a bottle of French wine with extra goodies :-)

Those further details included below are:
- My particular constraints
- My test environment (server & client)
- Source code snippet from Orion 5.0  deemed relevant from  "org.eclipse.orion.internal.server.servlets.file.NewFileServlet"
- Source code snippet from Jetty 8.x   deemed relevant from   "org.eclipse.jetty.server.handler.ContextHandler"
-- There is actually also some related code in Jetty's "DefaultServlet" implementation that just obtains the configuration settings and sets the corresponding property of the context initially.   
      I haven't included that snippet, because that part is pretty straight-forward.

Cheers,
Ayhan
 


##############################################################################################
# MY CONSTRAINTS:
##############################################################################################

  1. I cannot do without the symlinks.   
    • Because, for various reasons, I am not able to change the way the files/directories are organised within the existing attached location, which contains a bunch of symlinks on some files and directories.

  2. If at all possible, I really would like to avoid having to cook myself a WAR deployment of Orion.
    • Because, as it seems, Orion does is not -yet- distributed with a WAR deployment option, not even a boiler-plate. 
      Since I know how ignorant I am on WAR-cracft (and even on Java), it would take me several days to get something up and running, if I ever get there; 

  3. Did I tell you I am in a hurry (for an urgent task)?    -- but aren't we all? :-)

  4. I love Orion so far.
    • But if I can't find a resolution (even an acceptable workaround) to this issue in a day or so, I will probably find myself out there looking for an alternative to Orion... :-(



##############################################################################################
# EVEN GORIER DETAILS:
##############################################################################################

#------------------------------------------------------------------------------------
# MY SERVER ENVIRONMENT (most probably irrelevant):
#------------------------------------------------------------------------------------
Orion release = 5.0 (latest as of today)
Server OS       = Debian (weezy)
Java version          = "1.6.0_27"
JRE = OpenJDK Runtime Environment (IcedTea6 1.12.5) (6b27-1.12.5-1)

# The Java Security Policy on the server machine ("/etc/java-6-openjdk/security/java.policy") now contains the following snippet (as a result of my failed attempts of hacking a solution to the pro:

grant {
# ...
permission java.io.FilePermission "/home/example/-", "read,write,delete,execute,readlink";
# ...
};

# Naturally, when the issue is resolved, I will restrict the "grant" to a given codeBase (which one?), but for the moment it's wide open :-)

#------------------------------------------------------------------------------------
# MY CLIENT ENVIRONMENT (most probably irrelevant):
#------------------------------------------------------------------------------------
OS  = Mac OS X 10.8.x (Mountain Lion)
Browser = Chrome & Safari (tried both)




#==========================================================================================================
# RELEVANT ORION 5.0 SOURCE CODE  snippet -  which clearly suggests that Orion 5.0 forbids symbolic links (symlinks) altogether at this time.
#      
#     The following code snippet was obtained from the eclipse's official git repository @ around 2014-03-30T00:25:00GMT
#
#
#     The code snippet involved is basically the "doGet" method of NewFileServlet.
#
#     // ... denotes omitted content deemed to be irrelevant.
#==========================================================================================================

// ...
package org.eclipse.orion.internal.server.servlets.file;

// ...

public class NewFileServlet extends OrionServlet {

// ...

// lines of interest: [54 - 108]
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		traceRequest(req);
		String pathInfo = req.getPathInfo();
		IPath path = pathInfo == null ? Path.ROOT : new Path(pathInfo);

		// prevent path canonicalization hacks
		if (pathInfo != null && !pathInfo.equals(path.toString())) {
			handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_FORBIDDEN, NLS.bind("Forbidden: {0}", pathInfo), null));
			return;
		}
		//don't allow anyone to mess with metadata
		if (path.segmentCount() > 0 && ".metadata".equals(path.segment(0))) { //$NON-NLS-1$
			handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_FORBIDDEN, NLS.bind("Forbidden: {0}", pathInfo), null));
			return;
		}
		IFileStore file = getFileStore(req, path);
		IFileStore testLink = file;
		while (testLink != null) {
			IFileInfo info = testLink.fetchInfo();
			if (info.getAttribute(EFS.ATTRIBUTE_SYMLINK)) {
				if (file == testLink) {
					handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_FORBIDDEN, NLS.bind("Forbidden: {0}", pathInfo), null));
				} else {
					handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND, NLS.bind("File not found: {0}", pathInfo), null));
				}
				return;
			}
			testLink = testLink.getParent();
		}

		if (file == null) {
			handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND, NLS.bind("File not found: {0}", pathInfo), null));
			return;
		}
		if (fileSerializer.handleRequest(req, resp, file))
			return;
		// finally invoke super to return an error for requests we don't know how to handle
		super.doGet(req, resp);
	}

	@Override
	protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doGet(req, resp);
	}

	@Override
	protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doGet(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doGet(req, resp);
	}

// ...

}




#==========================================================================================================
# RELEVANT JETTY 8.x SOURCE CODE snippet that handles file and directory "aliases" (including symlinks)
#
#     This Jetty 8.x code snippet could perhaps be an inspiration for Orion 5.x (based on Jetty 8.x for the moment). 
 #    Note that Jetty 9.x seems to be handling this question in a completely different fashion, allowing for more granular control.
#      
#     The following code snippet was obtained from the eclipse's official git repository @ around 2014-03-30T00:25:00GMT
#
#     // ... denotes omitted content deemed to be irrelevant.
#==========================================================================================================

// ...
package   =  org.eclipse.jetty.server.handler;

// ...

public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful
{

// ...

// ****  lines of interest: [1570 - 1618] ***************************
 public Resource getResource(String path) throws MalformedURLException
    {
        if (path == null || !path.startsWith(URIUtil.SLASH))
            throw new MalformedURLException(path);

        if (_baseResource == null)
            return null;

        try
        {
            path = URIUtil.canonicalPath(path);
            Resource resource = _baseResource.addPath(path);
            
            if (checkAlias(path,resource))
                return resource;
            return null;
        }
        catch (Exception e)
        {
            LOG.ignore(e);
        }

        return null;
    }

    /* ------------------------------------------------------------ */
    public boolean checkAlias(String path, Resource resource)
    {
        // Is the resource aliased?
        if (!_aliasesAllowed && resource.getAlias() != null)
        {
            if (LOG.isDebugEnabled())
                LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());

            // alias checks
            for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
            {
                AliasCheck check = i.next();
                if (check.check(path,resource))
                {
                    if (LOG.isDebugEnabled())
                        LOG.debug("Aliased resource: " + resource + " approved by " + check);
                    return true;
                }
            }
            return false;
        }
        return true;
    }


// ...

}

##############################################################################################
# END OF MESSAGE!  -- yes, finally :-)
##############################################################################################





Back to the top