Preview does not work
I get an error:
URI: /index-0384.html STATUS: 404 MESSAGE: Not Found SERVLET: org.eclipse.jetty.servlet.ServletHandler$Default404Servlet-5d2a9b26 Powered by Jetty:// 11.0.15
trying to open the preview from eclipse ice:
OS: MacOS Ventura 13.4 Java Temurin 17.0.7 Eclipse: 2023-03 (4.27.0) Plugin: FluentMark Editor 1.8.0.20230519-1831
May be I need special items in my environment?
Verify the Readme/Configuration steps were followed.
Do you have any protection software running that would block local connections to localhost:9025?
Does the browser open? What is the complete URI in the browser?
After the browser opens (and is still open), verify that
<tmp>/liveview/client.zip and <tmp>/liveview/app/ both exist, where <tmp> is the value of System.getProperty("java.io.tmpdir"). The contents of app/ should be the zip unrolled plus a modified copy of index.html named index-<XXXX>.html
I have installed "Pandoc" and "Miktex-console" via brew. The Pandoc path is configured in the settings panel.
The browser opens with URL: http://127.0.0.1:9025/index-0067.html. The files in the tmp-Path are there, but I cannot see any application listening on port 9025?
There is no special content in the numbered index.html - file. It's just a copy of the original index.html.
The difference to the README: I use a very newer eclipse version and also a newer Jetty version!?
Fluentmark runs an embedded Jetty (11.0.15) server on localhost:9025 that serves the client app and creates a websocket connection between the Fluentmark plugin/server and the browser running the client app.
Is the browser is giving a custom 404 page? Is there a liveview button at the bottom?
If the browser is showing a default 404 page, something is blocking the local connection. Are you running in a container that would block the browser from accessing the local filesystem? Or, redirect HTTP connections? I do know others are successfully running Fluentmark on plain MacOs.
In any event, try diagnosing the browser operation: F12 in Firefox opens the Developer panel; the console should provide some information on what the browser is doing. For Safari, the Develop menu entry will open a similar panel (if you don't see the menu entry, do Settings -> Advanced -> Show Develop menu in menu bar).
It seems the jetty server is running, but the requested file is not available. The directory referenced by "java.io.tmpdir" contains the file described in the comments above.
Using "firefox" shows the same results.
It seems the root directory for the jetty instance is not set as necessary?
The embedded Jetty server is set up correctly - works well for others.
There is something on your machine/in your environment that is blocking correct operation. Shut down any antivirus, VPN, or other software that could constrain, alter, or redirect local web transactions or access to the local file system until the conflict is isolated.
@grosenberg This is still an issue, and always reproducible on modern MacOS.
The reason is MacOS's System Integrity Protection (SIP), which prevent access to java.io.tmpdir (on modern MacOS something like /var/folders/…VERY_LONG_HASH…) for processes/threads bound to a (TCP/UDP) socket on localhost (i.e. to block phishing attempts from any kind of maleware). Maybe additionally complicated by the fact that all Eclipse-distributions on MacOS do not declare the necessary privileges for this use-case in their app-recipe? This has always been an issue for Eclipse on MacOS.
So, please look again at the screenshot given in one of the previous comments. It proves that the Browser can access jetty, as 404 is from jetty. It also proves that jetty's attempt to access java.io.tmpdir is blocked, because jetty can not “see” the file. Here SIP does not simply block the access to the file (as even this can reveal security-relevant information), it completely hides it on kernel-level.
I tested the following two work-arounds, which both are really not recommendable for production:
- a) Add
-Djava.io.tmpdir=/path/to/some/non-protected/user/accessible/folder(i.e.$HOME/temp) toeclipse.ini - b) Disabling SIP
In both cases the live preview works. But changing eclipse.ini breaks Eclipse.app's code-signature on MacOS, creating new/other issues and disabling System Integrity Protection, is generally a really bad idea.
Therefore I'd recommend to use a different folder as document-root, at least on MacOS - maybe something in the current Eclipse workspace? Or we make this document-root configurable?
Don't have MacOS to test. Does MacOs have a way to temporarily declare appropriate privileges on a directory? Does MacOs have/recommend some other location to serve dynamically generated HTML from? Is there a consensus way other applications (Eclipse or non-Eclipse) handle this problem?
The public documentation found is sparse, but suggests SIP should not be the blocking factor:
The symbolic links from
/etc,/tmpand/varto/private/etc,/private/tmpand/private/varare also protected, although the target directories are not themselves protected.
ls -lOd /usr/libexec drwxr-xr-x 338 root wheel restricted 10816 May 13 00:29 /usr/libexec
Here, the restricted flag indicates that the
/usr/libexecdirectory is protected by SIP. In a SIP-protected directory, files cannot be created, modified, or deleted. Moreover, if a file contains the attributecom.apple.rootlessextended attribute, that file will also be protected by SIP.
Can find no publicly reported problems for Jetty serving content from a file stored in the system temporary directory, i.e. System.getProperty("java.io.tmpdir"), for any operating system.
Are you running any software that might be implementing firewall protections on network connections?
Has /System/Library/Sandbox/rootless.conf been modified on your system?
Are any of the files/directories <tmp>/liveview/client.zip, <tmp>/liveview/app/... marked restricted or set with the com.apple.rootless extended attribute?
@grosenberg First of all - thanks for reopening and your feedback.
Before I did my test, with the overridden temporary directory, I removed anything (like my custom firewall) that could interfere with this setup. (Because I didn't want to waste you're time.) Today I reproduced this issue on a freshly installed prestine Mac.
On thing I can remember about jetty - there was at least one CVE related to jetty, that was about severing data from temporary directory might reveal sensitive information. Due to lack of time, I could not search for it yet.
I'll take more time this weekend to investigate this issue. I'll build your plugin from source, attaching some more debug statements to jetty's resource-handler implementation. I already ran eclipse with trace logging enabled, but that did not reveal anything new.
Not sure adding debug statements would be illuminating.
Might first try using Java's built-in Simple Web Server to serve static content from a subdirectory of System.getProperty("java.io.tmpdir").
Also, did you inspect the extracted file to see if they or their directories were marked restricted or otherwise had their com.apple.rootless extended attribute set?
Just for protocol here are the filesystem rights - so it's not a matter of user rights or SIP per se.
❯ ls -lOd /var/folders/…/liveview/ /var/folders/…/liveview/* /var/folders/…/liveview/*/*
drwxr-xr-x@ 4 MY_USER_NAME staff - 128 27 Jan 12:26 liveview/
drwxr-xr-x@ 8 MY_USER_NAME staff - 256 27 Jan 12:26 liveview/app
-rw-r--r--@ 1 MY_USER_NAME staff - 2423373 27 Jan 12:26 liveview/client.zip
-rw-r--r--@ 1 MY_USER_NAME staff - 153 27 Jan 12:26 liveview/app/app-config.json
drwxr-xr-x@ 22 MY_USER_NAME staff - 704 27 Jan 12:26 liveview/app/assets
-rw-r--r--@ 1 MY_USER_NAME staff - 1150 27 Jan 12:26 liveview/app/fluentmark_doc.ico
drwxr-xr-x@ 5 MY_USER_NAME staff - 160 27 Jan 12:26 liveview/app/images
-rw-r--r--@ 1 MY_USER_NAME staff - 1186 27 Jan 12:26 liveview/app/index-0916.html
-rw-r--r--@ 1 MY_USER_NAME staff - 1186 27 Jan 12:26 liveview/app/index.html
My current guess (which I'll try to prove) is a combination of missing or wrong entitlements of the Eclipse.app or the OpenJDK.bundle (https://developer.apple.com/documentation/bundleresources/entitlements). This could lead the MacOS gatekeeper to instruct MacOS launchd to run Eclipse in a Sandbox (https://developer.apple.com/documentation/security/app_sandbox/). But on the other hand - I just ran eclipse via Terminal, so not via launchd (≠ Double Click the App, but execute /Application/Eclipse.app/Contents/MacOS/eclipse in shell) and got the same results, a 404 from the jetty. So it is somehow mysterious to me.
Running the Simple Web Server, as you suggested, obviously works out of the box, because I run it from my privileged shell-session in Terminal. So this does not reveal any new insights. Therefore I'm going to debug the resource-handler you've bound to the server in your plugin, to see what is actually going on.
❯ /opt/…/bin/jwebserver -o verbose -b 127.0.0.1 -p 8081 -d /var/folders/…/liveview/app/
Serving /var/folders/…/liveview/app and subdirectories on 127.0.0.1 (all interfaces) port 8081
URL http://127.0.0.1:8081/
127.0.0.1 - - [27/Jan/2024:12:51:33 +0100] "GET /index-0916.html HTTP/1.1" 200 -
Resource requested: /var/folders/…/liveview/app/index-0916.html
> Accept-encoding: gzip, deflate
> Sec-fetch-dest: document
> Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
> Connection: keep-alive
> Sec-fetch-site: none
> Host: 127.0.0.1:8081
> Sec-fetch-mode: navigate
> User-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15
> Accept-language: de-DE,de;q=0.9
> Upgrade-insecure-requests: 1
> If-modified-since: Sat, 27 Jan 2024 11:26:24 GMT
>
< Date: Sat, 27 Jan 2024 11:51:33 GMT
< Last-modified: Sat, 27 Jan 2024 11:26:24 GMT
< Content-type: text/html
< Content-length: 1186
<
…
Hope, I'll find some new insights (and not wasting our time).
@grosenberg Got it. And I was on a completely wrong path. It has nothing to do with SIP, Gatekeeper, Entitlements or the like, so sorry for my initial “overexcitement”.
As already stated in my PR (https://github.com/grosenberg/Fluentmark/pull/78) on MacOS /var is a symlink to /private/var. Hence java's default temporary directory /var/folders/… actually points to /private/var/folders/….
After debugging the server-implementation for 2 hours without any new insights, I started digging through some similar/related issues, like …
- https://github.com/jetty/jetty.project/issues/8259
- https://github.com/jetty/jetty.project/issues/8296
This brought me back on the right track. Let's just use the realpath in the resource-handler's base-resource.
My tiny PR (https://github.com/grosenberg/Fluentmark/pull/78) has been tested successfully on MacOS.
Btw - disabling SIP removes the symlink from /private/var to /var- that's why it worked. 🤦
Very much appreciate your digging in to find a fix.
Please run and report the actual results (sans any personally identifying information) of the following:
Path path = Path.of(System.getProperty("java.io.tmpdir"), "test");
Path real = path.toRealPath();
System.out.print(String.format("Path is '%s'\n", path));
System.out.print(String.format("Real is '%s'\n", real));
Thanks.
Path path = Path.of(System.getProperty("java.io.tmpdir"), "test");
/var/folders/_v/bq1zqdzn0w5f4fzh7k7zdk5m0000gn/T/test
Path real = path.toRealPath();
Ausnahme java.nio.file.NoSuchFileException: /var/folders/_v/bq1zqdzn0w5f4fzh7k7zdk5m0000gn/T/test | at UnixException.translateToIOException (UnixException.java:92) | at UnixException.rethrowAsIOException (UnixException.java:106) | at UnixException.rethrowAsIOException (UnixException.java:111) | at UnixPath.toRealPath (UnixPath.java:834) | at (#2:1)
On my system:
Seems to expect an existing file!?
Again
try {
Path path = Path.of(System.getProperty("java.io.tmpdir"), "test");
path.toFile().mkdirs();
Path real = path.toRealPath();
System.out.print(String.format("Path is '%s'\n", path));
System.out.print(String.format("Real is '%s'\n", real));
} catch (Exception e) {
System.out.print(String.format("Except: '%s'\n", e.getMessage()));
}
@sjorek
If you still have your test build of Fluentmark, please test by:
-
reverse your proposed change
-
in
net.certiv.fluent.dt.vis.server.LiveServer.java, at about line 148 , comment out the following line:chx.clearAliasChecks();
Thanks
Again
try { Path path = Path.of(System.getProperty("java.io.tmpdir"), "test"); path.toFile().mkdirs(); Path real = path.toRealPath(); System.out.print(String.format("Path is '%s'\n", path)); System.out.print(String.format("Real is '%s'\n", real)); } catch (Exception e) { System.out.print(String.format("Except: '%s'\n", e.getMessage())); }
Path is '/var/folders/_v/bq1zqdzn0w5f4fzh7k7zdk5m0000gn/T/test' Real is '/private/var/folders/_v/bq1zqdzn0w5f4fzh7k7zdk5m0000gn/T/test'
@upeuker - thank you.
Fairly certain that removing chx.clearAliasChecks() will fix the problem (appears to remove symbolic link support on *nix-like systems). @sjorek, can you confirm?
Thanks.
@grosenberg Hmmm - jetty docs state:
By default, the context is created with the AllowedResourceAliasChecker which is configured to allow symlinks. If this alias checker is not required, then clearAliasChecks()
And AllowedResourceAliasChecker:
This will approve any alias to anything inside of the ContextHandlers resource base, which is not protected by a protected target as defined by ContextHandler.getProtectedTargets() at start. Aliases approved by this may still be able to bypass SecurityConstraints, so this class would need to be extended to enforce any additional security constraints that are required.
I do not (yet) know what exactly alias means here. And I'm not that sure, that serving content from outside the “DocumentRoot” is a good idea - if removing ctx.clearAliasChecks() will work. So using the realpath is in other circumstances a sane solution.
But I'll test removing ctx.clearAliasChecks() and give appropriate feedback tomorrow.
@sjorek - thanks for checking.
IIUC, the only effect of removing ctx.clearAliasChecks() is to not remove the symbolic link checker that is added by default in the constructor of the ContextHandler. Keeping the checker should enable what .toRealPath() is doing explicitly: allow Jetty to serve resources from /private/var/folders/....
Moving from overly-strict to a default level of security should be acceptable if it fixes the bug.
@grosenberg Good Morning!
I just rebuild and tested your plugin¹ with removed ctx.clearAliasChecks() and without the real-path as resource-base. And surprise-surprise: it does not work.
So I followed the documentation about jetty's alias-checks again:
From AllowedResourceAliasChecker: This will approve any alias to anything inside of the ContextHandlers resource base … Means the default alias-checks allow symlinks, which start and end within the resource-base.
Adding the slightly more relaxed alternative SymlinkAllowedResourceAliasChecker, also does not solve the problem: An extension of AllowedResourceAliasChecker, which will allow symlinks alias to arbitrary targets, so long as the symlink file itself is an allowed resource. Means this alias-check allows symlinks, which start within the resource-base and end somewhere.
So my conclusions are:
- our resource-base (on MacOS) is
/var/folders/…/livechecker, the symlink/var, pointing to/private/var, is outside the resource-base - adding one of the existing resource-context-related alias-checks, will never solve the problem and weakens security anyway
- The realpath can only be created for existing resources, as @upeuker proved in https://github.com/grosenberg/Fluentmark/issues/73#issuecomment-1923021178
- This applies to all implementations of realpaths, as far as I know, so use the real-path of
System.getProperty("java.io.tmpdir")to construct the resource-base directory
- This applies to all implementations of realpaths, as far as I know, so use the real-path of
- We should not just fix the resource-handler's base-dir, but instead the live-server root-dir, by using the canonical-path of the live-server's root-dir, which delivers the realpath, but would also resolves relative-directories into absolute ones.
Then it works:
10:04:56 [INFO ] core.DslCore : 98 - Plugin 'net.certiv.fluent.dt.core' starting.
10:04:56 [INFO ] dt.ui.FluentUI : 26 - Plugin 'net.certiv.fluent.dt.ui' starting.
10:04:56 [INFO ] ui.editor.DslEditor : 3098 - Fluent editor starting...
10:04:56 [INFO ] core.parser.DslSourceParser : 92 - Readme.md :: Md: parse 292,0 ms: analyze 15,0 ms; tree VALID
10:04:56 [INFO ] core.parser.DslSourceParser : 92 - Readme.md :: Md: parse 120,0 ms: analyze 3,0 ms; tree VALID
10:05:00 [DEBUG] dt.vis.handlers.OpenLiveHandler$1 : 54 - Waiting (0)...
10:05:00 [DEBUG] dt.vis.FluentVis : 78 - Liveserver starting...
10:05:00 [INFO ] dt.vis.server.LiveServer : 170 - Liveserver '127.0.0.1:9025//liveview'
10:05:00 [INFO ] dt.vis.server.LiveServer : 171 - WS server dump: Server@154a317c{STARTED}[11.0.15,sto=0]
10:05:00 [DEBUG] dt.vis.FluentVis : 80 - Liveserver started.
10:05:00 [DEBUG] dt.vis.FluentVis : 78 - Liveserver starting...
10:05:00 [WARN ] dt.vis.server.LiveServer : 93 - Liveserver already running.
10:05:00 [DEBUG] dt.vis.FluentVis : 80 - Liveserver started.
10:05:00 [DEBUG] dt.vis.server.SessionMap : 49 - Created entry: Readme.md
10:05:00 [DEBUG] dt.vis.FluentVis : 132 - Prepared app @http://127.0.0.1:9025/index-0078.html [Readme.md::/private/var/folders/pp/XXXXXXXXXXXX/liveview/app]
10:05:00 [DEBUG] dt.vis.handlers.OpenLiveHandler$1 : 69 - Live client page at http://127.0.0.1:9025/index-0078.html
10:05:00 [DEBUG] dt.vis.server.MsgHandler : 53 - Websocket connected: initialized message handler.
10:05:00 [DEBUG] dt.vis.server.LiveServer$Monitor : 375 - Tracking Readme.md
10:05:02 [DEBUG] dt.vis.server.LiveServer : 262 - Connect source 'Readme.md' -> /127.0.0.1:54968
10:05:02 [INFO ] dt.vis.server.MsgHandler : 106 - WS Refresh: '' at /127.0.0.1:54968
10:05:02 [ERROR] dt.vis.server.LiveServer : 359 - WS send message failed: Cannot invoke "org.eclipse.jetty.websocket.api.Session.getRemote()" because "session" is null
10:05:03 [INFO ] dt.vis.server.MsgHandler : 106 - WS Refresh: 'Readme.md' at /127.0.0.1:54968
10:05:03 [WARN ] dt.vis.util.LiveUtil : 128 - Replacing image '/private/var/folders/XXXXXXXXXXXX/liveview/app/doc/Logo110x80.png'
10:05:03 [WARN ] dt.vis.util.LiveUtil : 128 - Replacing image '/private/var/folders/XXXXXXXXXXXX/liveview/app/doc/Toolbar.png'
10:05:03 [WARN ] dt.vis.util.LiveUtil : 128 - Replacing image '/private/var/folders/XXXXXXXXXXXX/liveview/app/doc/ScreenShot.png'
I updated my PR #78 and its description accordingly.
- Btw. It was quite hard to build your plugin, because you reference windows local-directories and jars, which are not (yet) (publicly) released. This is what one needs to patch, to build
net.certiv.fluent.dt.vis.….jar:
diff --git a/net.certiv.fluent.dt.vis/META-INF/MANIFEST.MF b/net.certiv.fluent.dt.vis/META-INF/MANIFEST.MF
index b3b6d66..563b5bf 100644
--- a/net.certiv.fluent.dt.vis/META-INF/MANIFEST.MF
+++ b/net.certiv.fluent.dt.vis/META-INF/MANIFEST.MF
@@ -8,9 +8,9 @@ Bundle-Name: Fluent Vis
Bundle-SymbolicName: net.certiv.fluent.dt.vis;singleton:=true
Bundle-Version: 1.9.0.qualifier
Bundle-Activator: net.certiv.fluent.dt.vis.FluentVis
-Require-Bundle: net.certiv.dsl.core;bundle-version="0.32.0",
- net.certiv.dsl.lib;bundle-version="0.32.0",
- net.certiv.dsl.ui;bundle-version="0.32.0",
+Require-Bundle: net.certiv.dsl.core;bundle-version="0.31.0",
+ net.certiv.dsl.lib;bundle-version="0.31.0",
+ net.certiv.dsl.ui;bundle-version="0.31.0",
net.certiv.fluent.dt.core;bundle-version="1.9.0",
net.certiv.fluent.dt.ui;bundle-version="1.9.0",
org.eclipse.core.resources;bundle-version="3.18.100",
diff --git a/net.certiv.fluent.dt.vis/src/main/java/net/certiv/fluent/dt/vis/server/LiveServer.java b/net.certiv.fluent.dt.vis/src/main/java/net/certiv/fluent/dt/vis/server/LiveServer.java
index b97f7e5..24c7e5c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,9 +37,9 @@
<certiv.root>${local.root}/Tools/Certiv</certiv.root>
<dsl.root>${local.root}/Tools/Dsl</dsl.root>
- <tools.repo>${certiv.root}/net.certiv.tools.parent/net.certiv.tools.site/target/repository</tools.repo>
- <dsl.repo>${dsl.root}/net.certiv.dsl/net.certiv.dsl.site/target/repository</dsl.repo>
- <spell.repo>${dsl.root}/net.certiv.spellchecker/site/target/repository</spell.repo>
+ <tools.repo>https://www.certiv.net/updates/net.certiv.tools.site/</tools.repo>
+ <dsl.repo>https://www.certiv.net/updates/net.certiv.dsl.site/</dsl.repo>
+ <spell.repo>https://www.certiv.net/updates/</spell.repo>
<jetty.version>11.0.15</jetty.version>
<log4j.ver>2.20.0</log4j.ver>
@sjorek - excellent analysis. Thanks.
We should not just fix the resource-handler's base-dir, but instead the live-server root-dir, by using the canonical-path of the live-server's root-dir, which delivers the realpath, but would also resolves relative-directories into absolute ones.
The fix will be rolled into the next release, due very shortly.
New release is now live.