Skip to content
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

gui: add advance config port mapping to gui #7017

Merged
merged 56 commits into from Nov 10, 2020
Merged

Conversation

rjpruitt16
Copy link
Contributor

Purpose

#4824

Gives the user the opportunity to display a link to web gui remote machines. The user can choose the port and set if the link will display.

Testing

I just check to see if the link was shown depending on the setting.

Screenshots

Screen Shot 2020-09-29 at 11 13 53 PM
Screen Shot 2020-09-29 at 11 34 37 PM

Documentation

syncthing/docs#573

Authorship

Your name and email will be added automatically to the AUTHORS file
based on the commit metadata.

Copy link
Member

@calmh calmh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! I have some specific comments below

gui/default/syncthing/core/syncthingController.js Outdated Show resolved Hide resolved
gui/default/syncthing/core/syncthingController.js Outdated Show resolved Hide resolved
gui/default/syncthing/core/syncthingController.js Outdated Show resolved Hide resolved
gui/default/index.html Outdated Show resolved Hide resolved
@calmh
Copy link
Member

calmh commented Sep 30, 2020

There's a tradeoff here between having to configure the web_ui boolean and having the UI auto probe reachability. The latter is more convenient and more precise, but can also be a lot of work for the browser when loading a config with many devices...

gui/default/index.html Outdated Show resolved Hide resolved
@tomasz1986
Copy link
Contributor

tomasz1986 commented Sep 30, 2020

image

I think that it may be better to have a more descriptive name for this in the Advanced Configuration. "Web Ui" as such does not really mean anything, as we are in the Web UI right now, are we not 😉? Maybe WebUIAddress or ShowWebAddress?

I would also suggest using capitals for UI, as it is the case with most other acronyms like URL, TLS, LDAP, etc.

@AudriusButkevicius
Copy link
Member

To be honest, I'd prefer if this used probing, as otherwise, I can't see this being a feature that will get any use, because I can't see people finding it and enabling it.

@rjpruitt16
Copy link
Contributor Author

I'm open to port scanning, but I'm just a little lost on where to start. I see libraries with javascript implementation of port sweeping, but I did not know if adding a library is the best decision. Does anybody now of any javascript port sweeping articles? If I had to write it from scratch, I would send a GET request to a range of ports until a success header with X-Syncthing-Id return.

@AudriusButkevicius
Copy link
Member

I don't think we want port scan, but we probably want to check if we can reach a web server on the other ip on the right port before showing it in the ui. No point showing something that is unreachable.

@rjpruitt16
Copy link
Contributor Author

i thought port scanning and probing were similar. My mistake. Does anyone have any suggestion about the probing technique. I am suggesting doing a http get request on a range of ports until the 200 response with X-Syncthing-id

@AudriusButkevicius
Copy link
Member

AudriusButkevicius commented Sep 30, 2020

Ok, this is how I would do it, I guess it's up for other maintainers to agree/disagree.

You don't need to do it on a range of ports.
You still have a settings entry with the port.
It defaults to 8384, but people can change it.
In the UI, you check if you can reach :<preconfigured port which is 8384 by default>, if you can - you show the link it in the UI, if you can't you don't need to show anything in the UI.

For LAN scenarios where the GUI is listening on 0.0.0.0, this will "Just work", for cases where people are behind a NAT and what not, this won't work, but we don't expect it to without additional human effort.

There is still the whole "I have a million devices, I don't want a million requests upon accessing the UI", so there should probably be a switch to disable this all together.

@calmh
Copy link
Member

calmh commented Oct 1, 2020

Yes. Probe, with a default port. A large off switch to not do it at all.

@rjpruitt16
Copy link
Contributor Author

What range of ports should it look for the gui in?

@AudriusButkevicius
Copy link
Member

You don't need to look for a range. It's a single port preconfigured like you have now, defaulting to 8384

@rjpruitt16
Copy link
Contributor Author

Im confused. I thought @calmh just said to probe. I thought probe meant to look for the gui over a range of ports.

@calmh
Copy link
Member

calmh commented Oct 1, 2020

No. Probe the configured port. If it works, show the link.

@rjpruitt16
Copy link
Contributor Author

Understood.

@rjpruitt16
Copy link
Contributor Author

rjpruitt16 commented Oct 2, 2020

I am getting a cors error when trying to make a head request to connected machines. I am sure this could be fix by me adding remote devices to the CORS policy, but it seems like a big security design decision that I would like to make sure it okay before continuing.

@AudriusButkevicius
Copy link
Member

AudriusButkevicius commented Oct 2, 2020

I guess getting a cors error is a form of success, it's not a timeout.

Not sure if you have a way to tell from what the browser returns tho.

@rjpruitt16
Copy link
Contributor Author

Here is a look at the current state of my probe function. The ip address has authentication setup.

$scope.probeAddress = function (address) {
            $http.head(address, {
                header: {
                    "Access-Control-Allow-Origin": "*"
                }
            }).success(function (data) {
                console.log(data)
            }).error($scope.emitHTTPError);
        }

This is the error I am getting.
Screen Shot 2020-10-02 at 3 10 25 PM

I tried it again in insomnia (it similar to postman), and it sends me back that authentication is required.
Screen Shot 2020-10-02 at 3 09 52 PM

@rjpruitt16
Copy link
Contributor Author

it seems I need to set username and password to make request to machines with an authorization set up as well.

@AudriusButkevicius
Copy link
Member

So we don't need the request to succeed, we just need to know if there is anything on the other side or not, hence we're fine to get a 401 or a cors error, as long as it's not a timeout.

This might have some relevant info:
https://stackoverflow.com/questions/19325314/how-to-detect-cross-origin-cors-error-vs-other-types-of-errors-for-xmlhttpreq

@rjpruitt16
Copy link
Contributor Author

I am not finished.

Sometimes the link shows up correctly. Other times I think it is showing the address of a machine port forwarding to the device. it seems the $scope.connections address is not always the real address of the device.

@AudriusButkevicius
Copy link
Member

AudriusButkevicius commented Oct 3, 2020

If it's connected via relay etc, it doesn't make sense to do the port check. so I'd check what connection type you have.

@rjpruitt16
Copy link
Contributor Author

I did not see any documentation on the connectioninfo.type. it seems to me if the connectionInfo.type is "tcp-server", than this means the connection type is the actually the device. If the type is "tcp-client", it represent a relay. Is this feature only going to work when port forwarding is not on. Is there a way to track down the final destination?

@AudriusButkevicius
Copy link
Member

There should be no difference for you between tcp server and tcp client. To be honest, the only time when you should not bother is when the connection type starts with relay

Strip off the zone identifier from link-local addresses before
probing.  They will still fail if the browser does not allow
link-local IPv6 connections, but this at least avoids the "malformed
URL" error message, which is really a bug in AngularJS.
@acolomb
Copy link
Member

acolomb commented Nov 8, 2020

What we should try to avoid is an error about a malformed URL from the Angular $http library, though.

In my opinion an already known potential error needs to be dealt with before merging an optional enhancement like this. Either catch the error somehow to ignore it or filter problematic addresses before to not even try them.

Not handling link-local IPv6 addresses correctly is definitely a bug in AngularJS and also hard to catch in our code. The error message stack trace is contains only entries from the file angular.js:

Error: The URI is malformed.
createHttpBackend/<@http://127.0.0.1:8385/vendor/angular/angular.js:9851:11
sendReq@http://127.0.0.1:8385/vendor/angular/angular.js:9713:21
serverRequest@http://127.0.0.1:8385/vendor/angular/angular.js:9425:16
processQueue@http://127.0.0.1:8385/vendor/angular/angular.js:13337:27
scheduleProcessQueue/<@http://127.0.0.1:8385/vendor/angular/angular.js:13353:39
$eval@http://127.0.0.1:8385/vendor/angular/angular.js:14589:28
$digest@http://127.0.0.1:8385/vendor/angular/angular.js:14405:31
$apply@http://127.0.0.1:8385/vendor/angular/angular.js:14694:24
scheduleApplyAsync/applyAsyncId<@http://127.0.0.1:8385/vendor/angular/angular.js:14984:22
completeOutstandingRequest@http://127.0.0.1:8385/vendor/angular/angular.js:4959:10
Browser/self.defer/timeoutId<@http://127.0.0.1:8385/vendor/angular/angular.js:5347:33
setTimeout handler*Browser/self.defer@http://127.0.0.1:8385/vendor/angular/angular.js:5345:17
scheduleApplyAsync@http://127.0.0.1:8385/vendor/angular/angular.js:14983:33
$applyAsync@http://127.0.0.1:8385/vendor/angular/angular.js:14722:9
done@http://127.0.0.1:8385/vendor/angular/angular.js:9741:22
completeRequest@http://127.0.0.1:8385/vendor/angular/angular.js:9934:15
requestLoaded@http://127.0.0.1:8385/vendor/angular/angular.js:9875:24
EventHandlerNonNull*createHttpBackend/<@http://127.0.0.1:8385/vendor/angular/angular.js:9858:7
sendReq@http://127.0.0.1:8385/vendor/angular/angular.js:9713:21
serverRequest@http://127.0.0.1:8385/vendor/angular/angular.js:9425:16
processQueue@http://127.0.0.1:8385/vendor/angular/angular.js:13337:27
scheduleProcessQueue/<@http://127.0.0.1:8385/vendor/angular/angular.js:13353:39
$eval@http://127.0.0.1:8385/vendor/angular/angular.js:14589:28
$digest@http://127.0.0.1:8385/vendor/angular/angular.js:14405:31
$apply@http://127.0.0.1:8385/vendor/angular/angular.js:14694:24
scheduleApplyAsync/applyAsyncId<@http://127.0.0.1:8385/vendor/angular/angular.js:14984:22
completeOutstandingRequest@http://127.0.0.1:8385/vendor/angular/angular.js:4959:10
Browser/self.defer/timeoutId<@http://127.0.0.1:8385/vendor/angular/angular.js:5347:33
setTimeout handler*Browser/self.defer@http://127.0.0.1:8385/vendor/angular/angular.js:5345:17
scheduleApplyAsync@http://127.0.0.1:8385/vendor/angular/angular.js:14983:33
$applyAsync@http://127.0.0.1:8385/vendor/angular/angular.js:14722:9
done@http://127.0.0.1:8385/vendor/angular/angular.js:9741:22
completeRequest@http://127.0.0.1:8385/vendor/angular/angular.js:9934:15
requestLoaded@http://127.0.0.1:8385/vendor/angular/angular.js:9875:24
EventHandlerNonNull*createHttpBackend/<@http://127.0.0.1:8385/vendor/angular/angular.js:9858:7
sendReq@http://127.0.0.1:8385/vendor/angular/angular.js:9713:21
serverRequest@http://127.0.0.1:8385/vendor/angular/angular.js:9425:16
processQueue@http://127.0.0.1:8385/vendor/angular/angular.js:13337:27
scheduleProcessQueue/<@http://127.0.0.1:8385/vendor/angular/angular.js:13353:39
$eval@http://127.0.0.1:8385/vendor/angular/angular.js:14589:28
$digest@http://127.0.0.1:8385/vendor/angular/angular.js:14405:31
$apply@http://127.0.0.1:8385/vendor/angular/angular.js:14694:24
scheduleApplyAsync/applyAsyncId<@http://127.0.0.1:8385/vendor/angular/angular.js:14984:22
completeOutstandingRequest@http://127.0.0.1:8385/vendor/angular/angular.js:4959:10
Browser/self.defer/timeoutId<@http://127.0.0.1:8385/vendor/angular/angular.js:5347:33
setTimeout handler*Browser/self.defer@http://127.0.0.1:8385/vendor/angular/angular.js:5345:17
scheduleApplyAsync@http://127.0.0.1:8385/vendor/angular/angular.js:14983:33
$applyAsync@http://127.0.0.1:8385/vendor/angular/angular.js:14722:9
done@http://127.0.0.1:8385/vendor/angular/angular.js:9741:22
completeRequest@http://127.0.0.1:8385/vendor/angular/angular.js:9934:15
requestLoaded@http://127.0.0.1:8385/vendor/angular/angular.js:9875:24
EventHandlerNonNull*createHttpBackend/<@http://127.0.0.1:8385/vendor/angular/angular.js:9858:7
sendReq@http://127.0.0.1:8385/vendor/angular/angular.js:9713:21
serverRequest@http://127.0.0.1:8385/vendor/angular/angular.js:9425:16
processQueue@http://127.0.0.1:8385/vendor/angular/angular.js:13337:27
scheduleProcessQueue/<@http://127.0.0.1:8385/vendor/angular/angular.js:13353:39
$eval@http://127.0.0.1:8385/vendor/angular/angular.js:14589:28
$digest@http://127.0.0.1:8385/vendor/angular/angular.js:14405:31

I could only avoid that by removing the zone identifier before the probing request. That yields an invalid address, which makes the browser's CORS check fail. That in turn avoids displaying a dysfunctional link.

Ideally, if the Angular "malformed URL" bug was fixed, we should stop doing that, so at least compliant browsers without that limitation can actually work with the (properly encoded) address.

@imsodin
Copy link
Member

imsodin commented Nov 8, 2020

I could only avoid that by removing the zone identifier before the probing request. That yields an invalid address, which makes the browser's CORS check fail. That in turn avoids displaying a dysfunctional link.

Does the check fail all the time? Then we could just skip if there is a zone identifier without probing.

@acolomb
Copy link
Member

acolomb commented Nov 8, 2020

Does the check fail all the time? Then we could just skip if there is a zone identifier without probing.

Depends on the browser, so really hard to say.

calmh added a commit to calmh/syncthing that referenced this pull request Nov 9, 2020
* main:
  lib/folder: Clear pull errors when nothing is needed anymore (syncthing#7093)
  lib/api: Fix debug endpoints (ref syncthing#7001) (syncthing#7092)
  gui, man, authors: Update docs, translations, and contributors
  lib/config: Sanity checks on MaxConcurrentWrites (ref syncthing#7064) (syncthing#7069)
  lib/ur: Fix panics in failure-reporting (fixes syncthing#7090) (syncthing#7091)
  lib/ur: Fix panics in failure-reporting (fixes syncthing#7090) (syncthing#7091)
  build: Update dependencies (syncthing#7088)
  lib: Remove USE_BADGER experiment (syncthing#7089)
  build: Update notify (fixes syncthing#7063) (syncthing#7080)
  lib/api: Fix /rest/config path and add methods to cors (ref syncthing#7001) (syncthing#7081)
  lib/api: Allow OPTIONS method in CORS preflight request handling (ref syncthing#7017) (syncthing#7079)
  gui: Fix another undefined variable access (fixes syncthing#7077) (syncthing#7078)
  lib/config: Check for "msdos" when detecting FAT FS in Android (syncthing#7072)
  gui, man, authors: Update docs, translations, and contributors
@imsodin
Copy link
Member

imsodin commented Nov 9, 2020

lgtm (except for a tiny merge conflict). With the untrusted PR merged, the changes here need to be copied over to the file in gui/default/untrusted. Someone can either do it in the PR or otherwise I'll take care of that in a followup commit once merged.

@acolomb
Copy link
Member

acolomb commented Nov 9, 2020

I will merge in main tonight. Hope that's still in time for the next rc?

Adjust protobuf field for deviceconfiguration.  Untrusted was added
as no. 17, so move remote_gui_port to 18.
@acolomb
Copy link
Member

acolomb commented Nov 9, 2020

Merge conflicts fixed and incorporated the HTML changes to the untrusted GUI.

@acolomb
Copy link
Member

acolomb commented Nov 9, 2020

Sorry, overlooked that syncthingController.js was also duplicated. Ready for merging from my point of view. @imsodin

Strange merge failure, moved the addition to a different section somehow?!
@acolomb
Copy link
Member

acolomb commented Nov 9, 2020

No good doing things in a hurry. That was some kind of very strange merge conflict from the untrusted branch I suppose. Now the HTML code inserted by this PR is in the right spot again, so the link actually shows up correctly.

Sorry again for this confusion.

@imsodin imsodin merged commit 2f6a25a into syncthing:main Nov 10, 2020
@calmh calmh added this to the v1.12.0 milestone Nov 10, 2020
$scope.idToRemoteGUI[id] = "";
continue;
}
var newAddress = "http://" + replaceAddressPort(connections[id].address, port);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work when the GUI is loaded over HTTPS, as the request is blocked by the browser.

Screen Shot 2020-11-10 at 12 14 58

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just handle the error?

We can set upgrade insecure url in the options request header. Is this acceptable?

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Upgrade-Insecure-Requests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will see if it solves it tomorrow.

Copy link
Member

@acolomb acolomb Nov 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a Really Hard Problem.

Content-Security-Policy basically always disallows mixed content (HTTP request from a script loaded through HTTPS). There are some additional headers to control that, but apparently can only further restrict access.

On the other hand, doing probe requests to HTTPS directly will fail almost always. Either the remote GUI uses the auto-generated self-signed TLS certificate, or it has a proper TLS cert, which does not match the IP address we're using to construct the remote GUI address (only the cert domain name). Either case leads to the browser blocking the request because of an insecure connection.

One solution on the browser side would be to bypass CSP for OPTIONS requests completely, since these provide no displayed content and therefore pose no security threat. Still a possible privacy problem though.

What we could do on the Syncthing side is looking at the argument of the request error callback. If the request was blocked by the browser already, that err argument is null. Generating an http:// link in that case still works because clicking on that is not blocked by CSP (as tested under Firefox). Such a check produces some false-positives, though, as there are many reasons why the browser could have blocked. The OPTIONS method not being allowed in the preflight request (on versions before #7079 was implemented) exhibits the same err === null condition for example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should remove the probing in its entirety and just generate an url with the hope of it being useful. I very much wanted the probing approach to work but it seems to be low value...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, ironically browsers have become a rather difficult environment for network programming. I'm also quite disappointed by all these restrictions, but they do make sense in the name of security within a WWW browsing world.

Especially the problem with link-local IPv6 addresses being unsupported is very unfortunate. They are very common as a Syncthing connection on LANs, but often unpredictable. If using Bonjour / mDNS or similar, browsers will happily make such connections, but not with IPv6 literals :-( That's still a problem even without trying to probe, but maybe more bearable.

@calmh calmh removed this from the v1.12.0 milestone Nov 10, 2020
calmh added a commit to calmh/syncthing that referenced this pull request Nov 11, 2020
* main: (42 commits)
  gui: Initialise sharing when accepting new device (fixes syncthing#7113) (syncthing#7114)
  gui: Initialise sharing when accepting new device (fixes syncthing#7113) (syncthing#7114)
  lib/model: Prevent test deadlock (syncthing#7110)
  lib/api, lib/db: Add file debug endpoint (syncthing#7095)
  gui: Add advance config port mapping to gui (fixes syncthing#4824) (syncthing#7017)
  lib/tlsutil: Add O and OU to generated certificates (fixes syncthing#7108) (syncthing#7109)
  all: Add untrusted folders behind feature flag (ref syncthing#62) (syncthing#7055)
  build: Update notify (fixes syncthing#5360) (syncthing#7106)
  lib/model: Fix locking when resending cluster-configs (syncthing#7107)
  gui: Remove superfluous translate in previous (ref syncthing#7102)
  gui: Add warning when JavaScript is disabled in Web browser (fixes syncthing#7099) (syncthing#7102)
  lib/model: Add done chan to track folder-lifetime (fixes syncthing#6664) (syncthing#7094)
  lib/model: Send indexes for newly shared folder (fixes syncthing#7098) (syncthing#7100)
  lib/folder: Clear pull errors when nothing is needed anymore (syncthing#7093)
  lib/api: Fix debug endpoints (ref syncthing#7001) (syncthing#7092)
  gui, man, authors: Update docs, translations, and contributors
  lib/config: Sanity checks on MaxConcurrentWrites (ref syncthing#7064) (syncthing#7069)
  lib/ur: Fix panics in failure-reporting (fixes syncthing#7090) (syncthing#7091)
  lib/ur: Fix panics in failure-reporting (fixes syncthing#7090) (syncthing#7091)
  build: Update dependencies (syncthing#7088)
  ...
@calmh calmh added this to the v1.12.1 milestone Nov 18, 2020
calmh added a commit to calmh/syncthing that referenced this pull request Nov 20, 2020
This removes the probing for the remote side, instead making it so we
simply construct the address based on the currently connected address,
if any, and let the user sort out whether it works or not.
acolomb added a commit to acolomb/syncthing-docs that referenced this pull request Dec 29, 2020
calmh pushed a commit to syncthing/docs that referenced this pull request Dec 30, 2020
@st-review st-review added the frozen-due-to-age Issues closed and untouched for a long time, together with being locked for discussion label Nov 11, 2021
@syncthing syncthing locked and limited conversation to collaborators Nov 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
frozen-due-to-age Issues closed and untouched for a long time, together with being locked for discussion
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants