Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using HTTPS by default #247

Closed
robm82 opened this issue Feb 24, 2020 · 11 comments
Closed

Using HTTPS by default #247

robm82 opened this issue Feb 24, 2020 · 11 comments
Labels
wontfix The concern raised in this issue will not be acted upon.

Comments

@robm82
Copy link

robm82 commented Feb 24, 2020

Desired Behavior

It would be great if the compose file could incorporate using HTTPS with LetsEncrypt SSL certificates straight away. If this is not possible, is there a recommended method for enabling HTTPS?
...

Contrast to Current Behavior

Currently when deploying Netbox as a Docker image, it runs with port 8080 with no encryption which can lead to issues with security when deploying Netbox into a production environment.

...

Changes Required

I presume some changes would be required to the docker-compose.yml file as well as some nginx configuration changes?
As previously mentioned, if this isn't suitable is there any guidance to get Netbox working over HTTPS?

...

Discussion: Benefits and Drawbacks

The community would benefit certainly, as the traffic to the Netbox container would be secure by default.

...

@lunarthegrey
Copy link

lunarthegrey commented Feb 28, 2020

Usually the recommended way is to put a reverse proxy in front of these types of setups. It adds complexity to the project if maintainers have to make sure HTTPS is working, a certificate is fetched and that sort of thing. So I wouldn't consider this to be and issue with netbox-docker.

Here are some details https://blog.linuxserver.io/2017/11/28/how-to-setup-a-reverse-proxy-with-letsencrypt-ssl-for-all-your-docker-apps/

@cimnine
Copy link
Collaborator

cimnine commented Mar 9, 2020

Albeit a good idea, @lunarthegrey already explained why this feature request is unfeasible.

There are a lot of problems to be solved if we would like to do this, first and foremost the problem of the domain name. Also the respective container would have to be exposed to the internet. At least in my case, where this project is only used for developing integrations, this is never the case.

Therefore I'm going to close this issue. But if someone would like to contribute a wiki page describing how to setup TLS, I'm all ears.

@cimnine cimnine closed this as completed Mar 9, 2020
@cimnine cimnine added the wontfix The concern raised in this issue will not be acted upon. label Mar 9, 2020
@isodude
Copy link

isodude commented Jun 11, 2021

Hi,

It was pretty straight forward when you get into it.

commit aa4ed9946a65684332f74ed247cdcae2bd7493d2
Author: root <root@netbox.prod.oderland>
Date:   Fri Jun 11 07:50:33 2021 +0200

    Add TLS support
    
    Signed-off-by: Josef Johansson <josef86@gmail.com>

diff --git a/docker-compose.yml b/docker-compose.yml
index 4b0fda3..e4f276e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,7 +15,12 @@ services:
     - ./configuration:/etc/netbox/config:z,ro
     - ./reports:/etc/netbox/reports:z,ro
     - ./scripts:/etc/netbox/scripts:z,ro
+    - ./docker/bundle.pem:/etc/unit/bundle.pem:z,ro
+    - ./docker/nginx-unit.json:/etc/unit/nginx-unit.json:z,ro
+    - ./docker/launch-netbox.sh:/opt/netbox/launch-netbox.sh:z,ro
     - netbox-media-files:/opt/netbox/netbox/media:z
+    ports:
+    - 443:8443
   netbox-worker:
     <<: *netbox
     depends_on:
diff --git a/docker/launch-netbox.sh b/docker/launch-netbox.sh
index 3245c38..278fc12 100755
--- a/docker/launch-netbox.sh
+++ b/docker/launch-netbox.sh
@@ -1,9 +1,54 @@
 #!/bin/bash
 
 UNIT_CONFIG="${UNIT_CONFIG-/etc/unit/nginx-unit.json}"
+UNIT_PEM="${UNIT_PEM-/etc/unit/bundle.pem}"
 UNIT_SOCKET="/opt/unit/unit.sock"
 
+load_certificate() {
+  MAX_WAIT=10
+  WAIT_COUNT=0
+  while [ ! -S $UNIT_SOCKET ]; do
+    if [ $WAIT_COUNT -ge $MAX_WAIT ]; then
+      echo "⚠️ No control socket found; configuration will not be loaded."
+      return 1
+    fi
+
+    WAIT_COUNT=$((WAIT_COUNT + 1))
+    echo "⏳ Waiting for control socket to be created... (${WAIT_COUNT}/${MAX_WAIT})"
+
+    sleep 1
+  done
+
+  # even when the control socket exists, it does not mean unit has finished initialisation
+  # this curl call will get a reply once unit is fully launched
+  curl --silent --output /dev/null --request GET --unix-socket $UNIT_SOCKET http://localhost/
+
+  echo "⚙️ Applying certificate from $UNIT_PEM"
+
+  NAME="${UNIT_PEM%%.pem}"
+  NAME="${NAME##*/}"
+  RESP_CODE=$(
+    curl \
+      --output /dev/null \
+      --write-out '%{http_code}' \
+      --silent \
+      --request PUT \
+      --data-binary "@${UNIT_PEM}" \
+      --unix-socket $UNIT_SOCKET \
+      http://localhost/certificates/${NAME}
+  )
+  if [ "$RESP_CODE" != "200" ]; then
+    echo "⚠️ Could no load Unit certificate"
+    kill "$(cat /opt/unit/unit.pid)"
+    return 1
+  fi
+
+  echo "✅ Unit certificate loaded successfully"
+}
+
+
 load_configuration() {
+  load_certificate
   MAX_WAIT=10
   WAIT_COUNT=0
   while [ ! -S $UNIT_SOCKET ]; do
diff --git a/docker/nginx-unit.json b/docker/nginx-unit.json
index fbe8c2b..89c84cd 100644
--- a/docker/nginx-unit.json
+++ b/docker/nginx-unit.json
@@ -1,7 +1,10 @@
 {
   "listeners": {
-    "*:8080": {
-      "pass": "routes"
+    "*:8443": {
+      "pass": "routes",
+         "tls": {
+        "certificate": "bundle"
+         }
     }
   },

@isodude
Copy link

isodude commented Jun 11, 2021

Adapting the above code to use letsencrypt would be easy, I will probably have to do it soon.

If anyone want to, just ping me and I'll implement it.

@towfiquepbl
Copy link

Yes, I need help for making netbox secure (https), It is installed in Docker

@isodude
Copy link

isodude commented Aug 25, 2021

Hi, not there yet with the let's encrypt implementation. But I will do a MR with it when time is due.

@isodude
Copy link

isodude commented Aug 25, 2021

@cimnine I could look at implementing support for this if you'd like, not a wiki, but rather options such that if you enter them it will try to issue a certificate, or use one available on disk.

@cimnine
Copy link
Collaborator

cimnine commented Aug 25, 2021

I believe we have a rather good solution documented in the wiki.

TBH, I'm so far not willing to support a TLS implementation in our repository. We're barely able to keep up with the upstream changes to keep the basic image working. So I really don't want to take on the additional work of maintaining a TLS solution or provide support in case of errors, even if that solution would be provided to us.

Bit again, feel free to update the wiki with your instructions / patches.

@isodude
Copy link

isodude commented Oct 12, 2021

I hade to solve ACME anyhow so, here's a way of doing it.

Please note that nsenter to run certbot on the host, but in the dockers namespace.
The correct command for certbot is thus somewhere along the line (not fully tested).

id="$(docker ps --filter 'name=.*_netbox_1' --format '{{.ID}}')"
netns="$(docker inspect "$id" --format '{{.NetworkSettings.SandboxKey}}')"
sslpath="$(docker inspect "$id" --format '{{index (split (index .HostConfig.Binds 5) ":") 0}}')";
nsenter --net --target "$netns" certbot certonly --deploy-hook "cp \"$RENEW_LINEAGE/{fullchain,privkey}.pem\" \"$sslpath\"; docker-compose -f \"$sslpath../../docker-compose.yml\" restart netbox" ...
diff --git a/docker-compose.yml b/docker-compose.yml
index 09d52a7..fc23939 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,9 +15,32 @@ services:
     - ./configuration:/etc/netbox/config:z,ro
     - ./reports:/etc/netbox/reports:z,ro
     - ./scripts:/etc/netbox/scripts:z,ro
+    - ./docker/ssl:/etc/unit/ssl:ro
+    - ./docker/nginx-unit.json:/etc/unit/nginx-unit.json:z,ro
+    - ./docker/launch-netbox.sh:/opt/netbox/launch-netbox.sh:z,ro
     - netbox-media-files:/opt/netbox/netbox/media:z
+    ports:
+    - 443:8443
+    - 80:8080
   netbox-worker:
-    <<: *netbox
+    image: netboxcommunity/netbox:${VERSION-v3.0}
+    depends_on:
+    - postgres
+    - redis
+    - redis-cache
+    - netbox-worker
+    env_file: env/netbox.env
+    user: '101'
+    volumes:
+    - ./startup_scripts:/opt/netbox/startup_scripts:z,ro
+    - ./initializers:/opt/netbox/initializers:z,ro
+    - ./configuration:/etc/netbox/config:z,ro
+    - ./reports:/etc/netbox/reports:z,ro
+    - ./scripts:/etc/netbox/scripts:z,ro
+    - ./docker/bundle.pem:/etc/unit/bundle.pem:z,ro
+    - ./docker/nginx-unit.json:/etc/unit/nginx-unit.json:z,ro
+    - ./docker/launch-netbox.sh:/opt/netbox/launch-netbox.sh:z,ro
+    - netbox-media-files:/opt/netbox/netbox/media:z
     depends_on:
     - redis
     entrypoint:
@@ -31,7 +54,7 @@ services:
     image: postgres:13-alpine
     env_file: env/postgres.env
     volumes:
-    - netbox-postgres-data:/var/lib/postgresql/data
+    - netbox-postgres-data-13:/var/lib/postgresql/data
 
   # redis
   redis:
@@ -54,7 +77,7 @@ services:
 volumes:
   netbox-media-files:
     driver: local
-  netbox-postgres-data:
+  netbox-postgres-data-13:
     driver: local
   netbox-redis-data:
     driver: local
diff --git a/docker/launch-netbox.sh b/docker/launch-netbox.sh
index 3245c38..53e8a60 100755
--- a/docker/launch-netbox.sh
+++ b/docker/launch-netbox.sh
@@ -1,9 +1,142 @@
 #!/bin/bash
 
 UNIT_CONFIG="${UNIT_CONFIG-/etc/unit/nginx-unit.json}"
+UNIT_PEM="${UNIT_PEM-/etc/unit/ssl/fullchain.pem}"
+UNIT_KEY_PEM="${UNIT_KEY_PEM-/etc/unit/ssl/privkey.pem}"
 UNIT_SOCKET="/opt/unit/unit.sock"
 
+function check_or_die()
+{
+  if [ "$1" != "200" ]; then
+    echo "⚠️ Could no load Unit certificate"
+    kill "$(cat /opt/unit/unit.pid)"
+    return 1
+  fi
+}
+
+load_certificate() {
+  MAX_WAIT=10
+  WAIT_COUNT=0
+  while [ ! -S $UNIT_SOCKET ]; do
+    if [ $WAIT_COUNT -ge $MAX_WAIT ]; then
+      echo "⚠️ No control socket found; configuration will not be loaded."
+      return 1
+    fi
+
+    WAIT_COUNT=$((WAIT_COUNT + 1))
+    echo "⏳ Waiting for control socket to be created... (${WAIT_COUNT}/${MAX_WAIT})"
+
+    sleep 1
+  done
+
+  # even when the control socket exists, it does not mean unit has finished initialisation
+  # this curl call will get a reply once unit is fully launched
+  curl --silent --output /dev/null --request GET --unix-socket $UNIT_SOCKET http://localhost/
+
+  echo "⚙️ Applying certificate from $UNIT_PEM and $UNIT_KEY_PEM"
+
+  bundle="/tmp/bundle-new"
+
+  ( cat "$UNIT_PEM"; echo; cat "$UNIT_KEY_PEM" ) > "$bundle"
+
+  NAME="bundle"
+
+  RESP_CODE="$(curl \
+      --output /tmp/bundle \
+      --write-out '%{http_code}' \
+      --silent \
+      --request GET \
+      --data-binary "@${bundle}" \
+      --unix-socket $UNIT_SOCKET \
+      http://localhost/certificates/${NAME})"
+
+  if [ "$RESP_CODE" == "404" ]; then
+  RESP_CODE="$(
+    curl \
+      --output /dev/null \
+      --write-out '%{http_code}' \
+      --silent \
+      --request PUT \
+      --data-binary "@${bundle}" \
+      --unix-socket $UNIT_SOCKET \
+      http://localhost/certificates/${NAME}
+  )"
+  check_or_die "$RESP_CODE"
+else
+
+  if diff -q /tmp/bundle /tmp/bundle-new; then
+  RESP_CODE="$(
+    curl \
+      --output /dev/null \
+      --write-out '%{http_code}' \
+      --silent \
+      --request PUT \
+      --data-binary "@${bundle}" \
+      --unix-socket $UNIT_SOCKET \
+      http://localhost/certificates/${NAME}-new
+  )"
+  check_or_die "$RESP_CODE"
+  RESP_CODE="$(
+    curl \
+      --output /dev/null \
+      --write-out '%{http_code}' \
+      --silent \
+      --request PUT \
+      --data "${NAME}-new" \
+      --unix-socket $UNIT_SOCKET \
+      http://localhost/config/listeners/0.0.0.0:8443/tls/certificates
+  )"
+  check_or_die "$RESP_CODE"
+  RESP_CODE="$(
+    curl \
+      --output /dev/null \
+      --write-out '%{http_code}' \
+      --silent \
+      --request DELETE \
+      --unix-socket $UNIT_SOCKET \
+      http://localhost/certificates/${NAME}
+  )"
+  check_or_die "$RESP_CODE"
+  RESP_CODE="$(
+    curl \
+      --output /dev/null \
+      --write-out '%{http_code}' \
+      --silent \
+      --request PUT \
+      --data-binary "@${bundle}" \
+      --unix-socket $UNIT_SOCKET \
+      http://localhost/certificates/${NAME}
+  )"
+  check_or_die "$RESP_CODE"
+  RESP_CODE="$(
+    curl \
+      --output /dev/null \
+      --write-out '%{http_code}' \
+      --silent \
+      --request PUT \
+      --data "${NAME}" \
+      --unix-socket $UNIT_SOCKET \
+      http://localhost/config/listeners/0.0.0.0:8443/tls/certificates
+  )"
+  check_or_die "$RESP_CODE"
+  RESP_CODE="$(
+    curl \
+      --output /dev/null \
+      --write-out '%{http_code}' \
+      --silent \
+      --request DELETE \
+      --unix-socket $UNIT_SOCKET \
+      http://localhost/certificates/${NAME}-new
+  )"
+  check_or_die "$RESP_CODE"
+  fi
+fi
+ echo "✅ Unit certificate loaded successfully"
+}
+
+
 load_configuration() {
+  load_certificate
   MAX_WAIT=10
   WAIT_COUNT=0
   while [ ! -S $UNIT_SOCKET ]; do
diff --git a/docker/nginx-unit.json b/docker/nginx-unit.json
index fbe8c2b..e1ce70c 100644
--- a/docker/nginx-unit.json
+++ b/docker/nginx-unit.json
@@ -2,13 +2,41 @@
   "listeners": {
     "*:8080": {
       "pass": "routes"
+    },
+
+    "*:8443": {
+      "pass": "routes",
+      "tls": {
+        "certificate": "bundle"
+      }
     }
   },
 
   "routes": [
     {
       "match": {
-        "uri": "/static/*"
+        "uri": "/.well-known*",
+               "scheme": "http"
+      },
+      "action": {
+        "proxy": "http://127.0.0.1:8081"
+      }
+    },
+
+    {
+      "match": {
+               "scheme": "http"
+      },
+      "action": {
+        "return": 301,
+        "location": "https://netbox.prod.oderland"
+      }
+    },
+
+    {
+      "match": {
+        "uri": "/static/*",
+               "scheme": "https"
       },
       "action": {
         "share": "/opt/netbox/netbox"
@@ -16,6 +44,9 @@
     },
 
     {
+      "match": {
+               "scheme": "https"
+      },
       "action": {
         "pass": "applications/netbox"
       }

@cimnine
Copy link
Collaborator

cimnine commented Oct 12, 2021

This looks like an overly complex solution when all it takes are the roughly 20 lines using Caddy that we documented in the wiki: https://github.com/netbox-community/netbox-docker/wiki/TLS#tls-using-caddy

It supports Let's Encrypt as well.

@isodude
Copy link

isodude commented Oct 12, 2021

That's ok. Just documenting since I implemented it.
We had to do something general with it to solve it over the whole DC.
Nginx-unit is a bit of work but I can't find and good documentation regarding updating cert in it.
And I guess you could run certbot as a docker to get a better integration.

Running caddy is nice, we don't have it anywhere though so for us it's a no go.
Maybe this solution is the correct fit for someone else stuck in a corner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix The concern raised in this issue will not be acted upon.
Projects
None yet
Development

No branches or pull requests

5 participants