bnhf / openvpn-admin-plus

Docker-based web interface (with golang backend) for monitoring and admin of an OpenVPN TAP/TUN server setup with PiVPN or other OpenVPN server installations. This project has been renamed from pivpn-tap-web-ui, to reflect its new broader scope.
MIT License
141 stars 23 forks source link

SSL certificate and HTTPS protocol for OpenVPNAdmin #36

Closed karabelnikov closed 1 year ago

karabelnikov commented 1 year ago

@bnhf

Scott, I would like to touch on another important topic when using the OpenVPN Admin project in production. This is a connection to the web interface via an SSL certificate using an HTTPS connection. How difficult is it to implement in a container? Many use their own certificate authority to issue certificates for use in the organization projects, such as OpenVPN Admin.

bnhf commented 1 year ago

@karabelnikov

This would not be difficult to implement, either as a code change, or by updating the app.conf file, as referenced in this PR from the original project: https://github.com/adamwalach/openvpn-web-ui/pull/9

The app.conf change seems like the way to go, but I don't know much about the whole certificate generation process. We'd want users to have a self-signed certificate initially, with the ability to get a "real" certificate I would think -- but as I say I'm not really tuned-in to this process.

Personally, I would continue to use http since I only use the interface on my local network or over a VPN. The other reason I use http for most of my internal server stuff is Organizr, which I love, particularly when my various WebUIs work in iFrames. I've been known to go to some lengths to disable https, for this very reason. :-)

bnhf commented 1 year ago

@karabelnikov

Oh, and one other thing worth mentioning:

If you notice in the comment attached to the PR, the path for the certificates has "dehydrated" in it, which I'm sure refers to this project: https://github.com/dehydrated-io/dehydrated

karabelnikov commented 1 year ago

@bnhf

My suggestion is to use the code editing path. HTTPS should be an alternative to HTTP, but not a complete replacement. I threw some thoughts, I think you get the point. This is how I see it:

beego.BConfig.Listen.EnableHTTPS = true
beego.BConfig.Listen.HTTPSPort = 8443
beego.BConfig.Listen.HTTPSCertFile = "$SSLFolder" + "$SSLCert"
beego.BConfig.Listen.HTTPSKeyFile =  "$SSLFolder" + "$SSLPrivateKey"

$SSLFolder = "/opt/openvpn-admin/ssl/"
$SSLCert = {{ .SSLCert }}
$SSLPrivateKey = {{ .SSLKey }}

settings.html

<div class="form-group">
        <label for="name">SSL Server Certificate</label>
        <input type="text" class="form-control" id="SSLCert" name="SSLCert" placeholder="Enter certificate name"
          value="{{ .Settings.SSLCert }}">
        <span class="help-block">File name of the certificate to enable the https protocol. In the folder: /opt/openvpn-admin/ssl/</span>
 </div>
 <div class="form-group">
        <label for="name">SSL Server Private Key</label>
        <input type="text" class="form-control" id="SSLKey" name="SSLKey" placeholder="Enter private key name"
          value="{{ .Settings.SSLKey }}">
        <span class="help-block">File name of the private key to enable the https protocol. In the folder: /opt/openvpn-admin/ssl/</span>
 </div>

Add lines to settings.html file, write that you need to specify the filenames name.crt and name.key to enable the HTTPS protocol.

Administrator: Write file names in the lines on the settings.html page, saves and restarts the container. The https protocol should work.

karabelnikov commented 1 year ago

This is how you create folders:

package main

import (
    "os"
)

func main() {

    err = os.MkdirAll("openvpn-admin/ssl", 0777)
    if err != nil {
        panic(err)
    }

}
karabelnikov commented 1 year ago

Снимок экрана 2022-12-16 в 12 54 42

bnhf commented 1 year ago

@karabelnikov

I've added support for https, including the optional use of a custom certificate and key. You'll need to use a custom docker-compose to test it until we determine that it's ready for prime time:

version: '3'
services:
  gui:
    image: bnhf/pivpn-tap-web-ui:beta
    container_name: openvpn-gui-tap
    environment:
      - OPENVPN_ADMIN_USERNAME=admin
      - OPENVPN_ADMIN_PASSWORD=b3secure
      - COUNTRY=${COUNTRY}
      - PROVINCE=${PROVINCE}
      - CITY=${CITY}
      - ORG=${ORG}
      - EMAIL=${EMAIL}
      - OU=${OU}
      - PIVPN_SERVER=${PIVPN_SERVER}
      - PIVPN_CONF=${PIVPN_CONF}
      - TZ=${TZ}
      - ENABLEHTTPS=${ENABLEHTTPS} # Optional (default=false)
      - HTTPSPORT=${HTTPSPORT} # Optional (default=8443)
      - HTTPSCERT=${HTTPSCERT} # Optional (default=/etc/openvpn/easy-rsa/pki/issued/${PIVPN_SERVER}.crt
      - HTTPSKEY=${HTTPSKEY} # Optional (default=/etc/openvpn/easy-rsa/pki/private/${PIVPN_SERVER}.key
    ports:
       - "8080:8080/tcp"
       - "8443:8443/tcp" # Optional (required if ENABLEHTTPS=true)
    restart: always
    volumes:
       - /etc/openvpn:/etc/openvpn
       - ./openvpn-data/db:/opt/openvpn-gui-tap/db

Then, of course, you'd need to actually supply the environment variables in that section of the Portainer Stack.

karabelnikov commented 1 year ago

@bnhf

Hi, Scott! I have checked the functionality of the https protocol and can confirm that everything is working as it should. Great!

It turns out we have two scenarios:

  1. OpenVPN Admin uses the existing certificate and private key of PIVPN Server in the standard way. It turns out that you only need to install the CA root certificate on the client machine and use the hostname as the name of the PIVPN server and everything works.

  2. When creating a container, set in variables:

    HTTPSCERT=/etc/ssl/certs/server.crt
    HTTPSKEY=/etc/ssl/private/server.key

    specify your way to the server certificate and private key and install the certificate authority certificate on the client machine and you can access the browser by the host name.

Снимок экрана 2022-12-18 191346

karabelnikov commented 1 year ago

Scott, for some reason, after the first restart of the container or after it is stopped and turned on, immediately after the initial deployment, OpenVPN Admin is no longer available over http and https. If you look at the container, then the PORT CONFIGURATION disappears from it

bnhf commented 1 year ago

@karabelnikov

Not sure if this is the reason, but you can't map your own certificates into whatever location you want. Since /etc/openvpn is bound from the host to the container, you could create an ssl directory there, i.e. /etc/openvpn/ssl. Alternatively, if /etc/ssl doesn't already exist in the container (check first please), you could add an additional volume mapping to the docker-compose.

karabelnikov commented 1 year ago

@bnhf

Scott, made the container as in the screenshots. This works as it should, until the container is restarted for the first time or stopped and started. I don't specify my user certificates, I haven't tested such a scenario yet. But in the standard version, the container crashes after the following launches.

Снимок экрана 2022-12-18 232958

Снимок экрана 2022-12-18 233023

bnhf commented 1 year ago

@karabelnikov

Comment out the 3 variables you're not using currently in the docker-compose and delete those same 3 from the environment variables section of Portainer -- then restart the container.

karabelnikov commented 1 year ago

@bnhf

Scott, if I do as you wrote, then everything works. Deleting variables, restarting and everything works. But if I leave variables and specify their values, then the problem remains. After the first restart, the container crashes.

Снимок экрана 2022-12-19 002415

Снимок экрана 2022-12-19 002429

karabelnikov commented 1 year ago

If we want to specify variables, then it doesn't work.

bnhf commented 1 year ago

@karabelnikov

I see what you are saying, and I'm working on it now.

bnhf commented 1 year ago

@karabelnikov

Something I didn't realize, is that when a container gets restarted rather than redeployed, the app.conf file doesn't get recreated. The start.sh script is run again though, leading to the .crt file name being concatenated to the environment variable. I need a slightly different sed command in the script. For, now re-deploy the container in Portainer - Stacks (no need to re-pull, just re-deploy) rather than restarting. Shouldn't take long to fix.

karabelnikov commented 1 year ago

@bnhf

Scott, I've tried it in different ways, but it doesn't work fine for me, as it should. If I specify variable data, then the container crashes. Alas!

bnhf commented 1 year ago

@karabelnikov

I'm building a new development container now. I'll do some testing, and if this problem appears solved, I'll build a new multi-arch beta and post back here to let you know.

bnhf commented 1 year ago

@karabelnikov

There's more to this issue than I first thought, so don't use these variables for now. I'll have to look at this more after I'm back.

bnhf commented 1 year ago

@karabelnikov

I made my changes, did the commit, but then forgot to push them to Github -- little wonder it seemed like nothing was fixed when I built my dev container. :-/ Anyway, it appears my tweaks were successful, so I'm building a new multi-arch container now, which should be live very shortly.

Also, I had no idea there were so many configuration options available through the Beego framework and its app.conf file. Makes sense to me for us to map this .conf file (and maybe the whole conf directory) to the host so that advanced users can edit it directly. We'll make this change part of a future update.

https://github.com/beego/beedoc/blob/master/en-US/mvc/controller/config.md

karabelnikov commented 1 year ago

@bnhf

Scott, I take it you haven't solved the problem completely yet? I tested it and got the following results:

1. If you do not specify variables:

HTTPSPORT=${HTTPSPORT}
HTTPSCERT=${HTTPSCERT}
HTTPSKEY=${HTTPSKEY}

Then everything works, including restarting the container or stopping and restarting.

2. If you specify variables as default values forcibly:

HTTPSPORT=${HTTPSPORT}
HTTPSCERT=${HTTPSCERT}
HTTPSKEY=${HTTPSKEY}

Then everything works, including restarting the container or stopping and restarting.

3. If you specify the port and values with absolute paths in the variables:

HTTPSPORT=8443
HTTPSCERT=/etc/openvpn/easy-rsa/pki/issued/debian_34c93990-1e2e-49af-a768-f221fe898792.crt
HTTPSKEY=/etc/openvpn/easy-rsa/pki/private/debian_34c93990-1e2e-49af-a768-f221fe898792.key

Then nothing works. So setting your values causes the container to crash.

bnhf commented 1 year ago

@karabelnikov

You're a good tester Shura -- thanks. My modified sed was working for two of the variables, but not the other two. Your testing strategy of using the default variables, as though they were optional, was helpful.

It's working for me now on an arm64 platform, with all four variables specifically set. The new beta is available now, so give it a try when you have a moment.

bnhf commented 1 year ago

@karabelnikov

If you look at the Portainer logs for the container (you'll want to set Lines=500 and then turn Auto-refresh off), you should see something like this (I'm posting just the beginning and end of the log:

TERM environment variable not set.
OpenVPN directory set to /etc/openvpn
Working Directory set to /opt
PiVPN Server set to raspberrypi10_ca8554c5-6025-442f-9d06-25a5cab85a3c
Working directory set to /opt/openvpn-gui-tap
HTTPS enabled set to "true"
HTTPS port set to: "8443"
HTTPS Certificate path set to: "/etc/openvpn/easy-rsa/pki/issued/raspberrypi10_ca8554c5-6025-442f-9d06-25a5cab85a3c.crt"
HTTPS key path set to: "/etc/openvpn/easy-rsa/pki/private/raspberrypi10_ca8554c5-6025-442f-9d06-25a5cab85a3c.key"
...
2022/12/19 11:43:17.208 [I] [asm_arm64.s:1133]  http server Running on http://:8080
2022/12/19 11:43:17.209 [I] [asm_arm64.s:1133]  https server Running on https://:8443

If you don't, please post what you do see from similar log snippets.

karabelnikov commented 1 year ago

@bnhf

Scott, I tested and I can safely say everything works.

Option 1. We just enable "ENABLEHTTPS=true" support and don't specify any other variables. Used by default. After starting and 3 reboots of the container, everything works.

Option 2. We enable "ENABLEHTTPS=true" support and specify variables, but do not specify their values. Leaving the values as variables themselves for the default parameters in order to specify your data in the future.

HTTPSPORT=${HTTPSPORT}
HTTPSCERT=${HTTPSCERT}
HTTPSKEY=${HTTPSKEY}

After starting and 3 reboots of the container, everything works.

Option 3. We enable "ENABLEHTTPS=true" support and specify our data:

HTTPSPORT=8443
HTTPSCERT=/etc/openvpn/ssl/debian_34c93990-1e2e-49af-a768-f221fe898792.crt
HTTPSKEY=/etc/openvpn/ssl/debian_34c93990-1e2e-49af-a768-f221fe898792.key

After starting and 3 reboots of the container, everything works.

However, an important note! The path must be relative to the /etc/openvpn directory. Since this path is mounted in the container options at startup. If you specify a path other than /etc/openvpn, then you will have to add this path to the mount options, which is redundant.

Let's summarize. HTTPS support is fully functional.

karabelnikov commented 1 year ago

@karabelnikov

Если вы посмотрите журналы Portainer для контейнера (вы захотите установить Lines=500, а затем отключить автоматическое обновление), вы должны увидеть что-то вроде этого (я публикую только начало и конец журнала:

TERM environment variable not set.
OpenVPN directory set to /etc/openvpn
Working Directory set to /opt
PiVPN Server set to raspberrypi10_ca8554c5-6025-442f-9d06-25a5cab85a3c
Working directory set to /opt/openvpn-gui-tap
HTTPS enabled set to "true"
HTTPS port set to: "8443"
HTTPS Certificate path set to: "/etc/openvpn/easy-rsa/pki/issued/raspberrypi10_ca8554c5-6025-442f-9d06-25a5cab85a3c.crt"
HTTPS key path set to: "/etc/openvpn/easy-rsa/pki/private/raspberrypi10_ca8554c5-6025-442f-9d06-25a5cab85a3c.key"
...
2022/12/19 11:43:17.208 [I] [asm_arm64.s:1133]  http server Running on http://:8080
2022/12/19 11:43:17.209 [I] [asm_arm64.s:1133]  https server Running on https://:8443

Если вы этого не сделаете, пожалуйста, опубликуйте то, что вы видите из аналогичных фрагментов журнала.

I didn't quite understand why to make the value 500? Now in the magazine I have so:

OpenVPN directory set to /etc/openvpn
Working Directory set to /opt
TERM environment variable not set.
PiVPN Server set to debian_34c93990-1e2e-49af-a768-f221fe898792
Working directory set to /opt/openvpn-gui-tap
HTTPS enabled set to "true"
HTTPS Certificate set to default: "debian_34c93990-1e2e-49af-a768-f221fe898792".crt
HTTPS key set to default: "debian_34c93990-1e2e-49af-a768-f221fe898792".key

2022/12/20 02:48:48.271 [I] [asm_amd64.s:1581]  http server Running on http://:8080
2022/12/20 02:48:48.273 [I] [asm_amd64.s:1581]  https server Running on https://:8443
bnhf commented 1 year ago

@karabelnikov

Excellent news! Sorry it took multiple attempts to get it right. sed is nice utility but it can be fussy, and isn't implemented exactly the same across versions of Linux.

Log lines add up surprisingly quickly, so if you login to the application and then look at the Portainer log, 100 lines won't get you back to the first boot line. It doesn't hurt to set it large when you want to be sure to be able to scroll back. I often just add a zero and make it 1000 before I turn off Auto-refresh.