Closed ReK42 closed 1 year ago
Couple questions:
HTTPS is a basic feature of modern web communication and should be turned on by default. Even with an untrusted certificate, such as a self-signed cert, it still provides numerous benefits. All major browsers require it for HTTP/2 connections and are moving towards requiring it for everything (https://www.eff.org/deeplinks/2021/09/https-actually-everywhere)
With a self-signed certificate, the user is prompted that the certificate is not trusted and they have to click accept to proceed. The UI is different depending on the client browser, but the behaviour is consistent across them. This is normal behaviour seen on many appliances which offer a web UI such as server infrastructure, IOT devices, etc.
My understanding is that HTTPS-only mode will be optional in Browsers for the foreseeable future. If Browsers only accepted HTTPS there would I imagine be a lot of application breakage on LAN's.
The challenge in doing HTTPS for moOde is the messy process the user has to go through to just access the player. it's an even messier process to get the Browser to trust the self-signed cert so the warnings and dialogs go away.
In spite of this I'm in favor of your approach.
Some things to consider are:
hostname
, hostname.loca
l or IP_ADDRESS
172.24.1.1
or hostname.local
http://localhost
hostname.local
and it can potentially change on each refresh.hostname.local
, however the others can be added to the certificates as SANs. There is already a SAN for hostname.local
so you can just add DNS.2 = <hostname>
. Note that I'd recommend suggesting users don't load via IP address directly as the default is DHCP, meaning it can change at any time. It is possible to add IP addresses as SANs on a certificate but I don't recommend it for that reason.hostname
. In linux, the local hostname is entered into /etc/hosts
and will always resolve immediately to localhost without a DNS request. This means the name matches the already-existing certificate and therefore won't throw any errors. Modifying the script to also add the self-signed certificate to the ca trust list will cause the display to load headlessly without prompting the user, resulting in no change in application behaviour.I suppose what I meant to say in (1) was that users will be changing the hostname but you have already covered that scenario (rerun the script when user changes hostname) in your OP.
Wrt IP address, there are several scenarios.
172.24.1.1
and so could be added to the cert.http://IP_ADDRESS
is needed.http://IP_ADDRESS
Does this mean no Browser warnings or dialogs that user has to deal with?
Modifying the script to also add the self-signed certificate to the ca trust list will cause the display to load headlessly without prompting the user, resulting in no change in application behaviour.
Yes, as long as the issuing certificate (which is the certificate itself, hence self-signed) is trusted by the base OS and the name/address used to access the page matches the CN or one of the SANs, no errors will be presented to the user and the page will just load.
It may be worth extending the script so that if a static IP is configured or AP mode is enabled, it will be added to the certificate. I generally recommend avoiding adding IP addresses to certificates even in cases where it's static since some organizations view internal addressing as sensitive information and having it in the certificate can leak that, even when behind a reverse proxy/load balancer, but that really shouldn't matter for moode.
To handle things like DHCP reservations or regular DNS records the user configures externally to moode you could simply allow the user to upload a certificate/key file manually to overwrite whatever the script would normally generate. This has the added benefit of allowing the user to sign the certificate with an actual CA, either private or public.
It may be worth extending the script so that if a static IP is configured or AP mode is enabled, it will be added to the certificate.
could work. AP mode address never changes so it could be default added to cert.add the self-signed certificate to the ca trust list
only covers the AP mode access and chromium-browser access to local display, correct?I think this feature would need to be implemented as optional at first so that field usage can surface the failure modes.
Wrt script automatically add the self-signed certificate to the ca trust list only covers the AP mode access and chromium-browser access to local display, correct?
The OS CA trust is only used for clients on that OS. So the local display would be the only thing affected.
There is still the mess of adding to root CA list on Mac, Windows, Linux clients. This could prolly be automated with some scripting but I have no expertise in Windows or MacOS scripting and their root CA stores.
I would not do this. Adding certificates to the trust store can be a significant security issue and is not necessary. Browsers will remember it once the user has clicked accept once.
This: `allow the user to upload a certificate/key file manually' is prolly too technical and complex for most users.
Probably, but it's very little effort to add as a feature for those who do want to do it. You simply need to overwrite /etc/pki/tls/certs/<fqdn>.crt
and /etc/pki/tls/private/<fqdn>.key
with the files they upload.
Is it this easy (one click) for user to add a self-signed cert to the store?
Browsers will remember it once the user has clicked accept once.
Just to get a level-set what other Linux audio players are running over https on a local network?
That does not add the certificate to the store, it adds an exception for that specific certificate at that specific location. But yes, depending on the UI it may be two clicks: this user help page from a university includes screenshots of the process for all major browsers.
Ok, thanks.
One possibility:
return 301 https://\$host\$request_uri;
in the port 80 section.Script enhancements:
Let me know what you think.
I'd recommend restructuring the config files. I wrote the script to run as part of the backup restore operation for the current environment but it would be better to use proper configs if we can modify the base image.
Nginx can use include files inside config statements. The HTTP redirect and full HTTP serve config could be in two separate files, then the setting can just change the one line in the main config for which should be included.
Similarly, the openssl config file could be stored on filesystem with placeholder values, then the script can simply copy that file to /tmp, replace the values and add the IP.1=172.24.1.1 to the end of the file if necessary.
I included the hostname in the openssl and certificate file name out of habit, as that is best practice due to potentially needing multiple certificates, but that isn't true in this case. It would be easier to detect and work with these files if they were statically named.
Makes sense. Prolly something similar to this https://gist.github.com/lon-io/443bbc7b1caa0b7c48bd3cfd0b89532a
I have a stub feature worked up and if you are able to do a Gulp build of moOde from the develop branch you could test it out after I post the commit later this week or just wait till 8.2.3 is released later this month. The feature can be unhidden for testing by updating the Feature Availability Bitmask via the moodeutl command.
Couple things:
curl
is used to send data to another PHP script. Will curl
automatically handle the 301 redirect?$_camillagui_url = 'http://'. $_SERVER['HTTP_HOST'] . ':15000';
where $_SERVER['HTTP_HOST']
is the hostname for example "moode". Camilla uses it's own built-in web server.After some testing, the Browser warnings issue ends up being too messy. Have a look at the Chrome address bar after allowing the exception. It has "Not secure" with https crossed out. The other challenge is that exception process has to be repeated each time Browser is opened afresh.
Btw, the feature framework for HTTPS-Only has been committed. There are some additional files not in the commit that are needed to make it fully functions. Email me if you would like the files for testing.
That should not be the behaviour. Unless you're in private/incognito mode, the exception should be remembered and the only reason to reprompt is if the certificate changes, you access it via a different name, or there's a new error (e.g. it expired). I just tested using Chrome 108.0.5359.95, Firefox 107.0.1 and Edge 108.0.1462.42 and they all behave that way for me (Windows 10 21H2 client).
Here's what it looks like on Firefox:
First great effort that you try to harding the security of moOde! Not sure if self signed certificates are a cure or a disease, especially with home networks ;-)
Some thoughts about it:
If something is eavesdropping on your local lan you will prob have other 'issues' as well. For example login with SSH to gain root access to the Pi will cause more harm (expect that most users never change the default password).
The camilladsp gui can be setup to be bound to http://localhost:15000. With a nginx conf you can redirect for example https://moode/camillagui to http://localhost:15000.
@bitkeeper: Good to know re camillagui. What about curl? It's used in 3 scripts to send commands to other scripts. Can it handle the 301 redirect?
@ReK42: Chrome ver was 108.0.5359.94 on MacOS. Of course there could be bugs in my implementation. Have a look at the files.
gen-cert.sh
# Template
OPENSSL_CFG_FILE=/tmp/nginx-selfsigned.conf
cat >> $OPENSSL_CFG_FILE <<EOF
[ req ]
default_bits = 2048
encrypt_key = no
default_md = sha256
string_mask = nombstr
prompt = no
distinguished_name = req_dn
req_extensions = req_ext
[ req_dn ]
commonName = $HOSTNAME.local
[ req_ext ]
basicConstraints = critical, CA:FALSE
keyUsage = digitalSignature, keyEncipherment, nonRepudiation
extendedKeyUsage = clientAuth, serverAuth
subjectAltName = @req_sans
[ req_sans ]
DNS.1 = $HOSTNAME.local
DNS.2 = $HOSTNAME
IP.1 = 172.24.1.1
EOF
# Cert
SSL_CSR_FILE=/tmp/nginx-selfsigned.csr
SSL_CRT_FILE=/etc/ssl/certs/nginx-selfsigned.crt
SSL_KEY_FILE=/etc/ssl/private/nginx-selfsigned.key
openssl req -new -config $OPENSSL_CFG_FILE -out $SSL_CSR_FILE -keyout $SSL_KEY_FILE
openssl req -x509 -days 3650 -config $OPENSSL_CFG_FILE -in $SSL_CSR_FILE -key $SSL_KEY_FILE -out $SSL_CRT_FILE -extensions req_ext
# TEST
# Chromium-browser trust store
#sudo apt -y install libnss3-tools
#CERT_NICKNAME=NGINX Self-signed Cert
#certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n $CERT_NICKNAME -i $SSL_CRT_FILE
# TEST
# Debian trust store (needed?)
#sudo cp $SSL_CRT_FILE /usr/local/share/ca-certificates/
#sudo update-ca-certificates
nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
client_max_body_size 75M;
##
# SSL Settings
##
#ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
#ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log off;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
#include /etc/nginx/conf.d/*.conf;
#include /etc/nginx/sites-enabled/*;
##
# moOde UI server
##
server {
listen 80 default_server;
#listen [::]:80 default_server;
#return 301 https://$host$request_uri;
location / {
root /var/www;
index index.html index.php;
try_files $uri $uri/ /coverart.php;
}
location /imagesw/ {
root /var/local/www;
}
# php-fpm
location ~ \.php$ {
root /var/www;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $request_filename;
include fastcgi_params;
}
}
server {
listen 443 ssl http2;
#listen [::]:443 ssl http2;
include snippets/self-signed.conf;
include snippets/ssl-params.conf;
location / {
root /var/www;
index index.html index.php;
try_files $uri $uri/ /coverart.php;
}
location /imagesw/ {
root /var/local/www;
}
# php-fpm
location ~ \.php$ {
root /var/www;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $request_filename;
include fastcgi_params;
}
}
}
snippets/self-signed.conf snippets/ssl-params.conf
# Self-signed
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
# SSL params
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_tickets off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
@moodeaudio can you post the certificate details you're seeing in browser? Specifically looking for the common name and subject alternative names. For Chrome on Windows this looks like:
@bitkeeper
- First issue is that not all home router have a correct working dhcp with dns integration. (Can not resolve devices based on name). Also assuming for the local domain can get you into trouble.
.local
domains are resolved using MDNS and do not rely on your home router. As long as the client and the moode box are on the same subnet it should work. If you want to configure a regular unicast DNS name for the moode box that would be on the user to setup their DNS server correctly.
- Not sure if a self signed certificate for 10 year is really a best security practice.
- Without authentication signing/encrypting will not prevent abuse by just opening a webclient to it. For example start the web ssh terminal.
This is very true, but encryption is better than no encryption and a long-term self-signed certificate is a low-friction way to get that without requiring user configuration. Further security enhancements are worth discussing but I'd recommend keeping this issue about the low hanging fruit that is enabling encryption.
- Next is the message Tim shows; if very scary for my other family members. Even if it is there only one time.
- Certificate is signed and created by device; I prefer to have the choice to supply it my self. (Added one CA as trusted to often used home clients, devices will get a cert signed by the CA. In this way you will not get any warnings on new devices, while you can easy add more devices with https )
This would be possible if a UI were added to allow the user to upload a customer certificate/keypair. The user will need to know how to properly construct the certificate file, i.e. certificate chaining, but I think that's a reasonable assumption for anyone running their own CA. Another possible solution is Let's Encrypt via certbot, but that requires publicly-resolvable DNS records which Let's Encrypt can verify, a discussion for another day I think.
- And what about other open ports from programs like mpd, upmpdcli ? Should for example mpd default be bound to localhost instead of 0.0.0.0 ?
Something I was going to bring up in a separate issue. A host firewall should absolutely be enabled but it will require some effort to understand exactly which services need to be allowed. A number of the protocols in use, like AirPlay and UPnP, are not as straightforward as HTTP/S.
If something is eavesdropping on your local lan you will prob have other 'issues' as well. For example login with SSH to gain root access to the Pi will cause more harm (expect that most users never change the default password).
True, but not a reason to not encrypt. What if the moode box is on an open guest network, or available publicly via port forwarding?
The camilladsp gui can be setup to be bound to http://localhost:15000. With a nginx conf you can redirect for example https://moode/camillagui to http://localhost:15000.
Yes, Nginx is perfect for this.
@moodeaudio I'd recommend keeping the /etc/pki/tks/certs
and /etc/pki/tls/private
paths, they are standard on many Linux distributions.
I'd also recommend structuring the Nginx configs as below. This means the setting only needs to comment/uncomment the redirect/include lines under the listen 80 section. I'd also recommend not disabling the IPv6 listen statement, MDNS responses can be for IPv6 and if both the moode box and the client have that enabled it will be used by default on many clients.
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
client_max_body_size 75M;
##
# SSL Settings
##
#ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
#ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log off;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
#include /etc/nginx/conf.d/*.conf;
#include /etc/nginx/sites-enabled/*;
##
# moOde UI server
##
server {
listen 80 default_server;
listen [::]:80 default_server;
# Redirect HTTP to HTTPS
#return 301 https://$host$request_uri;
# moOde web server config
include snippets/moode.conf;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/pki/tls/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/pki/tls/private/nginx-selfsigned.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
# moOde web server config
include snippets/moode.conf;
}
}
location / {
root /var/www;
index index.html index.php;
try_files $uri $uri/ /coverart.php;
}
location /imagesw/ {
root /var/local/www;
}
# php-fpm
location ~ \.php$ {
root /var/www;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $request_filename;
include fastcgi_params;
}
@moodeaudio I'd recommend placing a static OpenSSL config on the filesystem and then running it from the script, using an environment variable for the hostname. This is a lot cleaner in the script and doesn't use a temp file.
[ req ]
default_bits = 2048
encrypt_key = no
default_md = sha256
string_mask = nombstr
prompt = no
distinguished_name = req_dn
req_extensions = req_ext
oid_section = new_oids
[ req_dn ]
commonName = ${ENV::MOODE_HOSTNAME}.local
[ req_ext ]
basicConstraints = critical, CA:FALSE
keyUsage = digitalSignature, keyEncipherment, nonRepudiation
extendedKeyUsage = clientAuth, serverAuth
subjectAltName = @req_sans
[ req_sans ]
DNS.1 = ${ENV::MOODE_HOSTNAME}.local
DNS.2 = ${ENV::MOODE_HOSTNAME}
#!/bin/bash
export MOODE_HOSTNAME="testing"
openssl req -new -config /etc/pki/tls/nginx-selfsigned.conf -out /etc/pki/tls/nginx-selfsigned.csr -keyout /etc/pki/tls/private/nginx-selfsigned.key
You can check the results with openssl req -text -in /etc/pki/tls/nginx-selfsigned.csr
Wrt the recent posts:
On RaspiOS the standard is /etc/ssl/ and so I'm not seeing a good reason to use something else.
Good points on the configs. I'll review.
Here's some screenies:
Ah, /etc/pki/tls
is standard on EL-based distros, I didn't realize it was different for Debian-based distros.
I'm not sure why you're getting reprompted each time, the CN and SANs look correct. What is the error you see on the prompt page in Chrome?
Similar to your pic
Do you have the same problem if you go to https://moode.local ?
That screenie was from direct entry of the the https url. It's same if http is directly entered and 301'd to https.
I meant trying the FQDN rather than just the hostname. It's possible there's special handling in Chrome for TLDs?
Yes, I tested moode and moode.local. Same result.
I can make a test image available with the feature so you can attempt a repro. You would just need to add a few files to make it fully functional. Email tim at moodeaudio dot org and I'll send you a download link.
Will reopen if there is a solution that does not impact the user.
Maybe Browser makers will come up with something for HTTPS for web applications on residential networks.
I saw the custom scripts feature for the restore process and wrote a script which will re-enable TLS on Nginx.
/etc/pki/tls/certs/$HOSTNAME.local.crt
and, if not, creates a self-signed certificate which is valid for 10 years./etc/nginx/nginx.conf
for TLS support and, if not present, adds it and an HTTP->HTTPS redirect.This should be fairly easy to adapt into the image itself as a first-run script. Note that it should be re-run whenever the hostname changes.
Potential future improvements: