HowTo: TeddyCloud & Reverse-Proxy NGINX using Docker

Hey TeddyCloud Fellows,
I want to share something I’ve been working on with the flying around configs here in the forum. I’ve created a personal GitHub repository for TeddyCloud using Docker. It’s all set up to make it super easy to get TeddyCloud and NGINX running with Docker. The Webpanel uses Let’s Encrypt Certificates and Basic Authentication for securing the access.
Check it out here:

I would appreciate any feedback, issues, or anything else you might have.

1 Like

I would love to have an alternative authentication to the oldschool htaccess. like using a certificate also.

1 Like

If you would implement cert based authentication, add the following 2 lines and remove the lines for basic authentication:

ssl_client_certificate /opt/teddycloud/certs/MyOwnCA.crt;
ssl_verify_client on;

The first line is the path to the cert of your own, self-signed CA, created with openssl or XCA (X - Certificate and Key management ). The second line only allowsclients, with a signed cert from this specific CA to connect. Important to know, that this is fully independent from the server cert for TLS

-Teddy

3 Likes

@henryk @Teddy,
Thanks for your request. I also had certificate based authentication in mind. But it is not possible with the actual configuration. When using nginx ssl_preread for SNI-based routing, you cannot use SSL client verify. Dumb NGINX :smiley:.

So, I stayed with a simple version. However, I could implement it in a more complex way. This would involve using a second nginx container. The first nginx is just for the SNI-based routing. The second one then can handle the client-cert and proxy-forward to the teddycloud web interface.

Or have I misunderstood something here?

I do not think you did missunderstood something.

I just want to get rid of the basic auth. Just for the frontend. (You could also disable basic auth if opened from certain IPs, but usually you do not have a fixed ip for ll your devices)

I’ve made some modifications and added optional IP filtering for the server/backend and the web interface. If there’s no htpasswd file, basic auth will be disabled as well. To implement IP filtering and disable basic auth, remove the existing .htpasswd file and add the following lines to your .env file:
ALLOWED_IPS_WEB=
ALLOWED_IPS_BACKEND=

The README should be up to date. :sweat_smile:
I’m fiddling around with an additional scenario for cert authentication.

It should be both.

Skip htacces basic auth for given IPs, use basic auth for all others.

(Example: I have a fix ip at home, but not at grandparents house. So normal use at home shall not have basic auth, everywhere else it should)

Alrighty. I added BYPASS_AUTH_IPS=. This allows Bypassing the basic auth for given IPs.
Not fully tested. Give it a try :smiley:

I will give it a Try In a few weeks. No time currently.

1 Like

@Quentendo64 you are correct, sni would not work, therefore I have published the webinterface on a different port e.g. 4443 and use cert based authentication there, while 443 will be used by the tonieboxes

If I understand the docker compose yams correctly, you only use port 80 and 443. But teddycloud supports an additional port 8443 for https webfrontend, shouldn’t you support that also? And if I got the thread correctly, would it be possible to add only on the port 8443 the cert authentication and leave port 80 with basic authentication?

No, I do 4443 with cert based authentication only. It should be possible to try cert based authentication first and fall back to basic, on the same port. Let me do some tests over the next days and I will post my nginx config

Ok, that’s fine to :slight_smile: thank you!

This shoud do the job:

server {
        listen 4443 ssl;
        listen [::]:4443 ssl;

        server_name tc.test.com;
        ssl_certificate /etc/letsencrypt/live/tc.test.com/fullchain.pem; 
        ssl_certificate_key /etc/letsencrypt/live/tc.test.com/privkey.pem;
        ssl_client_certificate /opt/teddycloud/certs/CA_Privat.crt;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256";
        # Session improvements
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 1d;
        ssl_session_tickets off;
        auth_basic_user_file    /etc/nginx/conf.d/.htpasswd;

        ssl_verify_client optional;

        if ($ssl_protocol = "") {
                return 301 http://$server_name$request_uri;
        }

        location / {
                client_max_body_size 4096M;

                if ($ssl_client_verify = SUCCESS) {
                        set $auth_basic off;
                }
                if ($ssl_client_verify != SUCCESS) {
                        set $auth_basic Restricted;
                }

                auth_basic $auth_basic;

                add_header X-Frame-Options "SAMEORIGIN" always;
                add_header X-Content-Type-Options "nosniff" always;
                add_header X-XSS-Protection "1; mode=block" always;
                add_header Referrer-Policy "no-referrer-when-downgrade" always;
                add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
                # Disable request buffering for binary uploads
                proxy_request_buffering off;
                # Ensure headers are properly forwarded
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                # Timeouts
                proxy_read_timeout 300s;
                proxy_connect_timeout 60s;
                proxy_send_timeout 300s;
                # Buffer configuration for large binary transfers
                proxy_buffer_size 16k;
                proxy_buffers 8 16k;
                proxy_busy_buffers_size 32k;



                proxy_pass http://172.18.0.2/;

                #proxy_cache                     off;
                # Headers for client browser NOCACHE + CORS origin filter
                #add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
                #expires off;
        }
}

# openssl x509 -fingerprint -in ../certs/client/client.der 
map $ssl_client_fingerprint $reject {
        default 1;
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 0; # client rosa Box 111111111111
        "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" 0; # client graue Box 222222222222
        "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW" 0; # client hellblaue Box 333333333333
        "UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU" 0; # client beere Box 444444444444 
}
map $ssl_client_fingerprint $mac {
        "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "111111111111"; # client rosa Box
        "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" "222222222222"; # client graue Box
        "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW" "333333333333"; # client hellblaue Box
        "UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU" "444444444444"; # client beere
}

server {
        ## TeddyCloud subdomain
        listen                                    443 ssl;
        server_name _;

        ## Reject connection if ssl fingerprint from remote is unknown
        if ($reject) {
                return 403;
        }

        access_log /var/log/nginx/tc.access.log;
        error_log /var/log/nginx/tc.error.log;

        ## Edit ssl protocols and ciphers
        #ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers HIGH:!aNULL:!MD5@SECLEVEL=0;

        ## Server's certificate and private key

        ssl_certificate /opt/teddycloud/certs/nginx/ttt-fullchain.pem;
        ssl_certificate_key /opt/teddycloud/certs/nginx/ttt-teddy-key.pem;

        ## Require client certificate
        ssl_client_certificate /opt/teddycloud/certs/nginx/ca.pem;
        #ssl_verify_client on;
        ssl_verify_client optional_no_ca;

        ## Root location of subdomain
        location / {
                proxy_read_timeout 300s;
                proxy_connect_timeout 75s;
                proxy_pass https://172.18.0.2:443;
                # openssl x509 -inform DER -in client.der -out client.pem
                proxy_ssl_certificate /opt/teddycloud/certs/client/$mac/client.pem;
                # openssl rsa -inform DER -in private.der -out private.pem
                proxy_ssl_certificate_key /opt/teddycloud/certs/client/$mac/private.pem;
                proxy_ssl_conf_command Options UnsafeLegacyRenegotiation;
        }
}
1 Like

Thank you! @Quentendo64 maybe you can integrate this into your setup? I think this would be a great improvement!

@Teddy, thank you for your configuration.
@henryk, I will implement the approach as well.

Currently, I am working on another Tonie-related project regarding .TAF conversion. As soon as I am on a clean state there, I will invest time for implementing the other nginx approach.

Take your time. As my domain for that take longer as planned, I won’t make it before vacation. So no hurry :wink:

There is one major concern by ChatGPT:

Fingerprint Validation in NGINX map directive

if ($reject) {
    return 301  https://www.google.de;
}
  • Redirecting to Google is not a real security block.
  • It might leak traffic and confuse clients.
  • Suggestion: Replace with return 403; or log a warning and drop the request.

I think return 403 is the better solution. Or redirect to lemonparty,org :wink: (just joking, of course not)

Other things are okay as toniebox needs some unsecure (legacy) things.

agreed, already changed in my config;-)

Hello guys,
as discussed on Telegram, i will share my configuration for teddycloud setup with NGINX. Its running for 2 months now in “production”, i only faced some upload-timeout issues, i hope i fixed the most of them but maybe i didnt catched all :stuck_out_tongue: I will also review the setup of @Quentendo64 , looks quite similar to mine.

In general: I use a config based on the following idea:

As i have several other applications up and running, its the best solution for me, but that does not mean its the best solution for you if you dont have other services up and running on that machine :stuck_out_tongue:

Basically, all traffic (toniebox, web-accesses to all your domains) will be forwarded to one port to NGINX-STREAM module. The stream module routes the traffic based on the SNI (Server Name Indication) to your dedicated NGINX-SERVER modules. If no SNI is provided, traffic will be forwarded to teddycloud:443 port, so authentification for toniebox can be done there direclty. If you access the page tc.example.com with a browser, SNI will be provided and it will be forwarded to the dedicated NGINX-Server module, where you can also configure Letsencrypt-certificates and do a certificate-based authentification (thats what i did) or password-prompt.

Routing as diagram


:

Config for steam module:

upstream backend {
        server 127.0.0.1:9443;  # Replace with the host for teddycloud.example.com
}

upstream no_sni {
        server 127.0.0.1:443;  # Replace with the host for traffic without SNI
}


map $ssl_preread_server_name $upstream_name {
      tc.example.com backend;  # Match specific SNI
      cloud.example.com backend;
      apps.example.com backend;
      default no_sni;                        # Default upstream for other cases
}

    # Configure the server block
server {
  listen 8443;
  ssl_preread on;
  proxy_connect_timeout 36000s;
  proxy_pass $upstream_name;
}

Config for NGINX-Server module:

server {
    listen 9443 ssl http2;
    listen [::]:9443 ssl http2;
    ssl_certificate /etc/letsencrypt/live/tc.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tc.example.com/privkey.pem;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;

    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1.2 TLSv1.3;

    server_name tc.example.com;
    server_tokens off;
    error_page 497  https://$host:$server_port$request_uri;

    #Eigene Zertifikat-Authentifizierung, bitte abschalten, wenn nicht selbst implementiert
    ssl_client_certificate /opt/easy-rsa/keys/ca.crt;
    ssl_crl /opt/easy-rsa/keys/crl.pem;
    ssl_verify_client optional;

    client_max_body_size 4096M;

    location / {
         proxy_pass         http://127.0.0.1:5020;
         proxy_set_header   Host $host;
         proxy_set_header   X-Real-IP $remote_addr;
         proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header   X-Forwarded-Host $server_name;
         proxy_set_header   X-Forwarded-Proto https;
         proxy_max_temp_file_size 4096M;
         proxy_connect_timeout  36000s;
         proxy_read_timeout  36000s;
         proxy_send_timeout  36000s;
         send_timeout  36000s;
         proxy_buffers 6 16k;
         proxy_buffer_size 32k;

         #if client-side certificate failed - show error message
         #Eigene Zertifikat-Authentifizierung, bitte abschalten, wenn nicht selbst implementiert
         if ($ssl_client_verify != SUCCESS) {
            return 403;
         }

         if ($scheme != "https") {
            return 301 https://$host$request_uri;
         } # Macht einen Redirct auf HTTPs, falls Schema nicht https ist.

         access_log      /var/log/nginx/teddy.access.log;
         error_log       /var/log/nginx/teddy.error.log;
    }


    location /web/favicon.ico {
         alias /var/www/html/web/favicon.ico;

    }

    location /.well-known/acme-challenge {
        alias /var/www/tc.example.com/.well-known/acme-challenge;
        location ~ /.well-known/acme-challenge/(.*) {
            add_header Content-Type application/jose+json;
        }
    }
}



Main Benefit, if you do it with the stream-config:

  • you can flash you’re tonie box with same URL as you access it via browser (example: tc.example.com)
  • you can run several other apps on same port
  • letsencrypt certificates
  • certificate authentification possible