Browse Source

Changing over to multi-cert system.

multicert
Ruel Tmeizeh - RuhNet 2 years ago
parent
commit
87eff5db2b
4 changed files with 124 additions and 71 deletions
  1. +28
    -26
      actions.go
  2. +59
    -28
      api.go
  3. +25
    -15
      main.go
  4. +12
    -2
      sync.go

+ 28
- 26
actions.go View File

@ -6,6 +6,7 @@ import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
@ -19,9 +20,9 @@ import (
func writeDomains() error {
b := new(bytes.Buffer)
err := json.NewEncoder(b).Encode(domains)
err := json.NewEncoder(b).Encode(certgroups)
if err != nil {
return errors.New("Couldn't encode domains list into JSON: " + err.Error())
return errors.New("Couldn't encode certgroups struct into JSON: " + err.Error())
}
err = ioutil.WriteFile(configDir+"/domains.json", b.Bytes(), 0644)
@ -136,14 +137,16 @@ func sendFileToServer(filePath, server string) error {
return nil
}
func renew() error {
func renew(cert_idx int) error {
log.Println("Renew operation initiated...")
//BUILD/SET GETSSL ENVIRONMENT VARIABLES THEN EXECUTE GETSSL
//domain list
var domainlist string
cg := certgroups[cert_idx]
domains := cg.Domains
for _, d := range domains {
if d == appconf.PrimaryDomain { //ignore primary domain
if d == cg.PrimaryDomain { //ignore primary domain
continue
}
domainlist = domainlist + "," + d
@ -165,7 +168,6 @@ func renew() error {
continue
}
aclstring += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.LetsEncryptValidationPath
//aclstring += ";davs:leapi:" + appconf.SecretKey + ":" + server + ":" + syncPort + ":/api/file/upload/"
}
} else { //file sync type is HTTPS
aclstring += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync"
@ -182,68 +184,68 @@ func renew() error {
}
//Cert and key locations
domain_cert_location := appconf.TLSCertFile
domain_cert_location := appconf.TLSCertPath + fmt.Sprintf("%02d", cert_idx) + ".crt"
if appconf.SyncType == "ssh" {
for _, server := range servers {
if server == appconf.Hostname {
continue
}
domain_cert_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSCertFile
domain_cert_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSCertPath + fmt.Sprintf("%02d", cert_idx) + ".crt"
//domain_cert_location += ";davs:leapi:" + appconf.SecretKey + ":" + server + ":" + syncPort + ":/api/file/upload/cert"
}
} else { //file sync type is HTTPS
domain_cert_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/cert"
domain_cert_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/cert/" + fmt.Sprintf("%02d", cert_idx)
}
err = os.Setenv("DOMAIN_CERT_LOCATION", domain_cert_location)
if err != nil {
return errors.New("RENEW: error setting DOMAIN_CERT_LOCATION environment variable: " + err.Error())
}
domain_key_location := appconf.TLSKeyFile
domain_key_location := appconf.TLSKeyPath + fmt.Sprintf("%02d", cert_idx) + ".key"
if appconf.SyncType == "ssh" {
for _, server := range servers {
if server == appconf.Hostname {
continue
}
domain_key_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSKeyFile
domain_key_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSKeyPath + fmt.Sprintf("%02d", cert_idx) + ".key"
//domain_key_location += ";davs:leapi:" + appconf.SecretKey + ":" + server + ":" + syncPort + ":/api/file/upload/key"
}
} else { //file sync type is HTTPS
domain_key_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/key"
domain_key_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/key/" + fmt.Sprintf("%02d", cert_idx)
}
err = os.Setenv("DOMAIN_KEY_LOCATION", domain_key_location)
if err != nil {
return errors.New("RENEW: error setting DOMAIN_KEY_LOCATION environment variable: " + err.Error())
}
domain_chain_location := appconf.TLSChainFile
domain_chain_location := appconf.TLSChainPath + fmt.Sprintf("%02d", cert_idx) + ".crt"
if appconf.SyncType == "ssh" {
for _, server := range servers {
if server == appconf.Hostname {
continue
}
//domain_chain_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSChainFile
domain_chain_location += ";davs:leapi:" + appconf.SecretKey + ":" + server + ":" + syncPort + ":/api/file/upload/chain"
domain_chain_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSChainPath + fmt.Sprintf("%02d", cert_idx) + ".crt"
//domain_chain_location += ";davs:leapi:" + appconf.SecretKey + ":" + server + ":" + syncPort + ":/api/file/upload/chain"
}
} else { //file sync type is HTTPS
domain_chain_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/chain"
domain_chain_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/chain/" + fmt.Sprintf("%02d", cert_idx)
}
err = os.Setenv("DOMAIN_CHAIN_LOCATION", domain_chain_location)
if err != nil {
return errors.New("RENEW: error setting DOMAIN_CHAIN_LOCATION environment variable: " + err.Error())
}
domain_pem_location := appconf.TLSPEMFile
domain_pem_location := appconf.TLSPEMPath
if appconf.SyncType == "ssh" {
for _, server := range servers {
if server == appconf.Hostname {
continue
}
domain_pem_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSPEMFile
domain_pem_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSPEMPath + fmt.Sprintf("%02d", cert_idx) + ".pem"
//domain_pem_location += ";davs:leapi:" + appconf.SecretKey + ":" + server + ":" + syncPort + ":/api/file/upload/pem"
}
} else { //file sync type is HTTPS
domain_pem_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/pem"
domain_pem_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/pem/" + fmt.Sprintf("%02d", cert_idx)
}
err = os.Setenv("DOMAIN_PEM_LOCATION", domain_pem_location)
if err != nil {
@ -251,17 +253,17 @@ func renew() error {
}
//these parameters don't seem to be respected by gettssl from environment variables, so write them to config file:
ca_cert_location := appconf.TLSCAFile
ca_cert_location := appconf.TLSCAPath
if appconf.SyncType == "ssh" {
for _, server := range servers {
if server == appconf.Hostname {
continue
}
ca_cert_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSCAFile
ca_cert_location += ";ssh:" + appconf.Username + "@" + server + ":" + appconf.TLSCAPath + fmt.Sprintf("%02d", cert_idx) + ".crt"
//ca_cert_location += ";davs:leapi:" + appconf.SecretKey + ":" + server + ":" + syncPort + ":/api/file/upload/ca"
}
} else { //file sync type is HTTPS
ca_cert_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/ca"
ca_cert_location += ";davs:" + appconf.Username + ":" + appconf.SecretKey + ":" + appconf.Hostname + ":" + appconf.HTTPS_ServerPort + ":/api/file/sync/ca/" + fmt.Sprintf("%02d", cert_idx)
}
reload_command := appconf.ReloadCommand
@ -285,7 +287,7 @@ func renew() error {
configFile = "CA=\"" + ca_server + "\"\n"
configFile += "USE_SINGLE_ACL=\"true\"\n"
configFile += "CA_CERT_LOCATION=\"" + appconf.TLSCAFile + "\"\n"
configFile += "CA_CERT_LOCATION=\"" + appconf.TLSCAPath + "\"\n"
configFile += "RELOAD_CMD=\"" + reload_command + "\"\n"
configFile += "RENEW_ALLOW=\"" + appconf.RenewAllow + "\"\n"
configFile += "CHECK_REMOTE=\"true\"\n"
@ -293,9 +295,9 @@ func renew() error {
configFile += "CHECK_REMOTE_WAIT=\"5\"\n"
//write config file
err = ioutil.WriteFile(configDir+"/"+appconf.PrimaryDomain+"/getssl.cfg", []byte(configFile), 0644)
err = ioutil.WriteFile(configDir+"/"+cg.PrimaryDomain+"/getssl.cfg", []byte(configFile), 0644)
if err != nil {
return errors.New("Couldn't write getssl config file: " + configDir + "/" + appconf.PrimaryDomain + "/getssl.cfg")
return errors.New("Couldn't write getssl config file: " + configDir + "/" + cg.PrimaryDomain + "/getssl.cfg")
}
if appconf.Debug {
@ -316,9 +318,9 @@ func renew() error {
//RUN getssl on primary domain to renew
//cmd = exec.Command(appconf.SrvDir+"/getssl", "-u", "-w", appconf.SrvDir, appconf.PrimaryDomain)
if appconf.Debug {
cmd = exec.Command(appconf.SrvDir+"/getssl", "-d", "-w", appconf.SrvDir, appconf.PrimaryDomain)
cmd = exec.Command(appconf.SrvDir+"/getssl", "-d", "-w", appconf.SrvDir, cg.PrimaryDomain)
} else {
cmd = exec.Command(appconf.SrvDir+"/getssl", "-w", appconf.SrvDir, appconf.PrimaryDomain)
cmd = exec.Command(appconf.SrvDir+"/getssl", "-w", appconf.SrvDir, cg.PrimaryDomain)
}
output, err = cmd.CombinedOutput()
if err != nil {


+ 59
- 28
api.go View File

@ -2,6 +2,7 @@
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
@ -28,9 +29,11 @@ func uptimeCheck(c echo.Context) error {
}
func apiRenew(c echo.Context) error {
err := renew()
if err != nil {
return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error()))
for n, _ := range certgroups {
err := renew(n)
if err != nil {
return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error()))
}
}
return c.JSON(okOut())
}
@ -43,22 +46,24 @@ func apiReload(c echo.Context) error {
return c.JSON(okOut())
}
//Receive file and store it
func apiUpload(c echo.Context) error {
fileType := c.Param("fileType")
cert_idx := c.Param("cert_idx")
r := c.Request()
var filePath string
switch fileType {
case "ca":
filePath = appconf.TLSCAFile
filePath = appconf.TLSCAPath + fmt.Sprintf("%02d", cert_idx) + ".crt"
case "chain":
filePath = appconf.TLSChainFile
filePath = appconf.TLSChainPath + fmt.Sprintf("%02d", cert_idx) + ".crt"
case "key":
filePath = appconf.TLSKeyFile
filePath = appconf.TLSKeyPath + fmt.Sprintf("%02d", cert_idx) + ".key"
case "cert":
filePath = appconf.TLSCertFile
filePath = appconf.TLSCertPath + fmt.Sprintf("%02d", cert_idx) + ".crt"
case "pem":
filePath = appconf.TLSPEMFile
filePath = appconf.TLSPEMPath + fmt.Sprintf("%02d", cert_idx) + ".pem"
default: //ACL
//return c.JSON(errorOut(http.StatusBadRequest, "Invalid filetype/URL."))
filePath = appconf.LetsEncryptValidationPath + "/" + fileType
@ -131,7 +136,9 @@ func apiListDomains(c echo.Context) error {
var out APIOutput
out.Status = http.StatusOK
out.Message = "domains list"
out.Data = domains
for _, cg := range certgroups {
out.Data = append(out.Data, cg)
}
return c.JSON(out.Status, out)
}
@ -139,14 +146,26 @@ func apiPutDomain(c echo.Context) error {
domain := c.Param("domain")
//check for dups
for _, d := range domains {
if d == domain {
for _, cg := range certgroups {
if cg.PrimaryDomain == domain {
return c.JSON(errorOut(http.StatusBadRequest, "Bad request: Domain already exists."))
}
for _, d := range cg.Domains {
if d == domain {
return c.JSON(errorOut(http.StatusBadRequest, "Bad request: Domain already exists."))
}
}
}
var certgroup_slot int
//add domain to list
domains = append(domains, domain)
for n, cg := range certgroups {
if len(cg.Domains) < 99 { //can't have more than 100 names on a single cert
cg.Domains = append(cg.Domains, domain)
certgroups[n] = cg //replace with appended version
certgroup_slot = n //set slot we need to run renewal for
}
}
//write list to disk
err := writeDomains()
@ -163,7 +182,7 @@ func apiPutDomain(c echo.Context) error {
}
//renew cert
err = renew()
err = renew(certgroup_slot)
if err != nil {
log.Println(err.Error())
return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error()))
@ -175,14 +194,20 @@ func apiPutDomain(c echo.Context) error {
func apiDeleteDomain(c echo.Context) error {
deleteDomain := c.Param("domain")
var newlist []string
for _, d := range domains {
if d != deleteDomain {
newlist = append(newlist, d)
var certgroup_slot int
for n, cg := range certgroups {
for _, d := range cg.Domains {
if d == deleteDomain {
certgroup_slot = n //save the slot this deleted domain is in
} else { //rebuild the list with domains not deleted
newlist = append(newlist, d)
}
}
cg.Domains = newlist
certgroups[n] = cg //replace this slot
}
domains = newlist
//write list to disk
err := writeDomains()
if err != nil {
@ -198,7 +223,7 @@ func apiDeleteDomain(c echo.Context) error {
}
//renew cert
err = renew()
err = renew(certgroup_slot)
if err != nil {
log.Println(err.Error())
return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error()))
@ -211,7 +236,9 @@ func apiListServers(c echo.Context) error {
var out APIOutput
out.Status = http.StatusOK
out.Message = "servers list"
out.Data = servers
for _, server := range servers {
out.Data = append(out.Data, server)
}
return c.JSON(out.Status, out)
}
@ -243,10 +270,12 @@ func apiPutServer(c echo.Context) error {
}
//renew cert
err = renew()
if err != nil {
log.Println(err.Error())
return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error()))
for n, _ := range certgroups {
err = renew(n)
if err != nil {
log.Println(err.Error())
return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error()))
}
}
return c.JSON(okOut())
@ -278,10 +307,12 @@ func apiDeleteServer(c echo.Context) error {
}
//renew cert
err = renew()
if err != nil {
log.Println(err.Error())
return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error()))
for n, _ := range certgroups {
err = renew(n)
if err != nil {
log.Println(err.Error())
return c.JSON(errorOut(http.StatusInternalServerError, "Error renewing: "+err.Error()))
}
}
return c.JSON(okOut())


+ 25
- 15
main.go View File

@ -35,7 +35,7 @@ var appconf LEAPIConfig
var startupTime time.Time
var configDir string
var domains []string
var certgroups []CertGroup
var servers []string
var syncScheme string = "http://"
var syncPort string
@ -60,11 +60,11 @@ type LEAPIConfig struct {
Debug bool `json:"debug"`
HTTP_ServerPort string `json:"http_server_port"`
HTTPS_ServerPort string `json:"https_server_port"`
TLSCertFile string `json:"tls_cert_path"`
TLSKeyFile string `json:"tls_key_path"`
TLSChainFile string `json:"tls_chain_path"`
TLSPEMFile string `json:"tls_pem_path"`
TLSCAFile string `json:"tls_ca_path"`
TLSCertPath string `json:"tls_cert_path"`
TLSKeyPath string `json:"tls_key_path"`
TLSChainPath string `json:"tls_chain_path"`
TLSPEMPath string `json:"tls_pem_path"`
TLSCAPath string `json:"tls_ca_path"`
FrontEndURL string `json:"frontend_url"`
PrimaryDomain string `json:"primary_domain"`
LetsEncryptValidationPath string `json:"letsencrypt_validation_path"`
@ -75,6 +75,12 @@ type LEAPIConfig struct {
CheckPort string `json:"check_port"`
}
type CertGroup struct {
//CertPrefix string `json:"cert_prefix"`
PrimaryDomain string `json:"primary_domain"`
Domains []string `json:"domains"`
}
type UpOut struct {
Up bool `json:"up,omitempty"`
StartTime time.Time `json:"start_time,omitempty"`
@ -82,9 +88,9 @@ type UpOut struct {
}
type APIOutput struct {
Status int `json:"status,omitempty"`
Message string `json:"message,omitempty"`
Data []string `json:"data,omitempty"`
Status int `json:"status,omitempty"`
Message string `json:"message,omitempty"`
Data []interface{} `json:"data,omitempty"`
}
type keypairReloader struct {
@ -164,7 +170,7 @@ func main() {
}
defer jsonFile.Close()
fileBytes, err := ioutil.ReadAll(jsonFile)
err = json.Unmarshal(fileBytes, &domains)
err = json.Unmarshal(fileBytes, &certgroups)
if err != nil {
log.Fatal("Could not parse domains.json file: " + err.Error())
}
@ -268,9 +274,13 @@ func main() {
apiFile.OPTIONS("/upload/:fileType", apiUpload)
apiFile.PUT("/upload/:fileType", apiUpload)
apiFile.OPTIONS("/upload/:fileType/:cert_idx", apiUpload)
apiFile.PUT("/upload/:fileType/:cert_idx", apiUpload)
apiFile.OPTIONS("/sync/:fileType", apiUploadSync)
apiFile.PUT("/sync/:fileType", apiUploadSync)
apiFile.OPTIONS("/sync/:fileType/:cert_idx", apiUploadSync)
apiFile.PUT("/sync/:fileType/:cert_idx", apiUploadSync)
/////////////////////////////////////////////
// HTTP SERVERS CONFIG:
@ -281,14 +291,14 @@ func main() {
syncScheme = "https://"
syncPort = appconf.HTTPS_ServerPort
//certPair, err := tls.LoadX509KeyPair(appconf.TLSCertificateFile, appconf.TLSKeyFile)
if !fileExists(appconf.TLSChainFile) || !fileExists(appconf.TLSKeyFile) {
//certPair, err := tls.LoadX509KeyPair(appconf.TLSCertificateFile, appconf.TLSKeyPath)
if !fileExists(appconf.TLSChainPath) || !fileExists(appconf.TLSKeyPath) {
fmt.Println("Provided certificate and/or key file does not exist! Terminating.")
log.Fatal("Provided certificate and/or key file does not exist! Terminating.")
}
//Create loader for cert files
kpr, err := NewKeypairReloader(appconf.TLSChainFile, appconf.TLSKeyFile)
kpr, err := NewKeypairReloader(appconf.TLSChainPath, appconf.TLSKeyPath)
if err != nil {
log.Fatal(err)
}
@ -397,8 +407,8 @@ func NewKeypairReloader(certPath, keyPath string) (*keypairReloader, error) {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
for range c {
log.Printf("Received SIGHUP, reloading TLS certificate and key from %q and %q", appconf.TLSChainFile, appconf.TLSKeyFile)
fmt.Printf("Received SIGHUP, reloading TLS certificate and key from %q and %q\n", appconf.TLSChainFile, appconf.TLSKeyFile)
log.Printf("Received SIGHUP, reloading TLS certificate and key from %q and %q", appconf.TLSChainPath, appconf.TLSKeyPath)
fmt.Printf("Received SIGHUP, reloading TLS certificate and key from %q and %q\n", appconf.TLSChainPath, appconf.TLSKeyPath)
if err := result.maybeReload(); err != nil {
log.Printf("Keeping old TLS certificate because the new one could not be loaded: %v", err)
fmt.Printf("Keeping old TLS certificate because the new one could not be loaded: %v", err)


+ 12
- 2
sync.go View File

@ -123,7 +123,12 @@ func syncServersFromHost(host string) error {
log.Println(err.Error())
return errors.New("Couldn't parse response body from host " + host + ": " + err.Error())
}
servers = result.Data
for _, data := range result.Data {
switch server := data.(type) {
case string:
servers = append(servers, server)
}
}
err = writeServers()
if err != nil {
@ -171,7 +176,12 @@ func syncDomainsFromHost(host string) error {
log.Println(err.Error())
return errors.New("Couldn't parse response body from host " + host + ": " + err.Error())
}
domains = result.Data
for _, data := range result.Data {
switch cg := data.(type) {
case interface{}:
certgroups = append(certgroups, cg.(CertGroup))
}
}
err = writeDomains()
if err != nil {


Loading…
Cancel
Save