add new post (closes #11)
Build & Push Hugo Site Image / Build & Push Image (push) Successful in 12s

This commit is contained in:
2025-08-06 09:36:53 -05:00
parent acc732ba4d
commit caef3bb430
@@ -0,0 +1,162 @@
+++
categories = ["software"]
tags = ["proxy","infrastructure","traefik","crowdsec"]
date = 2025-08-06T08:00:00-05:00
description = ""
draft = false
slug = "edge-reverse-proxy"
title = "🧭 Centralized, Secure Reverse Proxy at the edge"
author = "nicholas"
+++
In a [previous post ]({{< relref "posts/infrastructure-overhaul" >}}) I recorded the state of my network at the time, which included no centralized reverse proxy. There was no need. I accessed my services **locally** and each of the service host ran its own reverse proxy on its own IP. I show this configuration in the diagram below.
**✅ Multiple Reverse Proxies (LOCAL ONLY)**
```mermaid
graph TD
subgraph lan[🏠LAN]
client[🧑‍💻Client]
subgraph hostA["🖥️Docker Host"]
reverseproxyA["🚪Reverse Proxy"]
serviceAA["🧠LLM"]
end
subgraph hostB["🖥️Kubernetes Host"]
reverseproxyB["🚪Reverse Proxy"]
serviceBA["🖼️Photos"]
serviceBB["📝Blog"]
end
end
client -->|192.168.1.10:443|reverseproxyA
client -->|192.168.1.11:443|reverseproxyB
reverseproxyA -->|llm.domain.com|serviceAA
reverseproxyB -->|photos.domain.com|serviceBA
reverseproxyB -->|blog.domain.com|serviceBB
```
However, now I would like to access certain services from beyond my local network, and not just for myself. I want them to be accessible to all web users, otherwise I would simply set up a client-to-site VPN and be done. I want them to be accessible over HTTPS.
If I want to expose a service on **two different local hosts** on a **single port** (:443, HTTPS) then I need a **single host** to bind to the single port. I show what this would look like in the diagram below.
> ⚠️ A single public IP and port 443 can only be forwarded to one internal IP at a time.
**✅ Single Edge Reverse Proxy**
```mermaid
graph TD
subgraph wan[🌐WAN]
client[🧑‍💻Client]
publicip["172.153.14.23:443"]
end
subgraph router[🛡️Router]
forward["WAN:443 → LAN:443"]
end
subgraph lan[🏠LAN]
subgraph hostEdge["🖥️Edge Host"]
reverseproxy["🚪Edge Reverse Proxy"]
end
hostA["🖥️ Docker Host"]
hostB["🖥️ Kubernetes Host"]
end
client -->|HTTPS to domain| publicip
publicip -->|HTTPS traffic| router
router -->|NAT| reverseproxy
reverseproxy -->|192.168.1.10:443|hostA
reverseproxy --> |192.168.1.11:443|hostB
```
## ➡️ Effects
**New host / VM**
I will need a new host to run my reverse proxy.
**Unencrypted traffic**
Moving from having multiple reverse proxies (each terminating TLS) to a single edge reverse proxy means TLS termination will happen at the edge and all traffic inside the LAN is now unencrypted. This is fine.
**DNS Configuration**
DNS entries will need to be updated to point to new proxy host.
**Automatic TLS cert renewal**
Kubernetes ingress controllers will no longer manage TLS certificates. This responsibility will shift to Traefik to simplify things.
**IPS**
Since I am allowing traffic through port 443 into my LAN, I will want a way to prevent rampant bots from molesting my network. I will use an IPS called **CrowdSec**.
**Firewall**
Sometime later, I may configure a router / firewall in front of the reverse proxy to preemptively block IPs from outside of the USA. Much of the suspected malicious traffic originates from strange foreign countries like Bulgaria, China, etc.
## 🖥️ New VM / Edge Host
The VM which will run my edge services (reverse proxy, IPS) will need very few resources. I set it up with the following specs:
| Resource | Specification |
|-|-|
| OS | Ubuntu Server 24 (minimal) |
| vCPU | 1 |
| RAM | 2048 MB |
| Storage | 10 GB |
All I need to do is:
- Install Docker
- Install, configure `ufw`
## 🚪Reverse Proxy + IPS
I run these two programs in Docker containers. The diagram below shows the directory structure of this stack. I do not bother adding configuration files to this post. **Security through obscurity**
**Directory structure**
```plaintext
📦ege-stack-config
├─📂crowdsec
│ └─📜acquis.yaml
├─📂traefik
│ ├─📂dynamic
│ │ ├─📜middlewares.yaml
│ │ ├─📜routers.yaml
│ │ └─📜services.yaml
│ └─📜traefik.yaml
├─📂logs
│ └─📜access.log
└─🐋docker-compose.yaml
```
Traefik performs its normal functions as a reverse proxy, generating access logs for incoming HTTPS traffic. CrowdSec reads these logs, parses them, and generates a decision as to whether an IP should be banned. CrowdSec sends this along to a **bouncer** to enforce the decision. For my bouncer, I am using a Traefik plugin aptly named *Crowdsec Bouncer Traefik plugin*.
## 🧪 Test it
After some time, I go check whether CrowdSec has banned any bots.
`docker exec -it crowdsec cscli alerts list`
```plaintext
╭────┬───────────────────┬───────────────────────────────────────┬─────────┬─────────────────────────────────────────────┬───────────┬─────────────────────────────────────────╮
│ ID │ value │ reason │ country │ as │ decisions │ created_at │
├────┼───────────────────┼───────────────────────────────────────┼─────────┼─────────────────────────────────────────────┼───────────┼─────────────────────────────────────────┤
│ 86 │ Ip:129.146.52.207 │ crowdsecurity/thinkphp-cve-2018-20062 │ US │ 31898 ORACLE-BMC-31898 │ ban:1 │ 2025-08-06 07:51:14.59356465 +0000 UTC │
│ 85 │ Ip:129.146.52.207 │ crowdsecurity/http-probing │ US │ 31898 ORACLE-BMC-31898 │ ban:1 │ 2025-08-06 07:50:27.497858459 +0000 UTC │
│ 84 │ Ip:129.146.52.207 │ crowdsecurity/CVE-2017-9841 │ US │ 31898 ORACLE-BMC-31898 │ ban:1 │ 2025-08-06 07:50:31.986742743 +0000 UTC │
│ 80 │ Ip:47.89.154.16 │ crowdsecurity/http-probing │ US │ 45102 Alibaba US Technology Co., Ltd. │ ban:1 │ 2025-08-06 02:14:47.399363246 +0000 UTC │
│ 78 │ Ip:43.166.136.113 │ crowdsecurity/thinkphp-cve-2018-20062 │ US │ 132203 Tencent Building, Kejizhongyi Avenue │ ban:1 │ 2025-08-06 01:28:25.249998707 +0000 UTC │
│ 77 │ Ip:43.166.136.113 │ crowdsecurity/http-probing │ US │ 132203 Tencent Building, Kejizhongyi Avenue │ ban:1 │ 2025-08-06 01:27:55.834522627 +0000 UTC │
│ 76 │ Ip:43.166.136.113 │ crowdsecurity/CVE-2017-9841 │ US │ 132203 Tencent Building, Kejizhongyi Avenue │ ban:1 │ 2025-08-06 01:28:08.746875988 +0000 UTC │
│ 74 │ Ip:8.222.225.103 │ crowdsecurity/thinkphp-cve-2018-20062 │ SG │ 45102 Alibaba US Technology Co., Ltd. │ ban:1 │ 2025-08-05 22:04:44.325377414 +0000 UTC │
│ 73 │ Ip:8.222.225.103 │ crowdsecurity/http-probing │ SG │ 45102 Alibaba US Technology Co., Ltd. │ ban:1 │ 2025-08-05 22:04:30.396505319 +0000 UTC │
│ 72 │ Ip:8.222.225.103 │ crowdsecurity/CVE-2017-9841 │ SG │ 45102 Alibaba US Technology Co., Ltd. │ ban:1 │ 2025-08-05 22:04:34.878409129 +0000 UTC │
│ 69 │ Ip:20.163.10.187 │ crowdsecurity/http-cve-probing │ US │ 8075 MICROSOFT-CORP-MSN-AS-BLOCK │ ban:1 │ 2025-08-05 19:22:47.360100531 +0000 UTC │
│ 67 │ Ip:20.163.15.225 │ crowdsecurity/CVE-2022-41082 │ US │ 8075 MICROSOFT-CORP-MSN-AS-BLOCK │ ban:1 │ 2025-08-05 15:56:51.234726408 +0000 UTC │
│ 56 │ Ip:23.140.40.74 │ crowdsecurity/thinkphp-cve-2018-20062 │ RU │ 61400 Start LLC │ ban:1 │ 2025-08-04 21:01:55.536820464 +0000 UTC │
│ 55 │ Ip:23.140.40.74 │ crowdsecurity/http-probing │ RU │ 61400 Start LLC │ ban:1 │ 2025-08-04 21:01:44.946667866 +0000 UTC │
│ 54 │ Ip:23.140.40.74 │ crowdsecurity/CVE-2017-9841 │ RU │ 61400 Start LLC │ ban:1 │ 2025-08-04 21:01:48.880039776 +0000 UTC │
╰────┴───────────────────┴───────────────────────────────────────┴─────────┴─────────────────────────────────────────────┴───────────┴─────────────────────────────────────────╯
```
🎉 It works! The table generated by `cscli` shows a few IP addresses have been banned.
✅ Done.