document proxy setup in guide/secure.md (for #26)

The guide is not as quick to follow and amateur-friendly as I'd like. A
few things that might improve matters:

   * complete #27 (built-in https+letsencrypt), so that when not sharing
     the port, users don't need to use nginx or certbot.
   * more ubiquitous IPv6 (out of my control but should happen over
     time) to reduce need to share the port
   * embed a dynamic DNS client
   * support UPnP Internet Gateway Device Control Protocol (if common
     routers have this enabled? probably not for security reasons.)

It's progress, though. Enough that I think I'll merge the auth branch
into master shortly.
This commit is contained in:
Scott Lamb 2018-12-27 16:00:15 -06:00
parent 3c1163dfe2
commit 24674f5b50
7 changed files with 288 additions and 11 deletions

View File

@ -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)

View File

@ -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

View File

@ -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).

256
guide/secure.md Normal file
View File

@ -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.

View File

@ -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

View File

@ -194,5 +194,5 @@ pub fn top_dialog(db: &Arc<db::Database>, siv: &mut Cursive) {
.map(|(&id, user)| (format!("{}: {}", id, user.username), Some(id))))
.full_width())
.dismiss_button("Done")
.title("Edit cameras"));
.title("Edit users"));
}

View File

@ -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))))
}