mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-07 16:17:22 +02:00
Merge pull request #48 from daluntw/dev-custom-acme
Add custom ACME server feature in backend
This commit is contained in:
commit
dce58343db
@ -8,7 +8,6 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -28,6 +27,11 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CertificateInfoJSON struct {
|
||||||
|
AcmeName string `json:"acme_name"`
|
||||||
|
AcmeUrl string `json:"acme_url"`
|
||||||
|
}
|
||||||
|
|
||||||
// ACMEUser represents a user in the ACME system.
|
// ACMEUser represents a user in the ACME system.
|
||||||
type ACMEUser struct {
|
type ACMEUser struct {
|
||||||
Email string
|
Email string
|
||||||
@ -65,7 +69,7 @@ func NewACME(acmeServer string, port string) *ACMEHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ObtainCert obtains a certificate for the specified domains.
|
// ObtainCert obtains a certificate for the specified domains.
|
||||||
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, ca string) (bool, error) {
|
func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string) (bool, error) {
|
||||||
log.Println("[ACME] Obtaining certificate...")
|
log.Println("[ACME] Obtaining certificate...")
|
||||||
|
|
||||||
// generate private key
|
// generate private key
|
||||||
@ -84,17 +88,23 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
// create config
|
// create config
|
||||||
config := lego.NewConfig(&adminUser)
|
config := lego.NewConfig(&adminUser)
|
||||||
|
|
||||||
// setup who is the issuer and the key type
|
// setup the custom ACME url endpoint.
|
||||||
config.CADirURL = a.DefaultAcmeServer
|
if caUrl != "" {
|
||||||
|
config.CADirURL = caUrl
|
||||||
|
}
|
||||||
|
|
||||||
//Overwrite the CADir URL if set
|
// if not custom ACME url, load it from ca.json
|
||||||
if ca != "" {
|
if caName == "custom" {
|
||||||
caLinkOverwrite, err := loadCAApiServerFromName(ca)
|
log.Println("[INFO] Using Custom ACME " + caUrl + " for CA Directory URL")
|
||||||
|
} else {
|
||||||
|
caLinkOverwrite, err := loadCAApiServerFromName(caName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
config.CADirURL = caLinkOverwrite
|
config.CADirURL = caLinkOverwrite
|
||||||
log.Println("[INFO] Using " + caLinkOverwrite + " for CA Directory URL")
|
log.Println("[INFO] Using " + caLinkOverwrite + " for CA Directory URL")
|
||||||
} else {
|
} else {
|
||||||
return false, errors.New("CA " + ca + " is not supported. Please contribute to the source code and add this CA's directory link.")
|
// (caName == "" || caUrl == "") will use default acme
|
||||||
|
config.CADirURL = a.DefaultAcmeServer
|
||||||
|
log.Println("[INFO] Using Default ACME " + a.DefaultAcmeServer + " for CA Directory URL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +155,24 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save certificate's ACME info for renew usage
|
||||||
|
certInfo := &CertificateInfoJSON{
|
||||||
|
AcmeName: caName,
|
||||||
|
AcmeUrl: caUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
certInfoBytes, err := json.Marshal(certInfo)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile("./conf/certs/"+certificateName+".json", certInfoBytes, 0777)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,14 +278,24 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var caUrl string
|
||||||
|
|
||||||
ca, err := utils.PostPara(r, "ca")
|
ca, err := utils.PostPara(r, "ca")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("CA not set. Using default (Let's Encrypt)")
|
log.Println("CA not set. Using default")
|
||||||
ca = "Let's Encrypt"
|
ca, caUrl = "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ca == "custom" {
|
||||||
|
caUrl, err = utils.PostPara(r, "ca_url")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Custom CA set but no URL provide, Using default")
|
||||||
|
ca, caUrl = "", ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
domains := strings.Split(domainPara, ",")
|
domains := strings.Split(domainPara, ",")
|
||||||
result, err := a.ObtainCert(domains, filename, email, ca)
|
result, err := a.ObtainCert(domains, filename, email, ca, caUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||||
return
|
return
|
||||||
@ -285,4 +323,20 @@ func IsPortInUse(port int) bool {
|
|||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
return false // Port is not in use
|
return false // Port is not in use
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
|
||||||
|
|
||||||
|
certInfoBytes, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
certInfo := &CertificateInfoJSON{}
|
||||||
|
if err = json.Unmarshal(certInfoBytes, certInfo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return certInfo, nil
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package acme
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
@ -355,7 +356,16 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
|||||||
log.Println("Renewing " + expiredCert.Filepath + " (Might take a few minutes)")
|
log.Println("Renewing " + expiredCert.Filepath + " (Might take a few minutes)")
|
||||||
fileName := filepath.Base(expiredCert.Filepath)
|
fileName := filepath.Base(expiredCert.Filepath)
|
||||||
certName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
|
certName := fileName[:len(fileName)-len(filepath.Ext(fileName))]
|
||||||
_, err := a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, expiredCert.CA)
|
|
||||||
|
// Load certificate info for ACME detail
|
||||||
|
certInfoFilename := fmt.Sprintf("%s/%s.json", filepath.Dir(expiredCert.Filepath), certName)
|
||||||
|
certInfo, err := loadCertInfoJSON(certInfoFilename)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Renew %s certificate error, can't get the ACME detail for cert: %v, using default ACME", certName, err)
|
||||||
|
certInfo = &CertificateInfoJSON{}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = a.AcmeHandler.ObtainCert(expiredCert.Domains, certName, a.RenewerConfig.Email, certInfo.AcmeName, certInfo.AcmeUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
|
@ -109,10 +109,15 @@
|
|||||||
<div class="item" data-value="Let's Encrypt">Let's Encrypt</div>
|
<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="Buypass">Buypass</div>
|
||||||
<div class="item" data-value="ZeroSSL">ZeroSSL</div>
|
<div class="item" data-value="ZeroSSL">ZeroSSL</div>
|
||||||
|
<div class="item" data-value="Custom ACME Server">Custom ACME Server</div>
|
||||||
<!-- <div class="item" data-value="Google">Google</div> -->
|
<!-- <div class="item" data-value="Google">Google</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field" id="customca" style="display:none;">
|
||||||
|
<label>ACME Server URL</label>
|
||||||
|
<input id="caurl" type="text" placeholder="https://example.com/acme/dictionary">
|
||||||
|
</div>
|
||||||
<button id="obtainButton" class="ui basic button" type="submit"><i class="yellow refresh icon"></i> Renew Certificate</button>
|
<button id="obtainButton" class="ui basic button" type="submit"><i class="yellow refresh icon"></i> Renew Certificate</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
@ -295,6 +300,14 @@
|
|||||||
obtainCertificate();
|
obtainCertificate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("input[name=ca]").on('change', function() {
|
||||||
|
if(this.value == "Custom ACME Server") {
|
||||||
|
$("#customca").show();
|
||||||
|
} else {
|
||||||
|
$("#customca").hide();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Obtain certificate from API
|
// Obtain certificate from API
|
||||||
function obtainCertificate() {
|
function obtainCertificate() {
|
||||||
var domains = $("#domainsInput").val();
|
var domains = $("#domainsInput").val();
|
||||||
@ -316,7 +329,14 @@
|
|||||||
parent.msgbox("Filename cannot be empty for certs containing multiple domains.")
|
parent.msgbox("Filename cannot be empty for certs containing multiple domains.")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ca = $("#ca").dropdown("get value");
|
var ca = $("#ca").dropdown("get value");
|
||||||
|
var ca_url = "";
|
||||||
|
if (ca == "Custom ACME Server") {
|
||||||
|
ca = "custom";
|
||||||
|
ca_url = $("#caurl").val();
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/api/acme/obtainCert",
|
url: "/api/acme/obtainCert",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@ -325,6 +345,7 @@
|
|||||||
filename: filename,
|
filename: filename,
|
||||||
email: email,
|
email: email,
|
||||||
ca: ca,
|
ca: ca,
|
||||||
|
ca_url: ca_url,
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user