Explorar o código

auto update script executed

Toby Chui hai 1 ano
pai
achega
225b0b2d2f
Modificáronse 5 ficheiros con 256 adicións e 98 borrados
  1. 1 0
      api.go
  2. 41 0
      cert.go
  3. 17 0
      mod/acme/ca.json
  4. 18 0
      mod/acme/utils.go
  5. 179 98
      web/snippet/acme.html

+ 1 - 0
api.go

@@ -59,6 +59,7 @@ func initAPIs() {
 	authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
 	authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
 	authRouter.HandleFunc("/api/cert/list", handleListCertificate)
+	authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
 	authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
 	authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
 

+ 41 - 0
cert.go

@@ -10,6 +10,7 @@ import (
 	"net/http"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"imuslab.com/zoraxy/mod/utils"
 )
@@ -99,6 +100,46 @@ func handleListCertificate(w http.ResponseWriter, r *http.Request) {
 
 }
 
+// List all certificates and map all their domains to the cert filename
+func handleListDomains(w http.ResponseWriter, r *http.Request) {
+	filenames, err := os.ReadDir("./certs/")
+
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	certnameToDomainMap := map[string]string{}
+	for _, filename := range filenames {
+		if filename.IsDir() {
+			continue
+		}
+		certFilepath := filepath.Join("./certs/", filename.Name())
+
+		certBtyes, err := os.ReadFile(certFilepath)
+		if err != nil {
+			// Unable to load this file
+			continue
+		} else {
+			// Cert loaded. Check its expiry time
+			block, _ := pem.Decode(certBtyes)
+			if block != nil {
+				cert, err := x509.ParseCertificate(block.Bytes)
+				if err == nil {
+					certname := strings.TrimSuffix(filepath.Base(certFilepath), filepath.Ext(certFilepath))
+					for _, dnsName := range cert.DNSNames {
+						certnameToDomainMap[dnsName] = certname
+					}
+					certnameToDomainMap[cert.Subject.CommonName] = certname
+				}
+			}
+		}
+	}
+
+	js, _ := json.Marshal(certnameToDomainMap)
+	utils.SendJSONResponse(w, string(js))
+}
+
 // Handle front-end toggling TLS mode
 func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
 	currentTlsSetting := false

+ 17 - 0
mod/acme/ca.json

@@ -0,0 +1,17 @@
+{
+    "production": {
+        "Let's Encrypt": "https://acme-v02.api.letsencrypt.org/directory",
+        "Buypass": "https://api.buypass.com/acme/directory",
+        "ZeroSSL": "https://acme.zerossl.com/v2/DV90",
+        "Google": "https://dv.acme-v02.api.pki.goog/directory"
+    },
+    "test":{
+        "Let's Encrypt": "https://acme-staging-v02.api.letsencrypt.org/directory",
+        "Buypass": "https://api.test4.buypass.no/acme/directory",
+        "Google": "https://dv.acme-v02.test-api.pki.goog/directory"
+    }
+    
+    
+  }
+  
+  

+ 18 - 0
mod/acme/utils.go

@@ -5,6 +5,7 @@ import (
 	"encoding/pem"
 	"fmt"
 	"io/ioutil"
+	"time"
 )
 
 // Get the issuer name from pem file
@@ -32,3 +33,20 @@ func ExtractIssuerNameFromPEM(pemFilePath string) (string, error) {
 
 	return issuer, nil
 }
+
+// Check if a cert is expired
+func CertIsExpired(certBtyes []byte) bool {
+	block, _ := pem.Decode(certBtyes)
+	if block != nil {
+		cert, err := x509.ParseCertificate(block.Bytes)
+		if err == nil {
+			elapsed := time.Since(cert.NotAfter)
+			if elapsed > 0 {
+				// if it is expired then add it in
+				// make sure it's uniqueless
+				return true
+			}
+		}
+	}
+	return false
+}

+ 179 - 98
web/snippet/acme.html

@@ -5,6 +5,21 @@
       <link rel="stylesheet" href="../script/semantic/semantic.min.css">
       <script src="../script/jquery-3.6.0.min.js"></script>
       <script src="../script/semantic/semantic.min.js"></script>
+      <style>
+        .disabled.table{
+          opacity: 0.5;
+          pointer-events: none;
+          user-select: none;
+        }
+
+        .expiredDomain{
+          color: rgb(238, 31, 31);
+        }
+
+        .validDomain{
+          color: rgb(49, 192, 113);
+        }
+      </style>
   </head>
   <body>
   <br>
@@ -12,131 +27,197 @@
     <div class="ui header">
         <div class="content">
             Certificates Auto Renew Settings
-            <div class="sub header">Fetch and renew your certificates with ACME</div>
+            <div class="sub header">Fetch and renew your certificates with Automated Certificate Management Environment (ACME) protocol</div>
         </div>
-    </div>
-    <div class="ui message">
-      
     </div>
     <div class="ui basic segment">
       <div class="ui toggle checkbox">
-        <input type="checkbox" name="public">
-        <label>Enable Domain Auto Renew</label>
+        <input type="checkbox" id="enableCertAutoRenew" checked>
+        <label>Enable Certificate Auto Renew</label>
       </div>
     </div>
-    <div class="ui segment">
-      <p>This is an example of using ACME.</p>
-      <button id="fetchButton" class="ui primary button">Fetch Expired Domains</button>
+    <div class="ui yellow message">
+      Certificate Renew only works on the certification authority (CA) supported by Zoraxy. Check Zoraxy wiki for more information on supported list of CAs.
     </div>
-
-    <div id="domainTable" class="ui segment hidden">
-      <h2 class="ui header">Expired Domains</h2>
-      <table id="domainList" class="ui celled table">
-        <thead>
-          <tr>
-            <th>Domain</th>
-          </tr>
-        </thead>
-        <tbody></tbody>
-      </table>
-      <div class="ui segment">
-        <h2 class="ui header">Obtain Certificate</h2>
-        <div class="ui form">
-          <div class="field">
-            <label>Domains</label>
-            <input id="domainsInput" type="text" placeholder="Enter domains separated by commas (e.g. r5desktop.alanyeung.co,alanyeung.co)">
+    <div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
+      <div class="ui accordion advanceSettings">
+          <div class="title">
+            <i class="dropdown icon"></i>
+              Advance Renew Policy
           </div>
-          <div class="field">
-            <label>Filename</label>
-            <input id="filenameInput" type="text" placeholder="Enter filename">
+          <div class="content">
+              <p>Renew all certificates with ACME supported CAs</p>
+              <div class="ui toggle checkbox">
+                <input type="checkbox" id="renewAllSupported" onchange="setAutoRenewIfCASupportMode(this.checked);" checked>
+                <label>Auto renew if CA is supported</label>
+              </div>
+              <div class="ui horizontal divider"> OR </div>
+              <p>Select the certificates to automatic renew in the list below</p>
+              <table id="domainCertFileTable" class="ui very compact unstackable basic disabled table">
+                <thead>
+                  <tr>
+                    <th>Domain Name</th>
+                    <th>Filename</th>
+                    <th>Auto-Renew</th>
+                  </tr>
+                </thead>
+                <tbody id="domainTableBody"></tbody>
+              </table>
+              <button class="ui basic right floated button"><i class="blue save icon"></i> Save Changes</button>
+              <button class="ui basic right floated button"><i class="yellow refresh icon"></i> Renew All</button>
+              <br><br>
           </div>
-          <button id="obtainButton" class="ui button" type="submit">Obtain Certificate</button>
+      </div>
+  </div>
+  <div class="ui divider"></div>
+  <h3>Manual Renew</h3>
+  <p>Pick a certificate below to force renew</p>
+  <div class="ui form">
+    <div class="field">
+      <label>Domains</label>
+      <input id="domainsInput" type="text" placeholder="example.com">
+      <small>If you have more than one domain in a single certificate, enter the domains separated by commas (e.g. test.example.com,example.com)</small>
+    </div>
+    <div class="field">
+      <label>Filename</label>
+      <input id="filenameInput" type="text" placeholder="Enter filename">
+    </div>
+    <div class="field">
+      <label>Certificate Authority (CA)</label>
+      <div class="ui selection dropdown">
+        <input type="hidden" name="ca">
+        <i class="dropdown icon"></i>
+        <div class="default text">Let's Encrypt</div>
+        <div class="menu">
+          <div class="item" data-value="Let's Encrypt">Let's Encrypt</div>
+          <div class="item" data-value="Buypass">Buypass</div>
+          <div class="item" data-value="ZeroSSL">ZeroSSL</div>
+          <div class="item" data-value="Google">Google</div>
         </div>
       </div>
     </div>
+    <button id="obtainButton" class="ui basic button" type="submit"><i class="yellow refresh icon"></i> Renew Certificate</button>
+  </div>
+
+  <button class="ui basic button"  style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Cancel</button>
+  <br><br><br><br>
   </div>
 
-  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
-  <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js"></script>
   <script>
-    $(document).ready(function() {
-      // Button click event handler
-      $("#fetchButton").click(function() {
-        fetchExpiredDomains();
-      });
+    let expiredDomains = [];
+    $(".accordion").accordion();
+    $(".dropdown").dropdown();
 
-      // Fetch expired domains from API
-      function fetchExpiredDomains() {
-        $.ajax({
-          url: "/api/acme/listExpiredDomains",
-          method: "GET",
-          success: function(response) {
-            // Render domain table
-            renderDomainTable(response.domain);
-          },
-          error: function(error) {
-            console.log("Failed to fetch expired domains:", error);
-          }
-        });
+    function setAutoRenewIfCASupportMode(useAutoMode = true){
+      if (useAutoMode){
+        $("#domainCertFileTable").addClass("disabled");
+      }else{
+        $("#domainCertFileTable").removeClass("disabled");
       }
+    }
+
+    //Render the domains table that exists in this zoraxy host
+    function renderDomainTable(domains) {
+      // Get the table body element
+      var tableBody = $('#domainTableBody');
+      
+      // Clear the table body
+      tableBody.empty();
+      
+      // Iterate over the domain names
+      var counter = 0;
+      for (const [domain, srcfile] of Object.entries(domains)) {
 
-      // Render domain table with data
-      function renderDomainTable(domains) {
-        var tableBody = $("#domainList tbody");
-        tableBody.empty();
+        // Create a table row
+        var row = $('<tr>');
+        
+        // Create the domain name cell
+        var domainClass = "validDomain";
+        if (expiredDomains.includes(domain)){
+          domainClass = "expiredDomain";
+        }
+        var domainCell = $('<td class="' + domainClass  +'">').text(domain);
+        row.append(domainCell);
 
-        $.each(domains, function(index, domain) {
-          var row = $("<tr>").appendTo(tableBody);
-          $("<td>").text(domain).appendTo(row);
-        });
+        var srcFileCell = $('<td>').text(srcfile);
+        row.append(srcFileCell);
+        
+        // Create the auto-renew checkbox cell
+        var checkboxCell = $(`<td domain="${domain}" srcfile="${srcfile}">`);
+        var checkbox = $('<input>').attr('type', 'checkbox');
+        checkboxCell.append(checkbox);
+        row.append(checkboxCell);
+        
+        // Add the row to the table body
+        tableBody.append(row);
 
-        // Show the domain table
-        $("#domainTable").removeClass("hidden");
+        counter++;
       }
+    }
+
+    //Initiate domain table. If you needs to update the expired domain as well
+    //call from initDomainFileList() instead
+    function initDomainTable(){
+      $.get("/api/cert/listdomains", function(data){
+        if (data.error != undefined){
+          parent.msgbox(data.error, false);
+        }else{
+          renderDomainTable(data);
+        }
+      })
+    }
 
-      // Button click event handler for obtaining certificate
-      $("#obtainButton").click(function() {
-        obtainCertificate();
+    function initDomainFileList() {
+      $.ajax({
+        url: "/api/acme/listExpiredDomains",
+        method: "GET",
+        success: function(response) {
+          // Render domain table
+          expiredDomains = response.domain;
+          initDomainTable();
+          //renderDomainTable(response.domain);
+        },
+        error: function(error) {
+          console.log("Failed to fetch expired domains:", error);
+        }
       });
+    }
+    initDomainFileList();
+
+    // Button click event handler for obtaining certificate
+    $("#obtainButton").click(function() {
+      obtainCertificate();
+    });
 
-      // Obtain certificate from API
-      function obtainCertificate() {
-        var domains = $("#domainsInput").val();
-        var filename = $("#filenameInput").val();
+    // Obtain certificate from API
+    function obtainCertificate() {
+      var domains = $("#domainsInput").val();
+      var filename = $("#filenameInput").val();
 
-        $.ajax({
-          url: "/api/acme/obtainCert",
-          method: "GET",
-          data: {
-            domains: domains,
-            filename: filename
-          },
-          success: function(response) {
-            if (response.error) {
-              console.log("Error:", response.error);
-              // Show error message
-              showMessage(response.error);
-            } else {
-              console.log("Certificate obtained successfully");
-              // Show success message
-              showMessage("Certificate obtained successfully");
-            }
-          },
-          error: function(error) {
-            console.log("Failed to obtain certificate:", error);
+      $.ajax({
+        url: "/api/acme/obtainCert",
+        method: "GET",
+        data: {
+          domains: domains,
+          filename: filename
+        },
+        success: function(response) {
+          if (response.error) {
+            console.log("Error:", response.error);
+            // Show error message
+            parent.msgbox(response.error, false, 12000);
+          } else {
+            console.log("Certificate renewed successfully");
+            // Show success message
+            parent.msgbox("Certificate renewed successfully");
           }
-        });
-      }
+        },
+        error: function(error) {
+          console.log("Failed to renewed certificate:", error);
+        }
+      });
+    }
 
-      // Show message in a popup
-      function showMessage(message) {
-        $("<div>").addClass("ui message").text(message).appendTo("body").modal({
-          onHide: function() {
-            $(this).remove();
-          }
-        }).modal("show");
-      }
-    });
   </script>
 </body>
 </html>