Motioneyeos: Use SSL?

Created on 24 Dec 2016  路  26Comments  路  Source: ccrisan/motioneyeos

Hey, i wonder how to add a SSL certificate so i can use the webpanel with HTTPS only.

Most helpful comment

If anyone is still interested in this, hacking the code to enable SSL isn't that hard. You basically wrap the Application object in an HTTPServer object configured to use SSL. On Raspbian I generated a cert and a key using openssl. From there I edited settings.py and server.py from the motioneye package as follows (/usr/local/lib/python2.7/dist-packages/motioneye/server.py and settings.py on my server, for example) as well as /etc/motioneye/motioneye.conf:

If you need to generate a cert/key:
openssl req -new -x509 -days 3650 -nodes -out motioneye.pem -keyout motioneye.key

I put these files into /etc/motioneye/ssl

Edit /etc/motioneye/motioneye.conf to add the following lines:

sslcert /etc/motioneye/ssl/motioneye.pem
sslkey /etc/motioneye/ssl/motioneye.key

Edit settings.py to add default settings for SSLCERT and SSLKEY - add the following lines to the bottom. The paths here will be default values going forward:

SSLCERT = "/etc/motioneye/ssl.pem"
SSLKEY = "/etc/motioneye/ssl.key"

Edit server.py as follows:

  1. Add from tornado import httpserver at the end of the imports section.
  2. Around line 400, replace the line application.listen(settings.PORT, settings.LISTEN) with the following code:
    if os.path.exists(settings.SSLCERT) and os.path.exists(settings.SSLKEY):
       http_server = httpserver.HTTPServer(application,ssl_options={
                                            "certfile": settings.SSLCERT,
                                            "keyfile": settings.SSLKEY,
                                            })
        http_server.listen(settings.PORT)
    else:
        application.listen(settings.PORT, settings.LISTEN)
  1. stop and restart motioneye, you'll have SSL now.

All 26 comments

Transport Layer Security (TLS)-support in general would be great for a security device and service like MotionEye(OS) and the Tornado python library supports it as far as I recall.

The on-device generation of (self-signed) certificates is not required, they can also be imported from other system.

i have configured my apache2 for SSL, with free letsencrypt certificate and activated the proxypass feature:

...
ProxyPreserveHost On
SSLProxyEngine On
ProxyPass / "http://127.0.0.1:8765/"
ProxyPassReverse / "http://127.0.0.1:8765/"
...

motioneye is listening only on 127.0.0.1, so just SSL connections are possible from "outside".

Can this be done with Python Tornado? So no other webserver is started for this task?

sorry i am not familiar with Python Tornado. But this solution is for http application which do not offer SSL capability. You could also start the apache on another computer in the LAN, if you do not want to run it on same machine as Python Tornado runs.

Stunnel is a great way for this to be easily implemented with almost no load on cpu and memory with selected high-performance ciphers/kex/macs like TLS1.2-only and Elliptic Curve crypto like Salsa+Curve25519.

Closing this issue due to interest. If still interested please post your question in the forum page.

@blumenkistl
blumenkistl
commented over 1 year ago
i have configured my apache2 for SSL, with free letsencrypt certificate and activated the proxypass feature:

...
ProxyPreserveHost On
SSLProxyEngine On
ProxyPass / "http://127.0.0.1:8765/"
ProxyPassReverse / "http://127.0.0.1:8765/"
...
motioneye is listening only on 127.0.0.1, so just SSL connections are possible from "outside..

I would love to know how to accomplish this myself if anyone can give me some more insight it would be most appreciated.

Closing this issue due to interest. If still interested please post your question in the forum page.

MotionEyeOS has a forum?

If anyone is still interested in this, hacking the code to enable SSL isn't that hard. You basically wrap the Application object in an HTTPServer object configured to use SSL. On Raspbian I generated a cert and a key using openssl. From there I edited settings.py and server.py from the motioneye package as follows (/usr/local/lib/python2.7/dist-packages/motioneye/server.py and settings.py on my server, for example) as well as /etc/motioneye/motioneye.conf:

If you need to generate a cert/key:
openssl req -new -x509 -days 3650 -nodes -out motioneye.pem -keyout motioneye.key

I put these files into /etc/motioneye/ssl

Edit /etc/motioneye/motioneye.conf to add the following lines:

sslcert /etc/motioneye/ssl/motioneye.pem
sslkey /etc/motioneye/ssl/motioneye.key

Edit settings.py to add default settings for SSLCERT and SSLKEY - add the following lines to the bottom. The paths here will be default values going forward:

SSLCERT = "/etc/motioneye/ssl.pem"
SSLKEY = "/etc/motioneye/ssl.key"

Edit server.py as follows:

  1. Add from tornado import httpserver at the end of the imports section.
  2. Around line 400, replace the line application.listen(settings.PORT, settings.LISTEN) with the following code:
    if os.path.exists(settings.SSLCERT) and os.path.exists(settings.SSLKEY):
       http_server = httpserver.HTTPServer(application,ssl_options={
                                            "certfile": settings.SSLCERT,
                                            "keyfile": settings.SSLKEY,
                                            })
        http_server.listen(settings.PORT)
    else:
        application.listen(settings.PORT, settings.LISTEN)
  1. stop and restart motioneye, you'll have SSL now.

Thanks, zaphodbb!

Should update the instructions with the below commands included.

sudo mkdir /etc/motioneye/ssl
sudo mv motioneye.key /etc/motioneye/ssl
sudo mv motioneye.pem /etc/motioneye/ssl

I scratched my head for a while there when it wouldn't start but it's working now.
Directory didn't exist before hand and when I tried to move it, it dropped it into a file instead of a directory. Had to delete the file to create the directory.

Update:
I just noticed today that it wasn't uploading to google drive. I reverted the changes in server.py and google drive upload started working again. Also noted that the valid streaming URL is still http.

@zaphodbb for adding a cert with a chain, you you just throw them along into the certfile or does the chain needs its own file and config data?

It would be really nice if motioneye/motioneyeos supported SSL out of box (configurable via UI). Making it easy for the user to configure is ideal, especially for a project that is already security focused. Maybe an integration with Let's Encrypt, or at least generating self-signed certs?

totally agree. currely I have managed to put an instance of the caddy Webserver and a config onto the data partition, as well as throw in a startscript, so caddy can so the whole reverse-proxying

@zaphodbb @My1 @cinnamonsweets
Is there any way to get this SSL hack to work on the latest builds? I tried it on stable and nightly. I can get the server to work over HTTPS but the server restarts about every 5 minutes. The boot log says motioneye failed to start. I'm not exactly sure which component is detecting a failure and rebooting the OS, setting log_level to debug doesn't show much useful info.

you mean dev20190327? no Idea I personally prefer to run stable for things like this (I use 20190119).

also what precisely do you mean with the "server" restarting every 5 minutes?
the whole machine, the caddy or just the HTTP server of motioneyeos?

when setting up caddy you need to make sure to throw in the $CADDYPATH env variable to an rw location as the homepath is in a readonly location.

@My1
I'll document all of the steps in case anyone else was confused when trying it. Note that you shouldn't actually try this because the raspberry will reboot every 5 minutes.

I tried the fix of @zaphodbb on these images:
motioneyeos-raspberrypi3-20190119.img
motioneyeos-raspberrypi3-dev20190327.img

I generated SSL certificates using OpenSSL in git for windows which includes it:
openssl req -new -x509 -days 3650 -nodes -out ssl.pem -keyout ssl.key

In my SSH terminal I ran
mount -o remount,rw /
to mount the root partition.

In WinSCP I connected over SFTP and created the directory
/etc/motioneye/

Put ssl.pem and ssl.key in the directory.

Open /data/etc/motioneye.conf and add

sslcert /etc/motioneye/ssl.pem
sslkey /etc/motioneye/ssl.key

I grabbed these versions of settings.py and server.py from the motioneye repository
https://github.com/ccrisan/motioneye/blob/af26421b71d0ac145c7d66da1f208174da9bb6ee/motioneye/settings.py
https://github.com/ccrisan/motioneye/blob/af26421b71d0ac145c7d66da1f208174da9bb6ee/motioneye/server.py

edit settings.py and add this to the bottom
SSLCERT = "/etc/motioneye/ssl.pem"
SSLKEY = "/etc/motioneye/ssl.key"

edit server.py and put this in the imports section
from tornado import httpserver

replace
application.listen(settings.PORT, settings.LISTEN) with this

    if os.path.exists(settings.SSLCERT) and os.path.exists(settings.SSLKEY):
       http_server = httpserver.HTTPServer(application,ssl_options={
                                            "certfile": settings.SSLCERT,
                                            "keyfile": settings.SSLKEY,
                                            })
       http_server.listen(settings.PORT)
    else:
        application.listen(settings.PORT, settings.LISTEN)

There was an indentation error in @zaphodbb's post in case anyone wondered why it wouldn't compile.

if you have python (2.7?) you can run

python -m py_compile settings.py
python -m py_compile server.py

to make sure the files compile correctly. If they compile you can dump the .pyc files directly in

/usr/lib/python2.7/site-packages/motioneye

If not put the .py files in that folder and make sure they have new timestamps. They will be compiled on reboot. Once you reboot check to see that the timestamps of settings.pyc and server.pyc have changed.

Visit your motioneyeos interface with https://ip:port

This actually does work perfectly but the logs will spit out SSL warnings the OS will detect a failure and reboot every 5 minutes. I'm not an experienced programmer so I don't know what's wrong with it, probably something with the code that monitors logging.

After the device reboots I see this in boot.log

---- booting motionEyeOS dev20190327 ----
 * Detecting disk device: /dev/mmcblk0
 * Mounting filesystems: done
 * Loading kernel modules: done
 * Setting hostname: done
 * Starting syslogd: done
 * Configuring CPU frequency: done
 * Starting throttle watcher: done
 * Starting eudev: done
 * Starting watchdog: done
 * Starting wpa_supplicant: done (brcmfmac)
 * Configuring wireless network: dhcp
 * Setting current date using ntp: Thu Mar 28 20:21:31 UTC 2019
 * Starting ntpd: done
 * Starting crond: done
 * Starting sshd: done
 * Starting proftpd: done
 * Starting motioneye: failed
---- shutting down motionEyeOS dev20190327 ----
 * Stopping motioneye: done
 * Stopping proftpd: done
 * Stopping sshd: done
 * Stopping crond: done
 * Stopping ntpd: done
 * Stopping network: done
 * Stopping wpa_supplicant: done
 * Stopping eudev: done
 * Stopping throttle watcher: done
 * Stopping syslogd: done
---- booting motionEyeOS dev20190327 ----
 * Detecting disk device: /dev/mmcblk0
 * Mounting filesystems: done
 * Loading kernel modules: done
 * Setting hostname: done
 * Starting syslogd: done
 * Configuring CPU frequency: done
 * Starting throttle watcher: done
 * Starting eudev: done
 * Starting watchdog: done
 * Starting wpa_supplicant: done (brcmfmac)
 * Configuring wireless network: dhcp
 * Setting current date using ntp: Thu Mar 28 20:26:36 UTC 2019
 * Starting ntpd: done
 * Starting crond: done
 * Starting sshd: done
 * Starting proftpd: done
 * Starting motioneye: failed
---- shutting down motionEyeOS dev20190327 ----
 * Stopping motioneye: done
 * Stopping proftpd: done
 * Stopping sshd: done
 * Stopping crond: done
 * Stopping ntpd: done
 * Stopping network: done
 * Stopping wpa_supplicant: done
 * Stopping eudev: done
 * Stopping throttle watcher: done
 * Stopping syslogd: done

This appears in motioneye.log

2019-03-28 20:10:55: [motioneye]  WARNING: SSL Error on 14 ('127.0.0.1', 49576): [SSL: HTTP_REQUEST] http request (_ssl.c:726)
2019-03-28 20:10:56: [motioneye]     INFO: interrupt signal received, shutting down...

If anyone could help me find out what's causing the device to reboot that would be great.

I see. I actually didnt care enough to screw the system itself, I basically just (I run it on a pine64 so steps might differ a bit)

1) setup motioneyeos normally
2) compile the Caddy Web Server for Linux arm64 (you can do this on a normal computer no problem as go supports cross-os and cross-architecture compiling
3) transfer caddy onto it via FTP
4) connect via SSH and remount root as rw
5) write a little Caddyfile (configuration file) for caddy to use (basically not much more than reverse proxy and in our case a basic auth as well)
6) write a startscriptand put it into that sets the env variable for the caddy path to a writable location and starts caddy
7) make it so the start script gets started on boot

caddy will now automatically gets its certs from Lets Encrypt as well as serve the site over HTTPS

@My1
Can you post some config files for me to look at? That would be great. 馃憤

I ran less /proc/cpuinfo and the pi3 apparently runs in armv7 mode so I guess I can just download the Caddy binaries.

well do remind that the binaries you download from caddy are for personal use only and we are kinda too small to pay for all of this (my dev PC the surveillance thing and anything else I may set up) so I just compile it as it isnt really hard.

regarding the caddy config it's actually VERY simple, with not much inside, it was harder to search how to get the autostart running (remind me on monday to look it up)

This is probably similar to my caddyfile (recreated from memory, so probably not perfect and I wont gurantee it as working.

https://domain.tld {
  proxy / localhost:70 # note that I changed the port of the web-UI
  basicauth / username password # leave this if you don't need an extra auth
  tls {
    protocols tls1.2 tls1.3
    key_type p384 # I like ECC certs, also it enables IE11 to use AES-GCM with ECDHE
  }
}

@My1
Trying to set up Caddy made my brain hurt. I was trying to figure out why the reverse proxy worked over HTTP but not HTTPS. If I'm reading this GitHub issue right self-signed certificates don't really work anymore.
https://github.com/mholt/caddy/issues/2414

edit: found a workaround, I don't know if this is the right way to do it but it works I guess.

If you're using a Raspi3 get the Linux armv7 binary from here. Turn telemetry off you won't need it.
https://caddyserver.com/download
(If you want to secure your login page with basic auth later, add the http.ratelimit plugin. Also get http.ipfilter if you want to use my config at the bottom.)

the root directory is pretty small so I decided to chuck Caddy in a folder in /data. Set caddy's permissions to 0755 to make it executable.

Generate a cert using openssl and put them on your device somewhere. motioneyeos doesn't have openssl so I generated it in Windows with git for Windows.
openssl req -new -x509 -days 3650 -nodes -out motioneye.pem -keyout motioneye.key
optional: generate 4096 bit RSA key instead of 2048
openssl req -new -newkey rsa:4096 -x509 -days 3650 -nodes -out motioneye.pem -keyout motioneye.key

Use any info for the cert except for Common Name/FQDN. You want to use either * or your camera's static IP.

Caddyfile:

*:443 {
    proxy / localhost:<MOTIONEYE_PORT> { transparent }
    tls <PATH_TO_.PEM> <PATH_TO_.KEY>
   }

when you launch Caddy it must be ran with this flag
-default-sni *

edit: here's how you keep caddy running
create /data/etc/userinit.sh
(I have this on permission 700)
configure CADDYPATH and paste

#tvservice -o #optional, uncomment to disable HDMI and save power
export CADDYPATH=/data/<CADDY DIRECTORY> #Caddy will use root by default which is read-only, this changes the working directory that it stores its SSL files etc.
bash & /data/etc/init.sh

create /data/etc/init.sh
paste
nohup <caddy path> -conf <Caddyfile path> -log <log path> default-sni "*" &>> <log path>
those logs should go in a folder in /data probably
then set init.sh to permission 755

After you confirmed everything works go to
/data/etc/motioneye.conf

and replace
listen 0.0.0.0
with
listen 127.0.0.1

To add security, you can use basicauth to password protect your installation. This will help prevent your installation from showing up on search engines (like Shodan) if someone searches for the web interface. Add this to your Caddyfile configuration
basicauth / <USERNAME> <PASSWORD>

if you have the http.ratelimit plugin you can rate limit brute force attempts on the motionEyeOS panel.

On your Caddyfile add

 ratelimit post,get 10 10 minute {
        /login
    /config
    }

This limits login-related functions to approximately 10 per minute. Adjust if it causes issues for normal activity.

I've found that Caddy also works well with DDNS services like DuckDNS. It will be able to generate valid SSL certificates on DDNS hostnames using Let's Encrypt.

Another edit: Here's basically my caddyfile if anyone wants something to give themselves a headache. Please tell me if I made any errors editing it for consumption. I am using the http.ipfilter plugin to ban all IPs that aren't from my country. You can add https network cameras to a motioneyeos server but Caddy MUST be proxying with the transparent directive. Also if you are using self-signed certs on your network cameras you need to add validate_certs false to /data/etc/motioneyeos.conf on the motioneyeos server accessing the network cameras. The database for geoip filtering can be obtained from
https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz
More documentation here: https://github.com/pyed/ipfilter/blob/master/README.md

https://*:443 {
#gzip #Uncomment to save bandwidth viewing web panel at cost of CPU time and lower frame rates
 ratelimit post,get 10 10 minute {
    /login  #rate limit
    /config #rate limit
    }
    header / {
    Strict-Transport-Security "max-age=31536000;" #HSTS
    -server #strips server tags out of header 
    X-XSS-Protection "1; mode=block"
    X-Content-Type-Options "nosniff"
    }
    proxy / localhost:<<MOTIONEYE PORT>> { transparent } #you need this for the stream server
    errors /data/log/<<CADDY ERROR LOG FILENAME>> #records auth errors
    basicauth / <<BASICAUTH USERNAME>> <<BASICAUTH PASSWORD>>
    log / /data/log/<<CADDY LOG FILENAME>>  {
    except /picture/2/current/ #prevent log spam when viewing web panel, increment if you have more cameras
    except /picture/1/current/ #prevent log spam when viewing web panel, increment if you have more cameras
    }
    tls /data/<<PATH TO .PEM>> /data/<<PATH TO .KEY>> {
    #ciphers ECDHE-RSA-AES256-GCM-SHA384 #completely optional and overkill
    #key_type rsa4096 #this doesn't do anything if you're loading certs you already made 
   }

    #UNCOMMENT FOR IP FILTERING
    ###ipfilter / {
    ###rule allow #whitelist only mode
    ###database /data/<<PATH TO GeoLite2-Country.mmdb>>
    ###country US #Only allow US IPs. CHANGE IF YOU ARE NOT IN US. 
    ###ip 192.168.1.0/24 #Whitelists your internal network. CHANGE IF YOUR NETWORK USES DIFFERENT IP RANGES
    ###blockpage /dev/null #don't display an error log for bots to digest
    ###   }

   }

#UNCOMMENT IF YOU NEED TO MAKE STREAM SERVER AVAILABLE. Port 8082 will point to stream server at port 8081.
###https://*:8082 {
###    #gzip #Uncomment to save bandwidth on web panel at cost of CPU time and lower frame rates
###    proxy / localhost:8081 { transparent } #you need this for the stream server
###    tls /data/<<PATH TO .PEM>> /data/<<PATH TO .KEY>> {
###    #ciphers ECDHE-RSA-AES256-GCM-SHA384 #completely optional and overkill
###    #key_type rsa4096 #this doesn't do anything if you're loading certs if you already made 
###     }
###    ipfilter / {
###    rule allow
###    ip 192.168.1.<<XXX>> #STATIC IP OF RECORD SERVER IF YOU USE ONE. This will restrict so only the record server can see the stream. Otherwise, use something like 192.168.1.0/24 to whilelist whole internal network.
###    blockpage /dev/null #don't display an error log for bots to digest
###    }
###   }




####DELETE THIS ENTIRE BLOCK IF YOU ARE NOT USING A DDNS SERVICE
https://<<DDNS/DUCKDNS DOMAIN>>:443 {
#gzip #Uncomment to save bandwidth viewing web panel at cost of CPU time and lower frame rates
 ratelimit post,get 10 10 minute {
    /login  #rate limit
    /config #rate limit
    }
    header / {
    Strict-Transport-Security "max-age=31536000;" #HSTS
    -server #strips server tags out of header 
    X-XSS-Protection "1; mode=block"
    X-Content-Type-Options "nosniff"
    }
    proxy / localhost:<<MOTIONEYE PORT>> { transparent }
    errors /data/log/<<CADDY ERROR LOG FILENAME>> #records auth errors
    basicauth / <<BASICAUTH USERNAME>> <<BASICAUTH PASSWORD>>
    log / /data/log/<<CADDY LOG FILENAME>>  {
    except /picture/2/current/ #prevent log spam when viewing web panel, increment if you have more cameras
    except /picture/1/current/ #prevent log spam when viewing web panel, increment if you have more cameras
    }
    tls {
     #ciphers ECDHE-RSA-AES256-GCM-SHA384 #completely optional and overkill
     #key_type rsa4096 #completely optional and overkill
   }

       #UNCOMMENT FOR IP FILTERING
    ###ipfilter / {
    ###rule allow #whitelist only mode
    ###database /data/<<PATH TO GeoLite2-Country.mmdb>>
    ###country US #Only allow US IPs. CHANGE IF YOU ARE NOT IN US. 
    ###ip 192.168.1.0/24 #Whitelists your internal network. CHANGE IF YOUR NETWORK USES DIFFERENT IP RANGES
    ###blockpage /dev/null #don't display an error log for bots to digest
    ###   }

    }

To reset your Caddy server do killall caddy and then restart it with /data/etc/userinit.sh
If you're using Let's Encrypt with the default settings you need ports 443 and 80 open to get certs. You might have to enable port 80 in your Caddy config.

okay I never used the automatic self signed function mainly because it's a mess, I mean every restart generates a new 7 day temporary cert (in memory, aka RAM) which you have to trust again on each restart and if you leave it running it might be that it wont replace it by itself, which means more messages about it baing expired.

I just let it generate certs via Let's encrypt for setups which are web accessible and have a local CA managed with XCA for localhost stuff.

Edited my post to include the 'proper' way to make self-signed certs work in Caddy with IP addresses. I still can't believe the tomfoolery in that issue chain.

Would love to know how you autostart Caddy.

I have no access to the machine right now, if I remember (or you remember me) on monday (CEST) I can check. it was a bit hidden so I don't know atm.

got everything to work, hooray

@My1
Sorry for asking so many questions. Is there an easy way to rate limit login attempts on motionEyeOS? I looked up the documentations for Caddy. Using basicauth and enabling error logging looks like a good way to protect the login page when it's internet-facing. The error logs are identical to NGINX's which would make it easily compatible with Fail2Ban.

However motionEyeOS is missing so many dependencies that Fail2Ban won't run properly.

I guess I should really install Diet Pi or Rasbian for this.

edit: There's a rate-limiting plugin for Caddy!
https://github.com/xuqingfeng/caddy-rate-limit

[root@CCTV data]# sh /data/etc/init.sh
killall: caddy: no process killed
[root@CCTV data]# cat /data/caddy/start.log
Tue Sep 3 23:34:17 CEST 2019
2019/09/03 23:34:17 Caddyfile loaded multiple times; first by short, then by flag

i get this error

Was this page helpful?
0 / 5 - 0 ratings

Related issues

andrei-volkau picture andrei-volkau  路  3Comments

tehXor picture tehXor  路  3Comments

manianac picture manianac  路  5Comments

JasonMorris1 picture JasonMorris1  路  5Comments

darrenc90 picture darrenc90  路  3Comments