Mjpg-streamer password-protected or bound to loopback IP

What is the problem?

I'm currently working on a best practice implementation for mjpg-streamer and OctoPrint. Best would be to by default have the mjpg-streamer stream password-protected and bound to localhost/127.0.0.1 to restrict access as much as possible. However, OctoPrint seems to not being able to deal with both:

  • In case of password-protection, the URL would need to contain credentials, like http://user:password@host/?action=stream, but if I got it right, this is already prevented on browser-level: https://github.com/OctoPrint/OctoPrint/issues/3056
    While I cannot reproduce a related browser console error, I can at least verify, that it does not work for the stream in OctoPrint. As fast as credentials are removed, it works fine.
  • Now, if no password-protection is possible, it would at least great to have mjpg-streamer bound to the loopback IP to prevent any non-local access. While this is perfectly possible at mjpg-streamer side via -i 'output_http.so -l 127.0.0.1', OctoPrint fails to connect to the Stream when using localhost or any 127.0.0.0/8 IP. This is btw independent from whether mjpg-streamer is actually bound to any IP or not, OctoPrint seems to simply deny using any local IP, it seems. This is btw not an issue I found just by myself, but while I was able to verify, other users reported this already, so it is not related to my system or network.
  • Taking both together, I currently fail to achieve a secure setup with OctoPrint + mjpg-streamer.

Interestingly, snapshots work in both cases, but I think those are cached?

What did you already try to solve it?

  • I tried multiple IP bindings, "localhost" and many 127.0.0.0/8 IPs, checked browser console and OctoPrint logs, but the failing stream connection does not even create a log entry.
  • I verified that otherwise access to the stream via local browser works fine, including sudo -u octoprint curl http://127.0.0.1:8082/?action=stream to rule out any user permissions issue.

Complete Logs

Browser console:

GET http://127.0.0.1:8082/?action=stream&1612704601611 net::ERR_CONNECTION_REFUSED

Additional information about your setup

  • Debian (any)
  • OctoPrint freshly installed via Python 3 pip.
  • mjpg-streamer self-compiled.

Reason for this issue

Actually while writing/copying the browser error here, the underlying problem became clear (EDIT: Okay, it's even stated in the web UI settings :sweat_smile:!):

  • The connection to the webcam stream is not done from OctoPrint (i.e. locally), but from the browser, which accesses remotely. Using a loopback host, makes the browser hence accessing itself, which of course can only fail.
  • It is hence necessary currently to use the remote and in case public hostname/IP for webcam access.
  • This is problematic, because it breaks the ability to e.g. control OctoPrint with webcam stream remotely without exposing the bare webcam stream, without even password-protection, remotely as well.
  • It would be reasonable (but I have no idea if/how to achieve it) when OctoPrint would not present the webcam as resource to be requested by the browser, but would internally do the request locally (or within the local network) and stream it to the browser through the OctoPrint connection. Excuse my probably wrong wording here, I hope you get the idea.

But as neither bound webcam streams nor password-protection is possible, how do you guys setup a secure OctoPrint + webcam stream, or is it them simply not possible currently?

Okay, I see now how OctoPi does it:

  • mjpg-streamer is bound to 127.0.0.1.
  • HAProxy is up to proxy /webcam/ to :8080 (mjpg-streamer port).
  • So mjpg-streamer is accessed locally by HAProxy.
  • But since there is still no authentication enforced(?), I cannot see how the webcam stream is secured with this, but only that it is now available on regular port 80/443 instead of 8080 (or whichever custom port mjpg-streamer listens on) :thinking:.

Generally the mjpg-streamer camera stream can be accessed via POST request, like curl -X POST http://user:password@127.0.0.1:8082/stream (of course curl cannot print binary data to console). So if I'm not mistaken, it would be possible to implement a change into OctoPrint to not let the browser establish the connection to the camera stream, but do it directly via POST request to loopback/localhost, so that mjpg-streamer could be bound to 127.0.0.1 as well as authentication could be applied (for max protection :wink:). This would greatly enhance security, I think, but I have no idea how hard it would be to implement this :sweat_smile:.

Hey an easy way is to use the mjpg-stream config to protect your stream.

workflow:

  • connect to your octopi instance via ssh
  • stop the webcam service if running
 $ sudo systemctl stop webcamd.service
  • edit the config file:
sudo nano /root/bin/webcamd

-then search for the camera_http_options var and modify like:

camera_http_options="-n --listen 127.0.0.1 -c username:password"

-restart the service:

sudo systemctl restart webcamd.service

then your browser is asking you for username and password

you can use your username and password in the url:

`username:password@http://IP:PORT/webcam/?action=stream`

maybe its a solution for you

1 Like

Many thanks for your suggestion. As stated above, this is the very thing I tried and of course it works for mjpg-streamer, but then it cannot be shown within OctoPrint's web interface since browsers do not accept resource URLs with embedded credentials, reasonably for security reasons.

It works for me on the octorprint web interface, you have to edit the url string:

http://USERNAME:PASSWORD@IP:PORT/webcam/?action=stream

Why not leverage HAProxy's configuration (since it's already there for the webcam stream if you're using the default configuration) using for example this guide, and adapt it to secure the whole octoprint instance (by securing the frontend), or only the webcam stream (by securing only the webcam backend).

@Konrad_Munch
Which browser do you use? On my last test, Chromium based browsers denied to accept such an URL for an embedded resource. But this was half a year ago, so probably OctoPrint adapted the suggestions I made above, to internally grab the stream and forward it to the browser, instead of letting the browser grabbing the stream itself. This would mean that you should be able to use 127.0.0.1 as IP for the webcam stream.

@SwHawk
Have you tested this with OctoPrint and embedded webcam stream, so that OctoPrint as well as the webcam stream are both password-protected? As stated above, when testing it half a year ago, it was impossible to password-protect the webcam stream, as then (modern) browsers denied to access it due to embedded credentials. I guess it would be possible indeed when accessing OctoPrint via same hostname as the one given for the webcam stream, and then have everything behind HTTP authentication, so that an authenticated session is stored already when the OctoPrint web interface is accessed, so that probably the same session is used to access the webcam stream (so that no credentials need to be passed). But it is kind of fragile, e.g. what happens when the session ends while the webcam is streaming, or when you access OctoPrint via IP or an alias (this breaks it), and I'm also not sure whether HAProxy and other webservers accept a request without any HTTP authentication flag, even that a current session is active, so this would require testing as well.

Browsers blocking this was what I thought was the problem, I wasn't aware of any specific browsers that worked or didn't work with this - I had the same view as you.

OctoPrint's webcam streaming functions haven't changed, it still works the same way in answer to that question. It could be an interesting option to forward the stream through OctoPrint, maybe it's an option to implement as a plugin or something that can override the built in webcam.

I haven't tested specifically with OctoPrint, though I have already fiddled with Basic Auth. Since the whole website is secured, I wouldn't expect this to fail, and the webcam URLs would be relative (ie /webcam/?action=stream, instead of http://username:password@ip:port/?action=stream).

Although, as a default configuration, OctoPrint expects the REMOTE_USER HTTP header to contain the username from the OctoPrint database. Those settings can be changed in the config.yaml file, in the acl section (refer to this).

You'd have to alter the frontend configuration in the haproxy.cfg file by adding the following line to the frontend section, in order to set the header :
http-request set-header REMOTE_USER %[http_auth_group(GROUP_NAME)] (refer to this Stackoverflow thread to understand why this has to be the group name instead of the user name).
You'd obviously have to adapt the REMOTE_USER and GROUP_NAME to your configuration, as those are only placeholders... A sensible default for REMOTE_USER would be X-Remote-User.

As for the stream and the octoprint session, they're completely independent, they're two different backends to HAProxy, who treats them as such, two independent resources. That is the same behaviour as the default installation on OctoPi... The mjpg-streamer program works independently from OctoPrint. As a matter of fact, you can access it using either http://octopi.local/webcam/?action=stream or locally on the Pi http://127.0.0.1:8080/?action=stream without going through OctoPrint at all...
As for the alias and the IP, that would depend on the configuration of HAProxy. You can set a specific frontend on a specific hostname, or IP, or combination of both. You can check this in the HAProxy documentation.
As for accpeting a request without authentification, that's also configurable, whether or not something should be served if no authentication is present, or whether to issue a 401 or 403 status code.

Also please be aware that Basic Aurhentication does not support sessions... The browser provides the credentials for each resource on the page, so it should be secured through TLS at least so that those credentials are not sent in plaintext over the internet.

As a final note, I wouldn't consider Basic Authentication in itself to be a security measure, due to it being easily hackable in plain HTTP... HTTPS adds a layer of security, but I'd rather use OAuth or OIDC to secure an OctoPrint instance (which actually needs TLS), but that's a whole new level of complexity. I'm thinking about it, although I'm much more familiar with Apache's httpd (which has a module to check against an OAuth or OIDC server) rather than HAProxy (which seems to have some kind of lua module to check the jwt token, rather than the bearer token), but that's something that's on my todo list... Just need to figure out the time to test this, which may very well be in years rather than anything else...

AFAIK OctoPrint even requires a full URL here, but otherwise the browser would complete it with IP:PORT of the OctoPrint web interface, so HTTP authentication would be missing and hence the fragile situation I was describing.

Yes of course, and both are individually perfectly fine reachable, this is not the issue. The issue is when OctoPrint has a frame within its interface with the webcam stream embedded, this is where browsers do not allow (or did not allow) HTTP authentication embedded in the URL. Hence why I asked whether you tested it in exactly this setup :wink:.

Of course you can configure authentication for each individual backend. But it is (or was) not possible to tell the browser to accept embedded credentials as part of an iframe or other resource of a website. And actually there are strong reasons for browsers to behave so and hence discourage everyone to make this a common practice: If a resource has a password protection, then you don't want all visitors of a 3rd party website/application to have it in plain text visible in the 3rd parties HTML source. You actually don't want any password in plain text anywhere. So I would wonder if e.g. Chromium did a U turn in this regards :wink:.

Securing OctoPrint itself is not the question and not an issue and whether one wants to setup OAuth or similar depends on the use case. The issue is the currently completely unprotected webcam stream, hence HTTP authentication would be a minimum (for the webcam, for OctoPrint its trivial). But best would be to bind mjpg-streamer to localhost and have OctoPrint gabbing it directly from there, then you don't need to take care about authentication at all.

Yeah I finally need to retest it with some different browsers when I find time, to be sure that exact issue still exists.

Relative URL work just fine on my current setup. HTTP Authentication, if set on the frontend (thus securing OctoPrint and the webcam stream) would work fine, as all the elements of the page would be secured in the same way.

As stated above by @Charlie_Powell, having OctoPrint restream the mjpg-streamer stream would be a plugin's work.

EDIT : By the way, you've got me confused on some points. You're concerned about the stream security, but not OctoPrint's. So from that I can surmise, you're using OctoPrint on your LAN, which you should consider secure, and which shouldn't be accessible from the Internet. But your wanting to secure access to the stream makes me wonder if you're opening your instance to the internet, or if you consider you LAN insecure. In both cases, I would advise against using OctoPrint at all, as the stream isn't a security risk in and of itself, but as has been pointed out on multiple instances, having a moving and heating machine directly accessible on the internet provides a huge safety hazard (from someone printing something using your printer, to actually setting fire to the print and then to your house). From my point of view, either your LAN is secure, and then, there is no need to secure access to the stream, or your LAN is considered insecure, and OctoPrint shouldn't even be an option... But I may be mistaken, and would really like further details so that I could better answer your questions.

1 Like

I use Firefox and Chrome, with the localhost ( 127.0.0.1) the stream is not working. Only with my external domain address. An another benefit is I can use this URL for the app OctoRemote.
This was a workaround I found yesterday, not perfect but it works.

Ah no sorry, I phrased that misleadingly. I meant that there are no issues to secure OctoPrint itself, so this is not a topic for me and not the question here. The only question is how to secure the webcam stream and still show it within OctoPrint's web interface.

This has got me puzzled... The thing is, I don't understand your use case where you'd want that stream secured, unless you're exposing the instance on an unsecured network...

But still, as stated earlier, putting the frontend (in HAProxy terminology, so on the incoming connection and not each backend) behind Basic Authentication, using HAProxy as a reverse proxy for the stream, and using relative URLs for the stream (as is the default OctoPi implementation (reverse proxy and relative URLs) for now, as mjpg-streamer binds to 127.0.0.1:8080) would solve your problem, since the stream wouldn't be considered as an external resource, thus not falling inside the category you mentioned where browsers wouldn't load such assets. By the way, the stream is actually embedded as an img tag as long as it's a MJPG stream...