//LEAPI - ACME Certificate Renewal Control API - Copyright 2022-2024 Ruel Tmeizeh All Rights Reserved package main import ( "io" "io/ioutil" "log" "net/http" "os" "path" "github.com/labstack/echo/v4" ) ///////////////////////////////////////////// ///// API ROUTE FUNCTIONS ///////////////////////////////////////////// func nothingResponse(c echo.Context) error { return c.NoContent(http.StatusNotFound) } func uptimeCheck(c echo.Context) error { if c.Request().Method == http.MethodHead { return c.NoContent(http.StatusOK) } //return c.String(http.StatusOK, "{\"up\":true}") return c.JSON(http.StatusOK, uptime()) } func apiRenew(c echo.Context) error { err := renew() if err != nil { return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error())) } return c.JSON(okOut()) } func apiReload(c echo.Context) error { err := reload() if err != nil { return c.JSON(errorOut(http.StatusInternalServerError, "Error reloading services: "+err.Error())) } return c.JSON(okOut()) } func apiUpload(c echo.Context) error { fileType := c.Param("fileType") r := c.Request() var filePath string switch fileType { case "ca": filePath = appconf.TLSCAFile case "chain": filePath = appconf.TLSChainFile case "key": filePath = appconf.TLSKeyFile case "cert": filePath = appconf.TLSCertFile case "pem": filePath = appconf.TLSPEMFile default: //ACL //return c.JSON(errorOut(http.StatusBadRequest, "Invalid filetype/URL.")) filePath = appconf.LetsEncryptValidationPath + "/" + fileType } directory, _ := path.Split(filePath) //Check and create directory if _, err := os.Stat(directory); os.IsNotExist(err) { err = os.MkdirAll(directory, 0755) if err != nil { return c.JSON(errorOut(http.StatusInternalServerError, "Filetype "+fileType+" directory does not exist, and could not create: "+err.Error())) } } //Read the upload data var blimit int64 = 102400 //100k max upload size body, err := ioutil.ReadAll(io.LimitReader(r.Body, blimit)) if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error reading post body: "+err.Error())) } //Write the file err = ioutil.WriteFile(filePath, body, 0644) if err != nil { return c.JSON(errorOut(http.StatusInternalServerError, "Could not write file: "+err.Error())) } log.Println("Received PUT to " + r.RequestURI) log.Println("Writing to " + filePath) return c.JSON(okOut()) } func apiUploadSync(c echo.Context) error { fileType := c.Param("fileType") r := c.Request() //Read the upload data var blimit int64 = 102400 //100k max upload size body, err := ioutil.ReadAll(io.LimitReader(r.Body, blimit)) if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error reading post body: "+err.Error())) } uuid := generateUUID() filePath := appconf.SrvDir + "/" + fileType + "__" + uuid + ".tmpfile" //Write the file err = ioutil.WriteFile(filePath, body, 0644) if err != nil { return c.JSON(errorOut(http.StatusInternalServerError, "Could not write temporary file: "+err.Error())) } log.Println("Received PUT for sync to " + r.RequestURI) log.Println("Writing to " + filePath) err = sendFileToAllServers(filePath) if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error sending file "+filePath+" to other servers: "+err.Error())) } return c.JSON(okOut()) } func apiListDomains(c echo.Context) error { var out APIOutput out.Status = http.StatusOK out.Message = "domains list" out.Data = domains return c.JSON(out.Status, out) } func apiPutDomain(c echo.Context) error { domain := c.Param("domain") //check for dups for _, d := range domains { if d == domain { return c.JSON(errorOut(http.StatusBadRequest, "Bad request: Domain already exists.")) } } //add domain to list domains = append(domains, domain) //write list to disk err := writeDomains() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error writing domains list to disk: "+err.Error())) } //sync with other servers err = syncAllServers() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error syncing to other servers: "+err.Error())) } //renew cert err = renew() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error())) } return c.JSON(okOut()) } func apiDeleteDomain(c echo.Context) error { deleteDomain := c.Param("domain") var newlist []string for _, d := range domains { if d != deleteDomain { newlist = append(newlist, d) } } domains = newlist //write list to disk err := writeDomains() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error writing domains list to disk: "+err.Error())) } //sync with other servers err = syncAllServers() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error syncing to other servers: "+err.Error())) } //renew cert err = renew() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error())) } return c.JSON(okOut()) } func apiListServers(c echo.Context) error { var out APIOutput out.Status = http.StatusOK out.Message = "servers list" out.Data = servers return c.JSON(out.Status, out) } func apiPutServer(c echo.Context) error { server := c.Param("server") //check for dups for _, s := range servers { if s == server { return c.JSON(errorOut(http.StatusBadRequest, "Bad request: Server already exists.")) } } //add servers to list servers = append(servers, server) //write list to disk err := writeServers() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error writing servers list to disk: "+err.Error())) } //sync with other servers err = syncAllServers() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error syncing to other servers: "+err.Error())) } //renew cert err = renew() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error())) } return c.JSON(okOut()) } func apiDeleteServer(c echo.Context) error { deleteServer := c.Param("server") var newlist []string for _, s := range servers { if s != deleteServer { newlist = append(newlist, s) } } servers = newlist //write list to disk err := writeServers() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error writing servers list to disk: "+err.Error())) } //sync with other servers err = syncAllServers() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error syncing to other servers: "+err.Error())) } //renew cert err = renew() if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error())) } return c.JSON(okOut()) } func apiSync(c echo.Context) error { host := c.Param("host") log.Println("Received sync request for host: " + host + ". From IP address: " + c.RealIP() + " Syncing...") err := syncServersFromHost(host) if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error syncing servers from host: "+host+". "+err.Error())) } err = syncDomainsFromHost(host) if err != nil { log.Println(err.Error()) return c.JSON(errorOut(http.StatusInternalServerError, "Error syncing domains from host: "+host+". "+err.Error())) } return c.JSON(okOut()) }