diff --git a/src/acme.go b/src/acme.go index d9f0638..5699bdf 100644 --- a/src/acme.go +++ b/src/acme.go @@ -57,15 +57,18 @@ func acmeRegisterSpecialRoutingRule() { req.Host = r.Host if err != nil { fmt.Printf("client: could not create request: %s\n", err) + return } res, err := http.DefaultClient.Do(req) if err != nil { fmt.Printf("client: error making http request: %s\n", err) + return } resBody, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Printf("error reading: %s\n", err) + return } w.Write(resBody) }, diff --git a/src/api.go b/src/api.go index 016b90c..9fc95d0 100644 --- a/src/api.go +++ b/src/api.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" + "imuslab.com/zoraxy/mod/acme/acmewizard" "imuslab.com/zoraxy/mod/auth" "imuslab.com/zoraxy/mod/netstat" "imuslab.com/zoraxy/mod/netutils" @@ -159,7 +160,9 @@ func initAPIs() { authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail) authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains) authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains) + authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy) authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow) + authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) //ACME Wizard //If you got APIs to add, append them here } diff --git a/src/mod/acme/acmewizard/acmewizard.go b/src/mod/acme/acmewizard/acmewizard.go new file mode 100644 index 0000000..f833019 --- /dev/null +++ b/src/mod/acme/acmewizard/acmewizard.go @@ -0,0 +1,159 @@ +package acmewizard + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "strconv" + "strings" + "time" + + "imuslab.com/zoraxy/mod/utils" +) + +/* + ACME Wizard + + This wizard help validate the acme settings and configurations +*/ + +func HandleGuidedStepCheck(w http.ResponseWriter, r *http.Request) { + stepNoStr, err := utils.GetPara(r, "step") + if err != nil { + utils.SendErrorResponse(w, "invalid step number given") + return + } + + stepNo, err := strconv.Atoi(stepNoStr) + if err != nil { + utils.SendErrorResponse(w, "invalid step number given") + return + } + + if stepNo == 1 { + isListening, err := isLocalhostListening() + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + js, _ := json.Marshal(isListening) + utils.SendJSONResponse(w, string(js)) + } else if stepNo == 2 { + publicIp, err := getPublicIPAddress() + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + publicIp = strings.TrimSpace(publicIp) + + httpServerReachable := isHTTPServerAvailable(publicIp) + + js, _ := json.Marshal(httpServerReachable) + utils.SendJSONResponse(w, string(js)) + } else if stepNo == 3 { + domain, err := utils.GetPara(r, "domain") + if err != nil { + utils.SendErrorResponse(w, "domain cannot be empty") + return + } + + domain = strings.TrimSpace(domain) + + //Check if the domain is reachable + reachable := isDomainReachable(domain) + if !reachable { + utils.SendErrorResponse(w, "domain is not reachable") + return + } + + //Check http is setup correctly + httpServerReachable := isHTTPServerAvailable(domain) + js, _ := json.Marshal(httpServerReachable) + utils.SendJSONResponse(w, string(js)) + } else { + utils.SendErrorResponse(w, "invalid step number") + } +} + +// Step 1 +func isLocalhostListening() (isListening bool, err error) { + timeout := 2 * time.Second + isListening = false + // Check if localhost is listening on port 80 (HTTP) + conn, err := net.DialTimeout("tcp", "localhost:80", timeout) + if err == nil { + isListening = true + conn.Close() + } + + // Check if localhost is listening on port 443 (HTTPS) + conn, err = net.DialTimeout("tcp", "localhost:443", timeout) + if err == nil { + isListening = true + conn.Close() + } + + return isListening, err +} + +// Step 2 +func getPublicIPAddress() (string, error) { + resp, err := http.Get("http://checkip.amazonaws.com/") + if err != nil { + return "", err + } + defer resp.Body.Close() + + ip, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(ip), nil +} + +func isHTTPServerAvailable(ipAddress string) bool { + client := http.Client{ + Timeout: 5 * time.Second, // Timeout for the HTTP request + } + + urls := []string{ + "http://" + ipAddress + ":80", + "https://" + ipAddress + ":443", + } + + for _, url := range urls { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Println(err, url) + continue // Ignore invalid URLs + } + + // Disable TLS verification to handle invalid certificates + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + resp, err := client.Do(req) + if err == nil { + resp.Body.Close() + return true // HTTP server is available + } + } + + return false // HTTP server is not available +} + +// Step 3 +func isDomainReachable(domain string) bool { + _, err := net.LookupHost(domain) + if err != nil { + return false // Domain is not reachable + } + return true // Domain is reachable +} diff --git a/src/mod/acme/autorenew.go b/src/mod/acme/autorenew.go index b546944..47123f9 100644 --- a/src/mod/acme/autorenew.go +++ b/src/mod/acme/autorenew.go @@ -184,6 +184,12 @@ func (a *AutoRenewer) HandleLoadAutoRenewDomains(w http.ResponseWriter, r *http. utils.SendJSONResponse(w, string(js)) } +func (a *AutoRenewer) HandleRenewPolicy(w http.ResponseWriter, r *http.Request) { + //Load the current value + js, _ := json.Marshal(a.RenewerConfig.RenewAll) + utils.SendJSONResponse(w, string(js)) +} + func (a *AutoRenewer) HandleRenewNow(w http.ResponseWriter, r *http.Request) { renewedDomains, err := a.CheckAndRenewCertificates() if err != nil { diff --git a/src/web/snippet/acme.html b/src/web/snippet/acme.html index 5c801f2..49f3c1c 100644 --- a/src/web/snippet/acme.html +++ b/src/web/snippet/acme.html @@ -56,7 +56,7 @@

Renew all certificates with ACME supported CAs

- +

@@ -139,6 +139,7 @@ } function initRenewerConfigFromFile(){ + //Set the renew switch state $.get("/api/acme/autoRenew/enable", function(data){ if (data == true){ $("#enableCertAutoRenew").parent().checkbox("set checked"); @@ -152,11 +153,21 @@ }) }); + //Load the email from server side $.get("/api/acme/autoRenew/email", function(data){ if (data != "" && data != undefined && data != null){ $("#caRegisterEmail").val(data); } }); + + //Load the domain selection options + $.get("/api/acme/autoRenew/renewPolicy", function(data){ + if (data == true){ + $("#renewAllSupported").parent().checkbox("set checked"); + }else{ + $("#renewAllSupported").parent().checkbox("set unchecked"); + } + }); } initRenewerConfigFromFile(); @@ -238,6 +249,13 @@ counter++; } + + if (Object.keys(domainFileList).length == 0){ + //No certificate in this system + tableBody.append(` + No certificate in use + `); + } } //Initiate domain table. If you needs to update the expired domain as well diff --git a/src/web/tools/https.html b/src/web/tools/https.html new file mode 100644 index 0000000..c05faf5 --- /dev/null +++ b/src/web/tools/https.html @@ -0,0 +1,321 @@ + + + + + + + + + HTTPS Setup Wizard | Zoraxy + + + + + + + + + + +
+
+
+ This Wizard require both client and server connected to the internet. +
+
+

+ HTTPS (TLS/SSL Certificate) Setup Wizard +
This tool help you setup https with your domain / subdomain on your Zoraxy host.
+ Follow the steps below to get started
+

+
+
+

+ 1. Setup Zoraxy to listen to port 80 or 443 and start listening +
ACME can only works on port 80 (or 80 redirected 443). Please make sure Zoarxy is listening to either one of the ports.
+

+ +
+ +
+
+
+

+ 2. If you are under NAT, setup Port Forward and forward external port 80 (and 443, if you are using 443) to your Zoraxy's LAN IP address port 80 (and 443) +
If your Zoraxy server IP address starts with 192.168., you are mostly under a NAT router.
+

+ The check function below will use public ip to check if port is opened. Make sure your host is reachable from the internet!
+ If you are using 443, you still need to forward port 80 for performing 80 to 443 redirect.

+ +
+ +
+
+ +
+

+ 3. Point your domain (or sub-domain) to your Zoraxy server public IP address +
DNS records might takes 5 - 10 minutes to take effect. If checking did not poss the first time, wait for a few minutes and retry.
+

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

+ 4. Request a public CA to assign you a certificate +
This process might take a few minutes and usually fully automated. If there are any error, you can see Zoraxy STDOUT / log for more information.
+

+
+
+ +
+ +
+ Your CA might send expire notification to you via this email. +
+
+ + + If you have more than one domain in a single certificate, enter the domains separated by commas (e.g. s1.dev.example.com,s2.dev.example.com) +
+ + +
+ + +
+ +
+ +
+ + + \ No newline at end of file