Restrict Access to OctoPi Utilizing SSL Client Certificates

Disclaimer: First and foremost, I am not an internet security expert and I have no real idea if this procedure helps with securing your OctoPrint instance, but from what I can tell it doen't hurt. I do not take any responsibility for the security of your network and by following this procedure you release me of any liability associated with such. This process assumes that you are using an OctoPi image and only forwarding port 443 from your router to port 443 of your pi.

SSH to your pi and create a working folder and change into it.

mkdir ssl
cd ssl

Create Root Certification Authority

Create your root certificate authority for signing other certificate requests for both the server and users. Follow the prompts to enter passphrase and verify passphrase. This passphrase will be used any time you sign a new certificate request so don't forget it.

openssl genrsa -aes-128-cbc -out OctoPrintCA.key 4096
openssl req -x509 -new -nodes -key OctoPrintCA.key -sha256 -days 1825 -out OctoPrintCA.crt

Fill in whatever you want for the requested information, but for the Common Name you want to use something that you'll be able to recognize as the certificate authority. In this example I used OctoPrint CA for the common name and "blanked" everything else out using a "." for each prompt.

Country Name (2 letter code) [AU]:.
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:OctoPrint CA
Email Address []:.

Create Server Certificate

Now we'll create a server certificate that is signed by our newly created root CA. Ideally you'd have a registered domain name pointing to your router's public ip address. This can be a freely available dynamic dns domain name, and is what I actually use. If you don't have a registered domain name then you will have to contend with name mismatch errors in your browser. If you are just using a static public ip address you can also just enter that for common name as well.

openssl genrsa -out mydomain.com.key 2048
openssl req -new -key mydomain.com.key -out mydomain.com.csr

Because Chrome requires a SAN extension in the certificate,
we need to create a config file with the settings for this
extension. Run nano octopi.cnf to create a file octopi.cnf and add the following
content using your domain name in DNS.1, you can use multiple names by adding additions DNS.# fields, which may be useful for internal names like octopi.local.

[ v3_req ]
basicConstraints       = CA:false
extendedKeyUsage       = serverAuth
subjectAltName         = @alt_names

[ alt_names ]
DNS.1 = mydomain.com

Press ctrl+x to exit and answer Y to save the file and enter to keep the same file name.

Now we're ready to sign the certificate with our CA.

openssl x509 -req -in octopi.csr -CA OctoPrintCA.crt -CAkey OctoPrintCA.key -CAcreateserial -out octopi.crt -days 1825 -sha256 -extensions v3_req -extfile octopi.cnf
cat mydomain.com.crt mydomain.com.key > mydomain.com.pem

Create User Certificate

Create an individual user's certificate that will be used to "authorize" a device to connect to haproxy once we lock that down. It will be signed by our new CA we created above, and the process is the same as creating the server certificate. The important part here is to enter the Common Name as the user you want to identify as. When exporting the p12 format file make sure to remember the password as that will be needed any time you import the file into a device that will be used to connect.

openssl genrsa -out username1.key 2048
openssl req -new -key username1.key -out username1.csr
openssl x509 -req -in username1.csr -CA OctoPrintCA.crt -CAkey OctoPrintCA.key -CAcreateserial -out username1.crt -days 499 -sha256
openssl pkcs12 -export -in username1.crt -inkey username1.key -out username1.p12

Haproxy Configuration Changes

Now we'll update the haproxy config to bind port 443 to our new server cert after creating a backup of the original file.

cd /etc/haproxy
sudo cp haproxy.cfg haproxy.cfg.bak
sudo nano haproxy.cfg

Your specific configuration in haproxy.cfg may be different but the changes you will be making are in the global and frontend public sections. For the global section we just need to add a line that tell haproxy to use a higher bit length for the ssl connection. At the end of the global section just add the line tune.ssl.default-dh-param 2048.

Now in the frontend public section find the line like this bind :::443 v4v6 ssl crt /etc/ssl/snakeoil.pem and change it to bind :::443 v4v6 ssl crt /home/pi/ssl/mydomain.com.pem ca-file /home/pi/ssl/OctoPrintCA.crt verify required. This next change is optional but recommended; instruct haproxy to redirect standard http port 80 to use https port 443. Find the line option forwardfor except 127.0.0.1 and just below it enter redirect scheme https code 301 if !{ ssl_fc }.

Your final haproxy.cfg file should look something like the one below. This one started as the default config that came with OctoPi 0.15.1 with the above modifications completed.

global
        maxconn 4096
        user haproxy
        group haproxy
        log 127.0.0.1 local1 debug
		tune.ssl.default-dh-param 2048

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        retries 3
        option redispatch
        option http-server-close
        option forwardfor
        maxconn 2000
        timeout connect 5s
        timeout client  15min
        timeout server  15min

frontend public
        bind :::80 v4v6
        bind :::443 v4v6 ssl crt /home/pi/ssl/mydomain.com.pem ca-file /home/pi/ssl/OctoPrintCA.crt verify required
        option forwardfor except 127.0.0.1
        redirect scheme https code 301 if !{ ssl_fc }
        use_backend webcam if { path_beg /webcam/ }
		use_backend gstreamer if { path_beg /gstreamer/ }
        default_backend octoprint

backend octoprint
        acl needs_scheme req.hdr_cnt(X-Scheme) eq 0

        reqrep ^([^\ :]*)\ /(.*) \1\ /\2
        reqadd X-Scheme:\ https if needs_scheme { ssl_fc }
        reqadd X-Scheme:\ http if needs_scheme !{ ssl_fc }
        option forwardfor
        server octoprint1 127.0.0.1:5000
        errorfile 503 /etc/haproxy/errors/503-no-octoprint.http

backend webcam
        reqrep ^([^\ :]*)\ /webcam/(.*)     \1\ /\2
        server webcam1  127.0.0.1:8080
        errorfile 503 /etc/haproxy/errors/503-no-webcam.http

At this point save the file by pressing ctrl+x followed by y for yes and enter to overwrite the previous file. Restart haproxy sudo service haproxy restart and if we attempt to go to our octoprint https address you will get the below error. This is good news, because this is what we want. If you don't have a user certificate signed by our Certificate Authority it will not allow the connection.

image

You will now need to transfer the username1.p12 file from the pi to any device that you want to grant access to OctoPrint. This process varies from device to device, but for windows you just double-click the file and run through the certificate import wizard selecting all the default options and entering the password used when exporting the p12 file previously. If you want to avoid getting a trusted root certificate authority error you will also need to import the file OctoPrintCA.crt, but during the import wizard choose the option to add it to a specific store, press browse and choose the Trusted Root Certification Authorities.

If all goes well now when you attempt to go to the https address of your OctoPi you'll get right in to the standard web interface and be able to login, etc.

You may get certificate errors where you can click the Advanced link (in chrome) and choose the option to proceed to the site. This typically will happen because of a common name/url mismatch or an untrusted root certificate authority. This doesn't necessarily mean the connection isn't secure, but means the browser is not seeing what it expects to see related to the connection and the certificate(s) used to establish the connection.

Some common fixes for these errors are importing the CA certificate as mentioned previously, update your hosts file to point the common name used to generate the server certificate to your internal ip address (if you used a dns name for this) and access by the name instead of ip.

Optional Auto Login to OctoPrint

If you'd like for OctoPrint to automatically log you in with the client certificate you need to set the CN of the client certificate to the same as the username in OctoPrint when creating it. Then make the following changes to config.yaml in OctoPrint's base folder (/home/pi/.octoprint/config.yaml).

accessControl:
  trustRemoteUser: true

and the following line to /etc/haproxy/haproxy.cfg in the frontend section just before use_backend webcam line

http-request set-header REMOTE_USER %{+Q}[ssl_c_s_dn(cn)]
6 Likes

What version of Octopi/Raspbian are you using?
After making the changes and setting up everything as shown here, haproxy is throwing a ton of errors about v4 isn't a recognized command, :::443 not a valid address etc.

Linux octopi 4.14.67-v7+ #1139 SMP Wed Aug 29 15:17:05 BST 2018 armv7l GNU/Linux

HA-Proxy version 1.7.5-2 2017/05/27
Copyright 2000-2017 Willy Tarreau willy@haproxy.org

Thanks

I have this same setup on both an octopi 0.14 and 0.15.1 installs.

443 is the port address being used. Double check that your router and the Octo are the same address. 443 is HTTP with SSL, you may be using 80, HTTP, or 8080, HTTP proxy.

I started having other network issues as well, and popped in a fresh build of Jessie. everything worked as it should, so I re-downloaded Octopi, reflashed, configured and all is good. Somehow something got corrupted. Ne SD card (San Disk, and not a clone)

1 Like

I can't find the username1.p12 file. Where is it stored?

Everything should be able to be found in the ssl sub-folder that you created in the first step.

I've followed this guide and everything works well. However, I'm getting warnings on the web browser (sec_error_unknown_issuer), which I guess that it's unavoidable due to the fact that we are signing the certificate ourselves.

Do you know how could we use a certificate provided by, let's say, letsencrypt?

You might try a web search for something like browser how to import self-signed certificate. Or change "self-signed" to letsencrypt. It is usually different per system and browser.

For Safari, I'd guess that it starts by clicking the lock next to the hostname in the upper URL field. "Show Certificate" is then an option. If in my case this weren't a trusted certificate, I'm guessing I would be offered an opportunity to import that certificate, prompting for my admin password since that is what's necessary for making security adjustments on my MacBook.

I solved it already. Thanks for the help!

Since the Root/User certificates you generate in this guide don't have any sort of relation with the domain certification, you just have to skip the part of this guide where you generate the domain certificate:

openssl genrsa -out mydomain.com.key 2048
openssl req -new -key mydomain.com.key -out mydomain.com.csr
openssl x509 -req -in mydomain.com.csr -CA OctoPrintCA.crt -CAkey OctoPrintCA.key -CAcreateserial -out mydomain.com.crt -days 500 -sha256
cat mydomain.com.crt mydomain.com.key > mydomain.com.pem

And just use the certificate generated by letsencrypt instead. This guide was very useful regarding how to generate the certificate with haproxy: https://serversforhackers.com/c/letsencrypt-with-haproxy

(Note that in order to generate the needed .pem file for haproxy, you'll need to merge the cert and the privkey files given by certbot, as explained here: https://stackoverflow.com/a/27959983)

As a follow-up question, however: Does this work on android devices? I've installed both the .p12 file and the OctoPrintCA.crt on the android certificate manager, but it will refuse to connect anyways, saying that "the secured connection failed". It works on both windows and linux, though, and it also works on android if I remove the "verify required" part on the haproxy configuration (but that defeats the whole purpose of this, doesn't it?).

Any idea about what could be done here?

1 Like

It does work on android devices as I was using it with my setup. I don't remember if I did anything special for importing the file though. I've since removed my public forwarding but can re-enable it and get back to you. I vaguely remember having to do something special to import the certificate by saving it to the SD card or something.

1 Like

I would really appreciate it if you had some spare time to do so. I've tried for a while but I haven't made any progress so far.

So just checked and yes, I was able to re-enable my forwarding and get through to the server. It brought up a screen to select a certificate to use for identity and I selected my user cert and then received a notice about untrusted server certificate, click advanced button, and then click proceed. I never tried to import my own CA, but it looks like that is an option. I am using the chrome browser on my android, in case that makes a difference.

I need to see this file in PI sd card?
If the file in PI Linux system, how do I take it out to sd card?
Thanks for the help!!


Please some help, I'm really getting in here,
what I missing??

I would use a tool like winscp to copy the file over the network, but the error indicates you have another problem with your haproxy config potentially because the haproxy service won't start.

thank you for the answer!
I've been working on this for over 10 hours,
I can get the file out,
A friend of mine made the system work like a win98 :smile:
Even after installing the certificates, I get a browser error
please help

Have you tried the systemctl and journalctl command as the error message suggested?

Thanks for the answer!
I really don't know what that means,
What should I check?

Type

sudo systemctl status haproxy.service

and

sudo journalctl -xe

(the things you get es error message after you used sudo service haproxy restart)

It could show you where the problem is

1 Like