From 3ca015d279f109fad7af6722d2d06782b0cd6315 Mon Sep 17 00:00:00 2001 From: Aidaho Date: Wed, 16 Apr 2025 18:41:21 +0300 Subject: [PATCH] v8.1.8: Add SSL certificate management feature Introduced support for uploading, viewing, and deleting SSL certificates via a dedicated web interface. Updated routing, templates, and backend logic to handle certificate types (pem, key, crt) and improved integration with SSL-related UI components. --- app/create_db.py | 2 +- app/modules/config/add.py | 18 ++- app/modules/roxywi/class_models.py | 1 + app/routes/service/routes.py | 7 + app/static/js/{le.js => ssl.js} | 158 +++++++++++++++++++++++ app/templates/config.html | 57 ++++---- app/templates/ha_cluster.html | 1 + app/templates/include/add/add_proxy.html | 6 +- app/templates/include/main_menu.html | 6 +- app/templates/ssl.html | 79 ++++++++++++ 10 files changed, 298 insertions(+), 37 deletions(-) rename app/static/js/{le.js => ssl.js} (58%) create mode 100644 app/templates/ssl.html diff --git a/app/create_db.py b/app/create_db.py index cbc7cc00..e37011a9 100644 --- a/app/create_db.py +++ b/app/create_db.py @@ -723,7 +723,7 @@ def update_db_v_8_1_6(): def update_ver(): try: - Version.update(version='8.1.7').execute() + Version.update(version='8.1.8').execute() except Exception: print('Cannot update version') diff --git a/app/modules/config/add.py b/app/modules/config/add.py index 9836f4a6..d4016502 100644 --- a/app/modules/config/add.py +++ b/app/modules/config/add.py @@ -339,9 +339,17 @@ def get_ssl_raw_cert(server_ip: str, cert_id: str) -> str: return f'error: Cannot connect to the server {e}' -def get_ssl_certs(server_ip: str) -> str: +def get_ssl_certs(server_ip: str, cert_type: str = None) -> str: cert_path = sql.get_setting('cert_path') - command = f"sudo ls -1t {cert_path} |grep -E 'pem|crt|key'" + if cert_type == 'pem': + cert_type = 'pem' + elif cert_type == 'crt': + cert_type = 'crt' + elif cert_type == 'key': + cert_type = 'key' + else: + cert_type = 'pem|crt|key' + command = f"sudo ls -1t {cert_path} |grep -E '{cert_type}'" try: return server_mod.ssh_command(server_ip, command) except Exception as e: @@ -358,7 +366,7 @@ def del_ssl_cert(server_ip: str, cert_id: str) -> str: return f'error: Cannot delete the certificate {e}' -def upload_ssl_cert(server_ip: str, ssl_name: str, ssl_cont: str) -> list[str]: +def upload_ssl_cert(server_ip: str, ssl_name: str, ssl_cont: str, ssl_type: str = 'pem') -> list[str]: cert_path = sql.get_setting('cert_path') tmp_path = sql.get_setting('tmp_config_path') output = [] @@ -367,8 +375,8 @@ def upload_ssl_cert(server_ip: str, ssl_name: str, ssl_cont: str) -> list[str]: if ssl_name is None: raise Exception('Please enter a desired name') else: - name = f"{ssl_name}.pem" - path_to_file = f"{tmp_path}/{ssl_name}.pem" + name = f"{ssl_name}.{ssl_type}" + path_to_file = f"{tmp_path}/{ssl_name}.{ssl_type}" try: with open(path_to_file, "w") as ssl_cert: diff --git a/app/modules/roxywi/class_models.py b/app/modules/roxywi/class_models.py index f1eac83e..3774e6f4 100644 --- a/app/modules/roxywi/class_models.py +++ b/app/modules/roxywi/class_models.py @@ -304,6 +304,7 @@ class SSLCertUploadRequest(BaseModel): server_ip: Union[IPvAnyAddress, DomainName] name: EscapedString cert: EscapedString + cert_type: Literal['key', 'crt', 'pem'] = 'pem' class SavedServerRequest(BaseModel): diff --git a/app/routes/service/routes.py b/app/routes/service/routes.py index f587ef92..42718f3a 100644 --- a/app/routes/service/routes.py +++ b/app/routes/service/routes.py @@ -136,6 +136,13 @@ def last_edit(service: str, server_ip: Union[IPvAnyAddress, DomainName]): return service_common.get_overview_last_edit(str(server_ip), service) +@bp.route('//ssl') +@check_services +@get_user_params() +def ssl_service(service): + return render_template('ssl.html', lang=g.user_params['lang']) + + @bp.route('/cpu-ram-metrics////') @get_user_params() @validate() diff --git a/app/static/js/le.js b/app/static/js/ssl.js similarity index 58% rename from app/static/js/le.js rename to app/static/js/ssl.js index 91d557e5..5fbdccd6 100644 --- a/app/static/js/le.js +++ b/app/static/js/ssl.js @@ -1,3 +1,160 @@ +$( function() { + $("#ssl_key_or_crt_upload").click(function () { + if (!checkIsServerFiled('#serv6')) return false; + if (!checkIsServerFiled('#ssl_key_name', 'Enter the Certificate name')) return false; + if (!checkIsServerFiled('#ssl_key_or_crt', 'Paste the contents of the certificate file')) return false; + let jsonData = { + server_ip: $('#serv6').val(), + cert_type: $('#new-cert-file-type').val(), + cert: $('#ssl_key_or_crt').val(), + name: $('#ssl_key_name').val() + } + $.ajax({ + url: "/add/cert/add", + data: JSON.stringify(jsonData), + contentType: "application/json; charset=utf-8", + type: "POST", + success: function (data) { + if (data.error === 'failed') { + toastr.error(data.error); + } else { + for (let i = 0; i < data.length; i++) { + if (data[i]) { + if (data[i].indexOf('error: ') != '-1' || data[i].indexOf('Errno') != '-1') { + toastr.error(data[i]); + } else { + toastr.success(data[i]); + } + } + } + } + } + }); + }); + $('#ssl_key_view').click(function () { + if (!checkIsServerFiled('#serv5')) return false; + $.ajax({ + url: "/add/certs/" + $('#serv5').val(), + success: function (data) { + if (data.indexOf('error:') != '-1') { + toastr.error(data); + } else { + let i; + let new_data = ""; + data = data.split("\n"); + let j = 1 + for (i = 0; i < data.length; i++) { + data[i] = data[i].replace(/\s+/g, ' '); + if (data[i] != '') { + if (j % 2) { + if (j != 0) { + new_data += '' + } + new_data += '' + } else { + new_data += '' + + } + j += 1 + new_data += ' ' + data[i] + ' ' + } + } + $("#ajax-show-ssl").html(new_data); + } + } + }); + }); +}); +function view_ssl(id) { + let raw_word = translate_div.attr('data-raw'); + if(!checkIsServerFiled('#serv5')) return false; + $.ajax( { + url: "/add/cert/" + $('#serv5').val() + '/' + id, + success: function( data ) { + if (data.indexOf('error: ') != '-1') { + toastr.error(data); + } else { + $('#dialog-confirm-body').text(data); + $( "#dialog-confirm-cert" ).dialog({ + resizable: false, + height: "auto", + width: 670, + modal: true, + title: "Certificate from "+$('#serv5').val()+", name: "+id, + buttons: [{ + text: cancel_word, + click: function () { + $(this).dialog("close"); + } + }, { + text: raw_word, + click: function () { + showRawSSL(id); + } + }, { + text: delete_word, + click: function () { + $(this).dialog("close"); + confirmDeleting("SSL cert", id, $(this), ""); + } + }] + }); + } + } + } ); +} +function showRawSSL(id) { + $.ajax({ + url: "/add/cert/get/raw/" + $('#serv5').val() + "/" + id, + success: function (data) { + if (data.indexOf('error: ') != '-1') { + toastr.error(data); + } else { + $('#dialog-confirm-body').text(data); + $("#dialog-confirm-cert").dialog({ + resizable: false, + height: "auto", + width: 670, + modal: true, + title: "Certificate from " + $('#serv5').val() + ", name: " + id, + buttons: [{ + text: cancel_word, + click: function () { + $(this).dialog("close"); + } + }, { + text: "Human readable", + click: function () { + view_ssl(id); + } + }, { + text: delete_word, + click: function () { + $(this).dialog("close"); + confirmDeleting("SSL cert", id, $(this), ""); + } + }] + }); + } + } + }); +} +function deleteSsl(id) { + if (!checkIsServerFiled('#serv5')) return false; + $.ajax({ + url: "/add/cert/" + $("#serv5").val() + "/" + id, + type: "DELETE", + success: function (data) { + if (data.indexOf('error: ') != '-1') { + toastr.error(data); + } else { + toastr.clear(); + toastr.success('SSL cert ' + id + ' has been deleted'); + $("#ssl_key_view").trigger("click"); + } + } + }); +} let provides = {'standalone': "Stand alone", 'route53': 'Route53', 'linode': 'Linode', 'cloudflare': 'Cloudflare', 'digitalocean': 'Digitalocean'}; $( function() { let typeSelect = $( "#new-le-type" ); @@ -220,3 +377,4 @@ function showLe(data) { ]) $('#le_table_body').append(le_tag); } + diff --git a/app/templates/config.html b/app/templates/config.html index f1014b83..45d24c18 100644 --- a/app/templates/config.html +++ b/app/templates/config.html @@ -98,6 +98,9 @@ showCompareConfigs(); } if (cur_url[6] === 'show') { + if (cur_url[4] === 'nginx') { + showConfigFiles(false, cur_url[7]); + } showConfig(); } if (cur_url[6] === 'show-files') { @@ -116,21 +119,21 @@ } if (cur_url[4] === 'haproxy' && cur_url[6] === 'edit') { var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("config_text_area"), - { - mode: "haproxy", - lineNumbers: true, - lineWrapping: true, - autocapitalize: true, - autocorrect: true, - spellcheck: true, - autoCloseBrackets: true, - keyMap: "sublime", - matchBrackets: true, - foldGutter: true, - showCursorWhenSelecting: true, - gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "breakpoints"], - highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true} - }); + { + mode: "haproxy", + lineNumbers: true, + lineWrapping: true, + autocapitalize: true, + autocorrect: true, + spellcheck: true, + autoCloseBrackets: true, + keyMap: "sublime", + matchBrackets: true, + foldGutter: true, + showCursorWhenSelecting: true, + gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "breakpoints"], + highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true} + }); } else if (cur_url[6] === 'edit') { var myCodeMirror = CodeMirror.fromTextArea(document.getElementById("config_text_area"), { @@ -149,15 +152,17 @@ highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true} }); } - myCodeMirror.on("gutterClick", function(cm, n) { - let info = cm.lineInfo(n); - cm.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : makeMarker()); - }); - myCodeMirror.on("beforeChange", function (cm, change) { - $(window).bind('beforeunload', function(){ - return 'Are you sure you want to leave?'; - }); - }); + if (cur_url[6] === 'edit') { + myCodeMirror.on("gutterClick", function (cm, n) { + let info = cm.lineInfo(n); + cm.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : makeMarker()); + }); + myCodeMirror.on("beforeChange", function (cm, change) { + $(window).bind('beforeunload', function () { + return 'Are you sure you want to leave?'; + }); + }); + } function makeMarker() { var marker = document.createElement("div"); marker.style.color = "#822"; @@ -172,7 +177,9 @@ } {% endif %} diff --git a/app/templates/include/add/add_proxy.html b/app/templates/include/add/add_proxy.html index cb9c6246..37e86f41 100644 --- a/app/templates/include/add/add_proxy.html +++ b/app/templates/include/add/add_proxy.html @@ -40,7 +40,7 @@ {{lang.words.create|title()}} SSL {{lang.words.listener|title()}}
- {{lang.add_page.desc.create_ssl_proxy}} {{lang.words.uploaded}} {{lang.words.w_a}} PEM {{lang.words.cert}} + {{lang.add_page.desc.create_ssl_proxy}} {{lang.words.uploaded}} {{lang.words.w_a}} PEM {{lang.words.cert}}
@@ -90,7 +90,7 @@ {{lang.words.create|title()}} SSL {{lang.words.frontend|title()}}
- {{lang.add_page.desc.create_ssl_front}} {{lang.words.uploaded}} {{lang.words.w_a}} PEM {{lang.words.cert}} + {{lang.add_page.desc.create_ssl_front}} {{lang.words.uploaded}} {{lang.words.w_a}} PEM {{lang.words.cert}}
@@ -140,7 +140,7 @@ {{lang.words.create|title()}} SSL {{lang.words.backend|title()}}
- {{lang.add_page.desc.create_ssl_backend}} {{lang.words.uploaded}} {{lang.words.w_a}} PEM {{lang.words.cert}} + {{lang.add_page.desc.create_ssl_backend}} {{lang.words.uploaded}} {{lang.words.w_a}} PEM {{lang.words.cert}}
diff --git a/app/templates/include/main_menu.html b/app/templates/include/main_menu.html index 94ac0adf..038ed584 100644 --- a/app/templates/include/main_menu.html +++ b/app/templates/include/main_menu.html @@ -39,7 +39,7 @@ {% if g.user_params['role'] <= 3 %}
  • {{lang.menu_links.add_proxy.link}}
  • {{lang.menu_links.versions.link}}
  • -
  • {{lang.menu_links.ssl.link}}
  • +
  • {{lang.menu_links.ssl.link}}
  • {{lang.menu_links.lists.link}}
  • WAF
  • {% endif %} @@ -61,7 +61,7 @@ {% if g.user_params['role'] <= 3 %}
  • {{lang.menu_links.add_proxy.link}}
  • {{lang.menu_links.versions.link}}
  • -
  • {{lang.menu_links.ssl.link}}
  • +
  • {{lang.menu_links.ssl.link}}
  • WAF
  • {% endif %} @@ -78,7 +78,7 @@
  • {{lang.menu_links.metrics.link}}
  • {% if g.user_params['role'] <= 3 %}
  • {{lang.menu_links.versions.link}}
  • -
  • {{lang.menu_links.ssl.link}}
  • +
  • {{lang.menu_links.ssl.link}}
  • {% endif %} diff --git a/app/templates/ssl.html b/app/templates/ssl.html new file mode 100644 index 00000000..42d689d3 --- /dev/null +++ b/app/templates/ssl.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} +{% block title %}{{lang.words.add|title()}} SSL{% endblock %} +{% block h2 %}{{lang.words.add|title()}} SSL {% endblock %} +{% block content %} +{% from 'include/input_macros.html' import input, checkbox, select %} + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    SSL

    {{lang.words.view|title()}} {{lang.words.cert2}} + {{lang.words.uploaded|title()}} {{lang.words.certs}} +
    + {{ select('serv5', values=g.user_params['servers'], is_servers='true', by_id='true') }} + + + +
    {{lang.words.upload|title()}} SSL {{lang.words.certs}} + {{lang.words.cert_name|title()}} + + {{lang.words.file|title()}} {{ lang.words.type }} + + {{lang.add_page.desc.paste_cert}} +
    + {{ select('serv6', values=g.user_params['servers'], is_servers='true') }} + + {{ input('ssl_key_name') }} + + + +

    + +
    + + + + + + + + + + + +

    Let's Encrypt

    {{lang.words.server|title()}}{{lang.words.type|title()}}{{lang.words.domains|title()}}{{lang.words.desc|title()}}
    +
    + {{lang.words.create|title()}} +
    +{% include 'include/del_confirm.html' %} + + +{% endblock %}