mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-04-21 13:37:48 +00:00
feat: enhance custom SSL certificate support to accept plaintext PEM format in addition to base64 encoding
This commit is contained in:
parent
37bdef6622
commit
036c5340ef
6 changed files with 141 additions and 72 deletions
|
|
@ -96,3 +96,5 @@ docs/web-ui.md:hashicorp-tf-password:993
|
|||
docs/web-ui.md:hashicorp-tf-password:1149
|
||||
docs/web-ui.md:hashicorp-tf-password:1180
|
||||
src/common/core/antibot/files/recaptcha.html:generic-api-key:26
|
||||
src/common/core/customcert/README.md:private-key:84
|
||||
docs/features.md:private-key:1847
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
- [DEPS] Update coreruleset-v4 version to v4.13.0
|
||||
- [FEATURE] Add the possibility to choose a profile when generating certificates with Let's Encrypt using the `LETS_ENCRYPT_PROFILE` setting (`classic` (default), `tlsserver` for server-only validation, and `shortlived` for reduced 7-day validity) to provide flexibility in certificate configuration based on security requirements
|
||||
- [FEATURE] Add the possibility to declare custom certificates and keys data as plaintext as well as base64-encoded data in the `customcert` plugin using the `CUSTOM_SSL_CERT_DATA` and `CUSTOM_SSL_KEY_DATA` settings
|
||||
- [UI] Integrate Biscuit authentication and key management
|
||||
|
||||
## v1.6.2-rc1 - 2025/03/29
|
||||
|
|
|
|||
|
|
@ -1502,7 +1502,7 @@ CrowdSec is a modern, open-source security engine that detects and blocks malici
|
|||
};
|
||||
|
||||
destination d_file {
|
||||
file("/var/log/bunkerweb/bunkerweb.log" template(t_imp));
|
||||
file("/var/log/bunkerweb.log" template(t_imp));
|
||||
};
|
||||
|
||||
log {
|
||||
|
|
@ -1765,7 +1765,7 @@ The Custom SSL certificate plugin allows you to use your own SSL/TLS certificate
|
|||
|
||||
**How it works:**
|
||||
|
||||
1. You provide BunkerWeb with your certificate and private key files, either by specifying file paths or by providing the data in base64-encoded format.
|
||||
1. You provide BunkerWeb with your certificate and private key files, either by specifying file paths or by providing the data in base64-encoded or plaintext PEM format.
|
||||
2. BunkerWeb validates your certificate and key to ensure they are properly formatted and usable.
|
||||
3. When a secure connection is established, BunkerWeb serves your custom certificate instead of the auto-generated one.
|
||||
4. BunkerWeb automatically monitors your certificate's validity and displays warnings if it is approaching expiration.
|
||||
|
|
@ -1779,9 +1779,9 @@ The Custom SSL certificate plugin allows you to use your own SSL/TLS certificate
|
|||
Follow these steps to configure and use the Custom SSL certificate feature:
|
||||
|
||||
1. **Enable the feature:** Set the `USE_CUSTOM_SSL` setting to `yes` to enable custom certificate support.
|
||||
2. **Choose a method:** Decide whether to provide certificates via file paths or as base64-encoded data, and set the priority using `CUSTOM_SSL_CERT_PRIORITY`.
|
||||
2. **Choose a method:** Decide whether to provide certificates via file paths or as base64-encoded/plaintext data, and set the priority using `CUSTOM_SSL_CERT_PRIORITY`.
|
||||
3. **Provide certificate files:** If using file paths, specify the locations of your certificate and private key files.
|
||||
4. **Or provide certificate data:** If using base64 data, provide your certificate and key as base64-encoded strings.
|
||||
4. **Or provide certificate data:** If using data, provide your certificate and key as either base64-encoded strings or plaintext PEM format.
|
||||
5. **Let BunkerWeb handle the rest:** Once configured, BunkerWeb automatically uses your custom certificates for all HTTPS connections.
|
||||
|
||||
!!! tip "Stream Mode Configuration"
|
||||
|
|
@ -1795,8 +1795,8 @@ Follow these steps to configure and use the Custom SSL certificate feature:
|
|||
| `CUSTOM_SSL_CERT_PRIORITY` | `file` | multisite | no | **Certificate Priority:** Choose whether to prioritize the certificate from file path or from base64 data (`file` or `data`). |
|
||||
| `CUSTOM_SSL_CERT` | | multisite | no | **Certificate Path:** Full path to your SSL certificate or certificate bundle file. |
|
||||
| `CUSTOM_SSL_KEY` | | multisite | no | **Private Key Path:** Full path to your SSL private key file. |
|
||||
| `CUSTOM_SSL_CERT_DATA` | | multisite | no | **Certificate Data:** Your certificate encoded in base64 format. |
|
||||
| `CUSTOM_SSL_KEY_DATA` | | multisite | no | **Private Key Data:** Your private key encoded in base64 format. |
|
||||
| `CUSTOM_SSL_CERT_DATA` | | multisite | no | **Certificate Data:** Your certificate encoded in base64 format or as plaintext PEM. |
|
||||
| `CUSTOM_SSL_KEY_DATA` | | multisite | no | **Private Key Data:** Your private key encoded in base64 format or as plaintext PEM. |
|
||||
|
||||
!!! warning "Security Considerations"
|
||||
When using custom certificates, ensure your private key is properly secured and has appropriate permissions. The files must be readable by the BunkerWeb scheduler.
|
||||
|
|
@ -1832,6 +1832,23 @@ Follow these steps to configure and use the Custom SSL certificate feature:
|
|||
CUSTOM_SSL_KEY_DATA: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSEV...base64 encoded key...Cg=="
|
||||
```
|
||||
|
||||
=== "Using Plaintext PEM Data"
|
||||
|
||||
A configuration using plaintext certificate and key data in PEM format:
|
||||
|
||||
```yaml
|
||||
USE_CUSTOM_SSL: "yes"
|
||||
CUSTOM_SSL_CERT_PRIORITY: "data"
|
||||
CUSTOM_SSL_CERT_DATA: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDdzCCAl+gAwIBAgIUJH...certificate content...AAAA
|
||||
-----END CERTIFICATE-----
|
||||
CUSTOM_SSL_KEY_DATA: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADAN...key content...AAAA
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
=== "Fallback Configuration"
|
||||
|
||||
A configuration that prioritizes files but falls back to base64 data if files are unavailable:
|
||||
|
|
@ -2835,18 +2852,21 @@ STREAM support :x:
|
|||
|
||||
Provides load balancing feature to group of upstreams with optional healthchecks.
|
||||
|
||||
| Setting | Default | Context | Multiple | Description |
|
||||
| ----------------------------------------- | --------- | ------- | -------- | ------------------------------------------------------------------ |
|
||||
| `LOADBALANCER_HEALTHCHECK_DICT_SIZE` | `10m` | global | no | Shared dict size (datastore for all healthchecks). |
|
||||
| `LOADBALANCER_UPSTREAM_NAME` | | global | yes | Name of the upstream (used in REVERSE_PROXY_HOST). |
|
||||
| `LOADBALANCER_UPSTREAM_SERVERS` | | global | yes | List of servers/IPs in the server group. |
|
||||
| `LOADBALANCER_HEALTHCHECK_URL` | `/status` | global | yes | The healthcheck URL. |
|
||||
| `LOADBALANCER_HEALTHCHECK_INTERVAL` | `2000` | global | yes | Healthcheck interval in milliseconds. |
|
||||
| `LOADBALANCER_HEALTHCHECK_TIMEOUT` | `1000` | global | yes | Healthcheck timeout in milliseconds. |
|
||||
| `LOADBALANCER_HEALTHCHECK_FALL` | `3` | global | yes | Number of failed healthchecks before marking the server as down. |
|
||||
| `LOADBALANCER_HEALTHCHECK_RISE` | `1` | global | yes | Number of successful healthchecks before marking the server as up. |
|
||||
| `LOADBALANCER_HEALTHCHECK_VALID_STATUSES` | `200` | global | yes | HTTP status considered valid in healthchecks. |
|
||||
| `LOADBALANCER_HEALTHCHECK_CONCURRENCY` | `10` | global | yes | Maximum number of concurrent healthchecks. |
|
||||
| Setting | Default | Context | Multiple | Description |
|
||||
| ----------------------------------------- | ------------------- | ------- | -------- | ----------------------------------------------------------------------------------- |
|
||||
| `LOADBALANCER_HEALTHCHECK_DICT_SIZE` | `10m` | global | no | Shared dict size (datastore for all healthchecks). |
|
||||
| `LOADBALANCER_UPSTREAM_NAME` | | global | yes | Name of the upstream (used in REVERSE_PROXY_HOST). |
|
||||
| `LOADBALANCER_UPSTREAM_SERVERS` | | global | yes | List of servers/IPs in the server group. |
|
||||
| `LOADBALANCER_UPSTREAM_MODE` | `round-robin` | global | yes | Load balancing mode (round-robin or sticky). |
|
||||
| `LOADBALANCER_UPSTREAM_STICKY` | `srv_id expires=4h` | global | yes | Settings when load balancing mode is set to sticky (see sticky directive of nginx). |
|
||||
| `LOADBALANCER_UPSTREAM_RESOLVE` | `no` | global | yes | Dynamically resolve upstream hostnames. |
|
||||
| `LOADBALANCER_HEALTHCHECK_URL` | `/status` | global | yes | The healthcheck URL. |
|
||||
| `LOADBALANCER_HEALTHCHECK_INTERVAL` | `2000` | global | yes | Healthcheck interval in milliseconds. |
|
||||
| `LOADBALANCER_HEALTHCHECK_TIMEOUT` | `1000` | global | yes | Healthcheck timeout in milliseconds. |
|
||||
| `LOADBALANCER_HEALTHCHECK_FALL` | `3` | global | yes | Number of failed healthchecks before marking the server as down. |
|
||||
| `LOADBALANCER_HEALTHCHECK_RISE` | `1` | global | yes | Number of successful healthchecks before marking the server as up. |
|
||||
| `LOADBALANCER_HEALTHCHECK_VALID_STATUSES` | `200` | global | yes | HTTP status considered valid in healthchecks. |
|
||||
| `LOADBALANCER_HEALTHCHECK_CONCURRENCY` | `10` | global | yes | Maximum number of concurrent healthchecks. |
|
||||
|
||||
## Metrics
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ The Custom SSL certificate plugin allows you to use your own SSL/TLS certificate
|
|||
|
||||
**How it works:**
|
||||
|
||||
1. You provide BunkerWeb with your certificate and private key files, either by specifying file paths or by providing the data in base64-encoded format.
|
||||
1. You provide BunkerWeb with your certificate and private key files, either by specifying file paths or by providing the data in base64-encoded or plaintext PEM format.
|
||||
2. BunkerWeb validates your certificate and key to ensure they are properly formatted and usable.
|
||||
3. When a secure connection is established, BunkerWeb serves your custom certificate instead of the auto-generated one.
|
||||
4. BunkerWeb automatically monitors your certificate's validity and displays warnings if it is approaching expiration.
|
||||
|
|
@ -16,9 +16,9 @@ The Custom SSL certificate plugin allows you to use your own SSL/TLS certificate
|
|||
Follow these steps to configure and use the Custom SSL certificate feature:
|
||||
|
||||
1. **Enable the feature:** Set the `USE_CUSTOM_SSL` setting to `yes` to enable custom certificate support.
|
||||
2. **Choose a method:** Decide whether to provide certificates via file paths or as base64-encoded data, and set the priority using `CUSTOM_SSL_CERT_PRIORITY`.
|
||||
2. **Choose a method:** Decide whether to provide certificates via file paths or as base64-encoded/plaintext data, and set the priority using `CUSTOM_SSL_CERT_PRIORITY`.
|
||||
3. **Provide certificate files:** If using file paths, specify the locations of your certificate and private key files.
|
||||
4. **Or provide certificate data:** If using base64 data, provide your certificate and key as base64-encoded strings.
|
||||
4. **Or provide certificate data:** If using data, provide your certificate and key as either base64-encoded strings or plaintext PEM format.
|
||||
5. **Let BunkerWeb handle the rest:** Once configured, BunkerWeb automatically uses your custom certificates for all HTTPS connections.
|
||||
|
||||
!!! tip "Stream Mode Configuration"
|
||||
|
|
@ -32,8 +32,8 @@ Follow these steps to configure and use the Custom SSL certificate feature:
|
|||
| `CUSTOM_SSL_CERT_PRIORITY` | `file` | multisite | no | **Certificate Priority:** Choose whether to prioritize the certificate from file path or from base64 data (`file` or `data`). |
|
||||
| `CUSTOM_SSL_CERT` | | multisite | no | **Certificate Path:** Full path to your SSL certificate or certificate bundle file. |
|
||||
| `CUSTOM_SSL_KEY` | | multisite | no | **Private Key Path:** Full path to your SSL private key file. |
|
||||
| `CUSTOM_SSL_CERT_DATA` | | multisite | no | **Certificate Data:** Your certificate encoded in base64 format. |
|
||||
| `CUSTOM_SSL_KEY_DATA` | | multisite | no | **Private Key Data:** Your private key encoded in base64 format. |
|
||||
| `CUSTOM_SSL_CERT_DATA` | | multisite | no | **Certificate Data:** Your certificate encoded in base64 format or as plaintext PEM. |
|
||||
| `CUSTOM_SSL_KEY_DATA` | | multisite | no | **Private Key Data:** Your private key encoded in base64 format or as plaintext PEM. |
|
||||
|
||||
!!! warning "Security Considerations"
|
||||
When using custom certificates, ensure your private key is properly secured and has appropriate permissions. The files must be readable by the BunkerWeb scheduler.
|
||||
|
|
@ -69,6 +69,23 @@ Follow these steps to configure and use the Custom SSL certificate feature:
|
|||
CUSTOM_SSL_KEY_DATA: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSEV...base64 encoded key...Cg=="
|
||||
```
|
||||
|
||||
=== "Using Plaintext PEM Data"
|
||||
|
||||
A configuration using plaintext certificate and key data in PEM format:
|
||||
|
||||
```yaml
|
||||
USE_CUSTOM_SSL: "yes"
|
||||
CUSTOM_SSL_CERT_PRIORITY: "data"
|
||||
CUSTOM_SSL_CERT_DATA: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDdzCCAl+gAwIBAgIUJH...certificate content...AAAA
|
||||
-----END CERTIFICATE-----
|
||||
CUSTOM_SSL_KEY_DATA: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADAN...key content...AAAA
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
=== "Fallback Configuration"
|
||||
|
||||
A configuration that prioritizes files but falls back to base64 data if files are unavailable:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from sys import exit as sys_exit, path as sys_path
|
|||
from base64 import b64decode
|
||||
from tempfile import NamedTemporaryFile
|
||||
from traceback import format_exc
|
||||
from typing import Tuple, Union
|
||||
from typing import Tuple, Union, Optional, Literal
|
||||
|
||||
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("db",))]:
|
||||
if deps_path not in sys_path:
|
||||
|
|
@ -22,6 +22,48 @@ LOGGER = setup_logger("CUSTOM-CERT")
|
|||
JOB = Job(LOGGER, __file__)
|
||||
|
||||
|
||||
def process_ssl_data(data: str, file_path: Optional[str], data_type: Literal["cert", "key"], server_name: str) -> Union[bytes, Path, None]:
|
||||
"""Process SSL certificate or key data from file path or direct data (base64 or plain text)"""
|
||||
try:
|
||||
if file_path:
|
||||
path_obj = Path(file_path)
|
||||
if not path_obj.is_file():
|
||||
LOGGER.error(f"{data_type.capitalize()} file {file_path} is not a valid file for {server_name}")
|
||||
return None
|
||||
return path_obj
|
||||
|
||||
if not data:
|
||||
return None
|
||||
|
||||
# Try base64 decode first
|
||||
try:
|
||||
decoded = b64decode(data)
|
||||
return decoded
|
||||
except BaseException:
|
||||
LOGGER.debug(format_exc())
|
||||
LOGGER.warning(f"Failed to decode {data_type} data as base64 for server {server_name}, trying as plain text")
|
||||
|
||||
# Try using the data directly as plain text
|
||||
try:
|
||||
text_data = data.encode()
|
||||
# Quick validation check
|
||||
if data_type == "cert" and not text_data.strip().startswith(b"-----BEGIN CERTIFICATE-----"):
|
||||
LOGGER.error(f"Invalid certificate format for server {server_name}")
|
||||
return None
|
||||
elif data_type == "key" and (not text_data.strip().startswith(b"-----BEGIN") or not (b"PRIVATE KEY" in text_data)):
|
||||
LOGGER.error(f"Invalid key format for server {server_name}")
|
||||
return None
|
||||
return text_data
|
||||
except BaseException:
|
||||
LOGGER.debug(format_exc())
|
||||
LOGGER.error(f"Error while processing {data_type} data for server {server_name}")
|
||||
return None
|
||||
except BaseException as e:
|
||||
LOGGER.debug(format_exc())
|
||||
LOGGER.error(f"Error processing {data_type} for {server_name}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def check_cert(cert_file: Union[Path, bytes], key_file: Union[Path, bytes], first_server: str) -> Tuple[bool, Union[str, BaseException]]:
|
||||
try:
|
||||
ret = False
|
||||
|
|
@ -112,59 +154,46 @@ try:
|
|||
LOGGER.info(f"Service {first_server} is using custom SSL certificates, checking ...")
|
||||
|
||||
cert_priority = getenv(f"{first_server}_CUSTOM_SSL_CERT_PRIORITY", getenv("CUSTOM_SSL_CERT_PRIORITY", "file"))
|
||||
cert_file = getenv(f"{first_server}_CUSTOM_SSL_CERT", getenv("CUSTOM_SSL_CERT", ""))
|
||||
key_file = getenv(f"{first_server}_CUSTOM_SSL_KEY", getenv("CUSTOM_SSL_KEY", ""))
|
||||
cert_file_path = getenv(f"{first_server}_CUSTOM_SSL_CERT", getenv("CUSTOM_SSL_CERT", ""))
|
||||
key_file_path = getenv(f"{first_server}_CUSTOM_SSL_KEY", getenv("CUSTOM_SSL_KEY", ""))
|
||||
cert_data = getenv(f"{first_server}_CUSTOM_SSL_CERT_DATA", getenv("CUSTOM_SSL_CERT_DATA", ""))
|
||||
key_data = getenv(f"{first_server}_CUSTOM_SSL_KEY_DATA", getenv("CUSTOM_SSL_KEY_DATA", ""))
|
||||
|
||||
if (cert_file or cert_data) and (key_file or key_data):
|
||||
if (cert_priority == "file" or not cert_data) and cert_file:
|
||||
cert_file = Path(cert_file)
|
||||
else:
|
||||
try:
|
||||
cert_file = b64decode(cert_data)
|
||||
except BaseException as e:
|
||||
LOGGER.debug(format_exc())
|
||||
LOGGER.error(f"Error while decoding cert data, skipping server {first_server} :\n{e}")
|
||||
skipped_servers.append(first_server)
|
||||
status = 2
|
||||
continue
|
||||
# Use file or data based on priority
|
||||
use_cert_file = cert_priority == "file" and cert_file_path
|
||||
use_key_file = cert_priority == "file" and key_file_path
|
||||
|
||||
if (cert_priority == "file" or not key_data) and key_file:
|
||||
key_file = Path(key_file)
|
||||
else:
|
||||
try:
|
||||
key_file = b64decode(key_data)
|
||||
except BaseException as e:
|
||||
LOGGER.debug(format_exc())
|
||||
LOGGER.error(f"Error while decoding key data, skipping server {first_server} :\n{e}")
|
||||
skipped_servers.append(first_server)
|
||||
status = 2
|
||||
continue
|
||||
cert_file = process_ssl_data(cert_data if not use_cert_file else "", cert_file_path if use_cert_file else None, "cert", first_server)
|
||||
|
||||
LOGGER.info(f"Checking certificate for {first_server} ...")
|
||||
need_reload, err = check_cert(cert_file, key_file, first_server)
|
||||
if isinstance(err, BaseException):
|
||||
LOGGER.error(f"Exception while checking {first_server}'s certificate, skipping ... \n{err}")
|
||||
skipped_servers.append(first_server)
|
||||
status = 2
|
||||
continue
|
||||
elif err:
|
||||
LOGGER.warning(f"Error while checking {first_server}'s certificate : {err}")
|
||||
skipped_servers.append(first_server)
|
||||
status = 2
|
||||
continue
|
||||
elif need_reload:
|
||||
LOGGER.info(f"Detected change in {first_server}'s certificate")
|
||||
status = 1
|
||||
continue
|
||||
key_file = process_ssl_data(key_data if not use_key_file else "", key_file_path if use_key_file else None, "key", first_server)
|
||||
|
||||
LOGGER.info(f"No change in {first_server}'s certificate")
|
||||
elif not cert_file or not key_file:
|
||||
if not cert_file or not key_file:
|
||||
LOGGER.warning(
|
||||
"Variables (CUSTOM_SSL_CERT or CUSTOM_SSL_CERT_DATA) and (CUSTOM_SSL_KEY or CUSTOM_SSL_KEY_DATA) have to be set to use custom certificates"
|
||||
"Variables (CUSTOM_SSL_CERT or CUSTOM_SSL_CERT_DATA) and (CUSTOM_SSL_KEY or CUSTOM_SSL_KEY_DATA) "
|
||||
f"have to be set and valid to use custom certificates for {first_server}"
|
||||
)
|
||||
skipped_servers.append(first_server)
|
||||
status = 2
|
||||
continue
|
||||
|
||||
LOGGER.info(f"Checking certificate for {first_server} ...")
|
||||
need_reload, err = check_cert(cert_file, key_file, first_server)
|
||||
if isinstance(err, BaseException):
|
||||
LOGGER.error(f"Exception while checking {first_server}'s certificate, skipping ... \n{err}")
|
||||
skipped_servers.append(first_server)
|
||||
status = 2
|
||||
continue
|
||||
elif err:
|
||||
LOGGER.warning(f"Error while checking {first_server}'s certificate : {err}")
|
||||
skipped_servers.append(first_server)
|
||||
status = 2
|
||||
continue
|
||||
elif need_reload:
|
||||
LOGGER.info(f"Detected change in {first_server}'s certificate")
|
||||
status = 1
|
||||
continue
|
||||
|
||||
LOGGER.info(f"No change in {first_server}'s certificate")
|
||||
|
||||
for first_server in skipped_servers:
|
||||
JOB.del_cache("cert.pem", service_id=first_server)
|
||||
|
|
|
|||
|
|
@ -45,18 +45,18 @@
|
|||
"CUSTOM_SSL_CERT_DATA": {
|
||||
"context": "multisite",
|
||||
"default": "",
|
||||
"help": "Certificate data encoded in base64.",
|
||||
"help": "Certificate data encoded in base64 or plaintext PEM format.",
|
||||
"id": "custom-ssl-cert-data",
|
||||
"label": "Certificate data (base64)",
|
||||
"label": "Certificate data (base64/plaintext)",
|
||||
"regex": "^.*$",
|
||||
"type": "text"
|
||||
},
|
||||
"CUSTOM_SSL_KEY_DATA": {
|
||||
"context": "multisite",
|
||||
"default": "",
|
||||
"help": "Key data encoded in base64.",
|
||||
"help": "Key data encoded in base64 or plaintext PEM format.",
|
||||
"id": "custom-ssl-key-data",
|
||||
"label": "Key data (base64)",
|
||||
"label": "Key data (base64/plaintext)",
|
||||
"regex": "^.*$",
|
||||
"type": "text"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue