Add custom cert to setup wizard + Make it possible to edit some settings when the service is created using the wizard

This commit is contained in:
Théophile Diot 2024-10-28 13:54:32 +01:00
parent aa46ba62d2
commit 56dd0f069c
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
4 changed files with 219 additions and 22 deletions

View file

@ -210,7 +210,7 @@ class Config:
return variables
def new_service(self, variables: dict, is_draft: bool = False, override_method: str = "ui") -> Tuple[str, int]:
def new_service(self, variables: dict, is_draft: bool = False, override_method: str = "ui", check_changes: bool = True) -> Tuple[str, int]:
"""Creates a new service from the given variables
Parameters
@ -235,7 +235,9 @@ class Config:
return f"Service {service['SERVER_NAME'].split(' ')[0]} already exists.", 1
services.append(variables | {"IS_DRAFT": "yes" if is_draft else "no"})
ret = self.gen_conf(self.get_config(methods=False), services, check_changes=not is_draft, override_method=override_method)
ret = self.gen_conf(
self.get_config(methods=False), services, check_changes=False if not check_changes else not is_draft, override_method=override_method
)
if isinstance(ret, str):
return ret, 1
return f"Configuration for {variables['SERVER_NAME'].split(' ')[0]} has been generated.", 0

View file

@ -5,10 +5,10 @@ from os import getenv
from threading import Thread
from time import time
from flask import Blueprint, Response, flash, redirect, render_template, request, session, url_for
from flask import Blueprint, Response, flash, redirect, render_template, request, url_for
from flask_login import current_user
from app.models.totp import totp as TOTP
# from app.models.totp import totp as TOTP
from app.dependencies import BW_CONFIG, DATA, DB
from app.utils import USER_PASSWORD_RX, gen_password_hash
@ -23,7 +23,18 @@ def setup_page():
if current_user.is_authenticated:
return redirect(url_for("home.home_page"))
db_config = DB.get_config(
filtered_settings=("SERVER_NAME", "MULTISITE", "USE_UI", "UI_HOST", "AUTO_LETS_ENCRYPT", "USE_LETS_ENCRYPT_STAGING", "EMAIL_LETS_ENCRYPT"),
filtered_settings=(
"SERVER_NAME",
"MULTISITE",
"USE_UI",
"UI_HOST",
"AUTO_LETS_ENCRYPT",
"USE_LETS_ENCRYPT_STAGING",
"EMAIL_LETS_ENCRYPT",
"USE_CUSTOM_SSL",
"CUSTOM_SSL_CERT",
"CUSTOM_SSL_KEY",
),
)
admin_user = DB.get_ui_user()
@ -42,7 +53,19 @@ def setup_page():
required_keys = []
if not ui_reverse_proxy:
required_keys.extend(["server_name", "ui_host", "ui_url", "auto_lets_encrypt", "lets_encrypt_staging", "email_lets_encrypt"])
required_keys.extend(
[
"server_name",
"ui_host",
"ui_url",
"auto_lets_encrypt",
"lets_encrypt_staging",
"email_lets_encrypt",
"use_custom_ssl",
"custom_ssl_cert",
"custom_ssl_key",
]
)
if not admin_user:
required_keys.extend(
["admin_username", "admin_email", "admin_password", "admin_password_check"]
@ -104,9 +127,12 @@ def setup_page():
DATA["RELOADING"] = True
DATA["LAST_RELOAD"] = time()
config = {
base_config = {
"SERVER_NAME": request.form["server_name"],
"USE_TEMPLATE": "ui",
}
config = {
"USE_REVERSE_PROXY": "yes",
"REVERSE_PROXY_HOST": request.form["ui_host"],
"REVERSE_PROXY_URL": request.form["ui_url"] or "/",
@ -116,6 +142,22 @@ def setup_page():
if request.form.get("auto_lets_encrypt", "no") == "yes":
config["AUTO_LETS_ENCRYPT"] = "yes"
elif request.form.get("use_custom_ssl", "no") == "yes":
if not all(
[
bool(request.form.get("custom_ssl_cert", "")),
bool(request.form.get("custom_ssl_key", "")),
]
):
return handle_error("When using a custom SSL certificate, you must set both the certificate and the key.", "setup")
config.update(
{
"USE_CUSTOM_SSL": "yes",
"CUSTOM_SSL_CERT": request.form.get("custom_ssl_cert", ""),
"CUSTOM_SSL_KEY": request.form.get("custom_ssl_key", ""),
}
)
else:
config.update(
{
@ -128,18 +170,22 @@ def setup_page():
if not config.get("MULTISITE", "no") == "yes":
BW_CONFIG.edit_global_conf({"MULTISITE": "yes"}, check_changes=False)
operation, error = BW_CONFIG.new_service(base_config, override_method="wizard", check_changes=False)
if error:
return handle_error(f"Couldn't create the new service: {operation}", "setup", False, "error")
# deepcode ignore MissingAPI: We don't need to check to wait for the thread to finish
Thread(
target=manage_bunkerweb,
name="Reloading instances",
args=("services", config, request.form["server_name"], request.form["server_name"]),
kwargs={"operation": "new", "threaded": True, "override_method": "wizard"},
args=("services", config | base_config, request.form["server_name"], request.form["server_name"]),
kwargs={"operation": "edit", "threaded": True},
).start()
return Response(status=200)
session["tmp_totp_secret"] = TOTP.generate_totp_secret()
totp_qr_image = TOTP.generate_qrcode(current_user.get_id(), session["tmp_totp_secret"])
# session["tmp_totp_secret"] = TOTP.generate_totp_secret() # TODO: uncomment when TOTP is implemented in setup wizard
# totp_qr_image = TOTP.generate_qrcode(current_user.get_id(), session["tmp_totp_secret"])
return render_template(
"setup.html",
@ -151,8 +197,11 @@ def setup_page():
auto_lets_encrypt=db_config.get("AUTO_LETS_ENCRYPT", getenv("AUTO_LETS_ENCRYPT", "no")),
lets_encrypt_staging=db_config.get("USE_LETS_ENCRYPT_STAGING", getenv("USE_LETS_ENCRYPT_STAGING", "no")),
email_lets_encrypt=db_config.get("EMAIL_LETS_ENCRYPT", getenv("EMAIL_LETS_ENCRYPT", "")),
totp_qr_image=totp_qr_image,
totp_secret=TOTP.get_totp_pretty_key(session.get("tmp_totp_secret", "")),
use_custom_ssl=db_config.get("USE_CUSTOM_SSL", getenv("USE_CUSTOM_SSL", "no")),
custom_ssl_cert=db_config.get("CUSTOM_SSL_CERT", getenv("CUSTOM_SSL_CERT", "")),
custom_ssl_key=db_config.get("CUSTOM_SSL_KEY", getenv("CUSTOM_SSL_KEY", "")),
# totp_qr_image=totp_qr_image,
# totp_secret=TOTP.get_totp_pretty_key(session.get("tmp_totp_secret", "")),
)

View file

@ -404,6 +404,43 @@ $(document).ready(() => {
$confirmPasswordInput.siblings(".invalid-feedback").text("");
}
} else if (!uiReverseProxy && currentStep === 2) {
const $customSslCert = $("#CUSTOM_SSL_CERT");
const $customSslKey = $("#CUSTOM_SSL_KEY");
if (
$("#USE_CUSTOM_SSL").prop("checked") &&
(!$customSslCert.val() || !$customSslKey.val())
) {
if (!$customSslCert.val()) {
$customSslCert.addClass("is-invalid");
let $feedback = $customSslCert.siblings(".invalid-feedback");
if (!$feedback.length) {
const $textSpan = $customSslCert
.parent()
.find("span.input-group-text");
$feedback = $(
'<div class="invalid-feedback">This field is required when using custom SSL.</div>',
).insertAfter($textSpan.length ? $textSpan : $customSslCert);
} else {
$feedback.text("This field is required when using custom SSL.");
}
}
if (!$customSslKey.val()) {
$customSslKey.addClass("is-invalid");
let $feedback = $customSslKey.siblings(".invalid-feedback");
if (!$feedback.length) {
const $textSpan = $customSslKey
.parent()
.find("span.input-group-text");
$feedback = $(
'<div class="invalid-feedback">This field is required when using custom SSL.</div>',
).insertAfter($textSpan.length ? $textSpan : $customSslKey);
} else {
$feedback.text("This field is required when using custom SSL.");
}
}
return;
}
const result = await checkDNS();
const modal = $("#modal-confirm-dns");
const $checkUrl = $("#check-url");
@ -506,6 +543,12 @@ $(document).ready(() => {
$("#LETS_ENCRYPT_STAGING").prop("checked") ? "yes" : "no",
);
formData.append("email_lets_encrypt", $("#EMAIL_LETS_ENCRYPT").val());
formData.append(
"use_custom_ssl",
$("#USE_CUSTOM_SSL").prop("checked") ? "yes" : "no",
);
formData.append("custom_ssl_cert", $("#CUSTOM_SSL_CERT").val());
formData.append("custom_ssl_key", $("#CUSTOM_SSL_KEY").val());
}
// Remove beforeunload event to prevent prompt on form submission

View file

@ -112,7 +112,7 @@
</div>
{% if not ui_user %}
<div class="row pb-0">
<div class="col-12 col-sm-6 pb-3">
<div class="col-12 col-md-6 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
@ -136,7 +136,7 @@
pattern="^.{1,256}$"
required />
</div>
<div class="col-12 col-sm-6 pb-3">
<div class="col-12 col-md-6 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
@ -159,7 +159,7 @@
class="form-control plugin-setting mt-1"
aria-labelledby="label-email" />
</div>
<div class="col-12 col-sm-6 pb-3">
<div class="col-12 col-md-6 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-password"
for="password"
@ -210,7 +210,7 @@
</ul>
</div>
</div>
<div class="col-12 col-sm-6 pb-3">
<div class="col-12 col-md-6 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-confirm_password"
for="confirm_password"
@ -311,7 +311,8 @@
pattern="^.+$"
required />
</div>
<div class="col-12 col-sm-6 pb-6">
<h6 class="mt-2 mb-2 fw-bold">Reverse Proxy</h6>
<div class="col-12 col-md-6 pb-6">
<div class="d-flex justify-content-between align-items-center">
<label id="label-REVERSE_PROXY_HOST"
for="REVERSE_PROXY_HOST"
@ -344,7 +345,7 @@
pattern="^.+$"
required />
</div>
<div class="col-12 col-sm-6 pb-6">
<div class="col-12 col-md-6 pb-6">
<div class="d-flex justify-content-between align-items-center">
<label id="label-username"
for="username"
@ -374,7 +375,8 @@
aria-labelledby="label-REVERSE_PROXY_URL"
pattern="^.*$" />
</div>
<div class="col-6 col-sm-3 pb-3">
<h6 class="mt-2 mb-2 fw-bold">Let's Encrypt</h6>
<div class="col-6 col-md-3 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-AUTO_LETS_ENCRYPT"
for="AUTO_LETS_ENCRYPT"
@ -408,7 +410,7 @@
{% if auto_lets_encrypt == "yes" %}checked{% endif %} />
</div>
</div>
<div class="col-6 col-sm-3 pb-3">
<div class="col-6 col-md-3 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-USE_LETS_ENCRYPT_STAGING"
for="USE_LETS_ENCRYPT_STAGING"
@ -442,7 +444,7 @@
{% if lets_encrypt_staging == "yes" %}checked{% endif %} />
</div>
</div>
<div class="col-sm-6 pb-3">
<div class="col-md-6 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-EMAIL_LETS_ENCRYPT"
for="EMAIL_LETS_ENCRYPT"
@ -474,6 +476,107 @@
aria-labelledby="label-EMAIL_LETS_ENCRYPT"
pattern="^.*$" />
</div>
<h6 class="mt-2 mb-2 fw-bold">Custom certificate</h6>
<div class="col-12 col-md-2 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-USE_CUSTOM_SSL"
for="USE_CUSTOM_SSL"
class="form-label fw-semibold text-truncate">
Use Custom SSL
</label>
{% if use_custom_ssl == "yes" %}
<span class="badge badge-center rounded-pill bg-primary-subtle text-dark d-flex align-items-center justify-content-center p-1 me-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="From global configuration">
<span class="bx bx-globe bx-xs"></span>
</span>
{% endif %}
<div class="d-flex align-items-center">
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="Whether to use a custom SSL certificate">
<span class="bx bx-question-mark bx-xs"></span>
</span>
</div>
</div>
<div class="form-check form-switch mt-1">
<input id="USE_CUSTOM_SSL"
name="USE_CUSTOM_SSL"
class="form-check-input"
type="checkbox"
role="switch"
aria-labelledby="label-USE_CUSTOM_SSL"
{% if use_custom_ssl == "yes" %}checked{% endif %} />
</div>
</div>
<div class="col-6 col-md-5 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-CUSTOM_SSL_CERT"
for="CUSTOM_SSL_CERT"
class="form-label fw-semibold text-truncate">
Custom SSL Certificate
</label>
<div class="d-flex align-items-center">
{% if custom_ssl_cert %}
<span class="badge badge-center rounded-pill bg-primary-subtle text-dark d-flex align-items-center justify-content-center p-1 me-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="From global configuration">
<span class="bx bx-globe bx-xs"></span>
</span>
{% endif %}
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="The custom SSL certificate path">
<span class="bx bx-question-mark bx-xs"></span>
</span>
</div>
</div>
<input id="CUSTOM_SSL_CERT"
name="CUSTOM_SSL_CERT"
type="text"
placeholder="/tmp/cert.pem"
value="{{ custom_ssl_cert }}"
class="form-control plugin-setting mt-1"
aria-labelledby="label-CUSTOM_SSL_CERT"
pattern="^.*$" />
</div>
<div class="col-6 col-md-5 pb-3">
<div class="d-flex justify-content-between align-items-center">
<label id="label-CUSTOM_SSL_KEY"
for="CUSTOM_SSL_KEY"
class="form-label fw-semibold text-truncate">
Custom SSL Key
</label>
<div class="d-flex align-items-center">
{% if custom_ssl_key %}
<span class="badge badge-center rounded-pill bg-primary-subtle text-dark d-flex align-items-center justify-content-center p-1 me-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="From global configuration">
<span class="bx bx-globe bx-xs"></span>
</span>
{% endif %}
<span class="badge rounded-pill bg-secondary-subtle text-dark d-flex align-items-center justify-content-center p-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="The custom SSL key path">
<span class="bx bx-question-mark bx-xs"></span>
</span>
</div>
</div>
<input id="CUSTOM_SSL_KEY"
name="CUSTOM_SSL_KEY"
type="text"
placeholder="/tmp/key.pem"
value="{{ custom_ssl_key }}"
class="form-control plugin-setting mt-1"
aria-labelledby="label-CUSTOM_SSL_KEY"
pattern="^.*$" />
</div>
</div>
{% else %}
<p class="text-center relative w-full p-2 text-primary rounded-lg fw-bold">