diff --git a/README.md b/README.md index f497acfb..63cbca0c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ less than 10% of the machine's total CPU. So far, the web interface is basic: a filterable list of video segments, with support for trimming them to arbitrary time ranges. No scrub bar yet. There's also no support for motion detection, no https/SSL/TLS support (you'll -need a proxy server), and no config UI. +need a proxy server, as described [here](guide/secure.md)), and only a +console-based (rather than web-based) configuration UI. ![screenshot](screenshot.png) diff --git a/guide/install-manual.md b/guide/install-manual.md index f50b0c24..72a45acb 100644 --- a/guide/install-manual.md +++ b/guide/install-manual.md @@ -84,7 +84,8 @@ Moonfire NVR can be run as a systemd service. Create [Service] ExecStart=/usr/local/bin/moonfire-nvr run \ --db-dir=/var/lib/moonfire-nvr/db \ - --http-addr=0.0.0.0:8080 + --http-addr=0.0.0.0:8080 \ + --require-auth=false Environment=TZ=:/etc/localtime Environment=MOONFIRE_FORMAT=google-systemd Environment=MOONFIRE_LOG=info diff --git a/guide/install.md b/guide/install.md index 49685416..0be18bb1 100644 --- a/guide/install.md +++ b/guide/install.md @@ -56,7 +56,7 @@ In the fstab you'd add a line similar to this: /dev/disk/by-uuid/23d550bc-0e38-4825-acac-1cac8a7e091f /media/nvr ext4 defaults,noatime,nofail 0 2 You'll have to lookup the correct uuid for your disk. One way to do that is -to issue the following commands: +via the following command: $ ls -l /dev/disk/by-uuid @@ -104,6 +104,7 @@ In the user interface, be flushed when the first instant of a completed recording second is a minute old. Lower values cause less video to be lost on power loss; higher values reduce wear on the SSD holding the SQLite database. + 3. Assign disk space to your cameras back in "Directories and retention". Leave a little slack (at least 100 MB per camera) between the total limit and the filesystem capacity, even if you store nothing else on the disk. @@ -119,17 +120,30 @@ In the user interface, downloading it), it stays around until the file is closed. Moonfire NVR currently doesn't account for this. + 4. Add a user for yourself (and optionally others) under "Users". You'll need + this to access the web UI once you enable authentication. + ## Starting it up -When finished, start the daemon and enable it for following boots: +Note that at this stage, Moonfire NVR's web interface is **insecure**: it +doesn't use `https` and doesn't require you to authenticate +to it. You might be comfortable starting it in this configuration to try it +out, particularly if the machine it's running on is behind a home router's +firewall. You might not; in that case read through [secure the +system](secure.md) first. + +The following commands will start Moonfire NVR and enable it for following +boots, respectively: $ sudo systemctl start moonfire-nvr $ sudo systemctl enable moonfire-nvr -You can access the HTTP interface on http://localhost:8080/ by default. - -Note that the HTTP port currently has no authentication, encryption, or -logging; it should not be directly exposed to the Internet. +The HTTP interface is accessible on port 8080; if your web browser is running +on the same machine, you can access it at +[http://localhost:8080/](http://localhost:8080/). If the system isn't working, see the [Troubleshooting guide](troubleshooting.md). + +Once the web interface seems to be working, read through [securing Moonfire +NVR](secure.md). diff --git a/guide/secure.md b/guide/secure.md new file mode 100644 index 00000000..6805095a --- /dev/null +++ b/guide/secure.md @@ -0,0 +1,256 @@ +# Securing Moonfire NVR and exposing it to the Internet + +## The problem + +After you've completed the [Downloading, installing, and configuring +NVR guide](install.md), you should have a running system you can use from +within your home, but one that is insecure in a couple ways: + + 1. It doesn't use `https` to encrypt connections & authenticate itself to + you. + 2. It doesn't require you to sign in (with your chosen username and + password) to authenticate yourself to it. + +You'll want to change these points if you expose Moonfire NVR's web interface +to the Internet. Security-minded folks would say you shouldn't even allow +unauthenticated sessions within your local network. + +Besides security, the nature of home Internet setups presents challenges in +exposing Moonfire NVR to the Internet: + + 1. you likely have a single IPv4 address that all your devices share via NAT. + (Your ISP may also provide a set of IPv6 addresses; even if they do, you + likely don't have IPv6 available everywhere you want to connect from.) + You'll need to set up "port forwarding" on your home router, and there + are many routers with different interfaces for doing so. + 2. that IPv4 address is likely dynamic, so you'll need to configure "dynamic + DNS" to get a consistent URL to access Moonfire NVR. Most people do this + through their router's interface as well. + 3. you may want to share your single IP address's `http` and `https` ports + with other web interfaces, such as a network-attached storage device. + This requires setting up a proxy and configuring it with each + destination. + 4. unlike some commercial providers, Moonfire NVR doesn't have any central + organization to provide a central high-bandwidth, Internet-accessible + proxying service. + +This guide is therefore more abstract than the previous installation steps, +and may even make assumptions that aren't true for your setup. Improvements +are welcome, but it's not possible to make a single terse, concrete guide that +will work for everyone. If you're not a networking expert, you may need to +consult your home router's manual and other external guides or forums. + +## VPN or port forwarding? + +This guide describes how to set up Moonfire NVR with port forwarding. + +Any security camera forums such as [ipcamtalk](https://ipcamtalk.com/) will +recommend that you use a VPN to connect to your NVR rather than port +forwarding. The backstory is that most NVRs are untrustworthy. They have +low-budget, closed-source software written by companies which at best aren't +security-conscious and at worst allow the Chinese government to use [deliberate +backdoors](https://www.reddit.com/r/bestof/comments/8aqyto/user_explains_how_one_chinese_security_camera/). + +A VPN's advantage is that it doesn't allow any incoming traffic to reach the +NVR until after authentication, so it's far more secure when the NVR can't be +trusted to perform proper authentication itself. + +Port forwarding's advantage is that, once installed on the server, it's far +more convenient to use. There's no VPN client necessary, just a web browser. + +I believe Moonfire NVR authenticates properly. It's also open-source, so it's +practical to verify this yourself given sufficient time and expertise. + +If you'd prefer to use a VPN, the [ipcamtalk Cliff +Notes](https://ipcamtalk.com/wiki/ip-cam-talk-cliff-notes/) suggest reading +[Network Security +Primer](https://ipcamtalk.com/threads/network-security-primer.1123/) and/or +[VPN Primer for +Noobs](https://ipcamtalk.com/threads/vpn-primer-for-noobs.14601/). + +## Overview + + 1. Install a webserver + 2. Configure a static internal IP + 3. Set up port forwarding + 4. Configure a public DNS name + 5. Install a TLS certificate + 6. Reconfigure Moonfire NVR + 7. Configure the webserver + 8. Verify it works + +## 1. Install a webserver. + +Moonfire NVR's builtin webserver doesn't yet support `https` (see [issue +\#27](https://github.com/scottlamb/moonfire-nvr/issues/27), so you'll need to +proxy through a webserver that does. If Moonfire NVR will be sharing an +`https` port with anything else, you'll need to set up the webserver to proxy +to all of these interfaces as well. + +I use [nginx](https://https://nginx.com/) as the proxy server. Some folks may +prefer [Apache httpd](https://httpd.apache.org/) or some other webserver. Any +of these will work. I include snippets of a `nginx` config below, so stick +with that if you're not comfortable adapting it to some other server. + +I run the proxying webserver on the same machine as Moonfire NVR itself. You +might want to do something else, but this is the simplest setup that means you +only need to configure one machine with a static internal IP address. + +digitalocean has a nice [How to install Nginx on Ubuntu 18.04](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-18-04) guide. + +## 2. Configure a static internal IP + +When you configure port forwarding on your router, you'll most likely have to +specify the destination as an internal IP address. You could look up the +current IP address of the webserver machine, but it might change, and your +setup will break if it does. + +The easiest way to ensure your setup keeps working is to use the "static DHCP +lease" option on your home router to give your webserver machine the same +address every time it asks for a new lease. + +Alternatively, you can configure your webserver to use a static IP address +instead of asking for a DHCP lease. Ensure the address you choose is outside +the range assigned by the DHCP server, so that there are no conflicts. + +Reboot the webserver machine now and ensure it uses the IP address you choose on +startup, so you don't have a confusing experience after your next power +failure. + +## 3. Set up port forwarding + +In your router's setup, go to the "Port Forwarding" section and tell it to +forward TCP requests on the `http` port (80) and the `https` port (443) to +your webserver. The `https` port is necessary for secure access, and the +`http` port is necessary for the Let's Encrypt `http` challenge during the +setup process. + +Now if you go to your external IP address in a web browser, you should reach +your webserver. + +## 4. Configure a public DNS name + +Also in your router's setup, look for "Dynamic DNS" or "DDNS". Configure it to +update some DNS name with your home's external IP address. You should then be +able to go to this address in a web browser and reach your webserver again. + +It's possible to instead set up a dynamic DNS client on the Moonfire NVR +machine instead. See [this Ubuntu +guide](https://help.ubuntu.com/community/DynamicDNS). One disadvantage is that +it may be slower to recognize IP address changes, so there may be a longer +period in which the address is incorrect. + +## 5. Install a TLS certificate + +I recommend using the [Let's Encrypt](https://letsencrypt.org/) Certificate +Authority to obtain a TLS certificate that will be automatically trusted by +your browser. See [How to secure Nginx with Let's Encrypt on Ubuntu +18.04](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04). + +## 6. Reconfigure Moonfire NVR + +In your `/etc/systemd/system/moonfire-nvr.service` file, look for these lines: + +``` +ExecStart=/usr/local/bin/moonfire-nvr run \ + ... + --http-addr=0.0.0.0:8080 \ + --require-auth=false +``` + +Change `--require-auth=false` to `--require-auth=true --trust-forward-hdrs` +which has two effects: + + * `--require-auth=true` means that web users must authenticate. + * `--trust-forward-hdrs` means that Moonfire NVR will look for `X-Real-IP` + and `X-Forwarded-Proto` headers as added by the webserver configuration + in the next section. + +If the webserver is running on the same machine as Moonfire NVR, you might +also change `0.0.0.0:8080` to `127.0.0.1:8080`, which prevents other machines +on the network from impersonating the proxy, effectively allowing them to lie +about the client's IP and protocol. + +Run these commands to make the configuration take effect: + +``` +$ sudo systemctl daemon-reload +$ sudo systemctl restart moonfire-nvr +``` + +## 7. Configure the webserver + +Since step 5, you should have a `https`-capable webserver set up on your +desired DNS name. Now finalize its configuration: + + * redirect all `http` traffic to `https` + * proxy `https` traffic to Moonfire NVR + * add a `X-Real-IP` header with the original IP address + * add a `X-Forwarded-Proto` header with the original protocol (which should + be `https` if you've configured everything correctly). + +The author's system does this via the following +`/etc/nginx/sites-available/nvr.home.slamb.org` file: + +``` +upstream moonfire { + server 127.0.0.1:8080; +} + +server { + root /var/www/html; + index index.html index.htm index.nginx-debian.html; + + server_name nvr.home.slamb.org; + + location / { + proxy_pass http://moonfire; + # try_files $uri $uri/ =404; + } + + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $http_host; + proxy_redirect http:// $scheme://; + + listen [::]:443 ssl ipv6only=on; # managed by Certbot + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/nvr.home.slamb.org/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/nvr.home.slamb.org/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + +} + +server { + listen 80; + listen [::]:80; + + return 301 https://nvr.home.slamb.org$request_uri; + + server_name nvr.home.slamb.org nvr; +} +``` + +Check your configuration for syntax errors and reload it: + +``` +$ sudo nginx -t +$ sudo systemctl reload nginx +``` + +## Verify it works + +Go to `http://your.domain.here/api/request` and verify the following: + + * the browser redirects from `http` to `https` + * the address shown here matches your web browser's public IP address. + (Compare to [https://whatsmyip.com/].) + * the page says `secure: true` indicating you are using `https`. + +Then go to `https://your.domain.here/` and you should see the web interface, +including a login form. If you login, you should see your username and +"logout" in the upper-right corner of the web interface. + +If it doesn't work as expected, re-read the guide, or open an issue on github +for help. diff --git a/scripts/install.sh b/scripts/install.sh index 995e71df..40512e36 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -106,7 +106,8 @@ After=network-online.target ExecStart=${SERVICE_BIN} run \\ --db-dir=${DB_DIR} \\ --ui-dir=${LIB_DIR}/ui \\ - --http-addr=0.0.0.0:${NVR_PORT} + --http-addr=0.0.0.0:${NVR_PORT} \ + --require=auth=false Environment=TZ=:/etc/localtime Environment=MOONFIRE_FORMAT=google-systemd Environment=MOONFIRE_LOG=info diff --git a/src/cmds/config/users.rs b/src/cmds/config/users.rs index 01a73581..7254c991 100644 --- a/src/cmds/config/users.rs +++ b/src/cmds/config/users.rs @@ -194,5 +194,5 @@ pub fn top_dialog(db: &Arc, siv: &mut Cursive) { .map(|(&id, user)| (format!("{}: {}", id, user.username), Some(id)))) .full_width()) .dismiss_button("Done") - .title("Edit cameras")); + .title("Edit users")); } diff --git a/src/web.rs b/src/web.rs index 6e7668fc..95c4563a 100644 --- a/src/web.rs +++ b/src/web.rs @@ -486,8 +486,11 @@ impl ServiceInner { fn request(&self, req: &Request<::hyper::Body>) -> ResponseResult { let authreq = self.authreq(req); + let host = req.headers().get(header::HOST).map(|h| String::from_utf8_lossy(h.as_bytes())); + let agent = authreq.user_agent.as_ref().map(|u| String::from_utf8_lossy(&u[..])); Ok(plain_response(StatusCode::OK, format!( "when: {}\n\ + host: {:?}\n\ addr: {:?}\n\ user_agent: {:?}\n\ secure: {:?}", @@ -495,8 +498,9 @@ impl ServiceInner { .strftime("%FT%T") .map(|f| f.to_string()) .unwrap_or_else(|e| e.to_string()), + host.as_ref().map(|h| &*h), &authreq.addr, - authreq.user_agent.map(|u| String::from_utf8_lossy(&u[..]).into_owned()), + agent.as_ref().map(|a| &*a), self.is_secure(req)))) }