mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-07 08:07:20 +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"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@ -28,6 +27,11 @@ import (
|
||||
"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.
|
||||
type ACMEUser struct {
|
||||
Email string
|
||||
@ -65,7 +69,7 @@ func NewACME(acmeServer string, port string) *ACMEHandler {
|
||||
}
|
||||
|
||||
// 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...")
|
||||
|
||||
// generate private key
|
||||
@ -84,17 +88,23 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
// create config
|
||||
config := lego.NewConfig(&adminUser)
|
||||
|
||||
// setup who is the issuer and the key type
|
||||
config.CADirURL = a.DefaultAcmeServer
|
||||
// setup the custom ACME url endpoint.
|
||||
if caUrl != "" {
|
||||
config.CADirURL = caUrl
|
||||
}
|
||||
|
||||
//Overwrite the CADir URL if set
|
||||
if ca != "" {
|
||||
caLinkOverwrite, err := loadCAApiServerFromName(ca)
|
||||
// if not custom ACME url, load it from ca.json
|
||||
if caName == "custom" {
|
||||
log.Println("[INFO] Using Custom ACME " + caUrl + " for CA Directory URL")
|
||||
} else {
|
||||
caLinkOverwrite, err := loadCAApiServerFromName(caName)
|
||||
if err == nil {
|
||||
config.CADirURL = caLinkOverwrite
|
||||
log.Println("[INFO] Using " + caLinkOverwrite + " for CA Directory URL")
|
||||
} 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -250,14 +278,24 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
var caUrl string
|
||||
|
||||
ca, err := utils.PostPara(r, "ca")
|
||||
if err != nil {
|
||||
log.Println("CA not set. Using default (Let's Encrypt)")
|
||||
ca = "Let's Encrypt"
|
||||
log.Println("CA not set. Using default")
|
||||
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, ",")
|
||||
result, err := a.ObtainCert(domains, filename, email, ca)
|
||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||
return
|
||||
@ -285,4 +323,20 @@ func IsPortInUse(port int) bool {
|
||||
}
|
||||
defer listener.Close()
|
||||
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 (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
@ -355,7 +356,16 @@ func (a *AutoRenewer) renewExpiredDomains(certs []*ExpiredCerts) ([]string, erro
|
||||
log.Println("Renewing " + expiredCert.Filepath + " (Might take a few minutes)")
|
||||
fileName := filepath.Base(expiredCert.Filepath)
|
||||
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 {
|
||||
log.Println("Renew " + fileName + "(" + strings.Join(expiredCert.Domains, ",") + ") failed: " + err.Error())
|
||||
} else {
|
||||
|
@ -109,10 +109,15 @@
|
||||
<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="Custom ACME Server">Custom ACME Server</div>
|
||||
<!-- <div class="item" data-value="Google">Google</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>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
@ -295,6 +300,14 @@
|
||||
obtainCertificate();
|
||||
});
|
||||
|
||||
$("input[name=ca]").on('change', function() {
|
||||
if(this.value == "Custom ACME Server") {
|
||||
$("#customca").show();
|
||||
} else {
|
||||
$("#customca").hide();
|
||||
}
|
||||
})
|
||||
|
||||
// Obtain certificate from API
|
||||
function obtainCertificate() {
|
||||
var domains = $("#domainsInput").val();
|
||||
@ -316,7 +329,14 @@
|
||||
parent.msgbox("Filename cannot be empty for certs containing multiple domains.")
|
||||
return;
|
||||
}
|
||||
|
||||
var ca = $("#ca").dropdown("get value");
|
||||
var ca_url = "";
|
||||
if (ca == "Custom ACME Server") {
|
||||
ca = "custom";
|
||||
ca_url = $("#caurl").val();
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/acme/obtainCert",
|
||||
method: "GET",
|
||||
@ -325,6 +345,7 @@
|
||||
filename: filename,
|
||||
email: email,
|
||||
ca: ca,
|
||||
ca_url: ca_url,
|
||||
},
|
||||
success: function(response) {
|
||||
$("#obtainButton").removeClass("loading").removeClass("disabled");
|
||||
|
Loading…
x
Reference in New Issue
Block a user