From 7c7a6a2e35cbe02a891ff2ba429fe993ea66dd80 Mon Sep 17 00:00:00 2001 From: Paul Lecuq Date: Sun, 24 Jan 2021 18:42:12 +0100 Subject: [PATCH] handle multidomains, modified schema structure --- src/cert/main.go | 2 +- src/pki/acme.go | 18 +++++++++-------- src/pkiws/server.go | 20 +++++++++++++------ src/pkiws/serverhandle.go | 42 ++++++++++++++++++++++----------------- src/pkiws/utils.go | 1 + 5 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/cert/main.go b/src/cert/main.go index e1cff4f..c465178 100644 --- a/src/cert/main.go +++ b/src/cert/main.go @@ -5,7 +5,7 @@ import "time" // Entry is the main struct for stored certificates type Entry struct { ID int `xorm:"pk autoincr"` - Domain string `xorm:"notnull"` + Domains string `xorm:"notnull"` Certificate string `xorm:"text notnull"` PrivateKey string `xorm:"text notnull"` AuthURL string diff --git a/src/pki/acme.go b/src/pki/acme.go index cb99439..5decec3 100644 --- a/src/pki/acme.go +++ b/src/pki/acme.go @@ -9,6 +9,7 @@ import ( "encoding/pem" "fmt" "log" + "strings" "time" "git.paulbsd.com/paulbsd/pki/src/cert" @@ -29,14 +30,15 @@ func (u *User) Init(cfg *config.Config) (err error) { } // GetEntry returns requested acme ressource in database relative to domain -func (u *User) GetEntry(cfg *config.Config, domain string) (Entry cert.Entry, err error) { - todaydate := time.Now().Format("2006-01-02") +func (u *User) GetEntry(cfg *config.Config, domains []string) (Entry cert.Entry, err error) { requireddate := time.Now().AddDate(0, 0, -cfg.ACME.MaxDaysBefore).Format("2006-01-02") - has, err := cfg.Db.Where("domain = ?", domain).Where( - "validity_begin < ?::date", todaydate).Where( - "validity_end > ?::date", requireddate).Where( - "auth_url = ?", cfg.ACME.AuthURL).Get(&Entry) + has, err := cfg.Db.Where("domains = ?", strings.Join(domains, ",")).Where( + "validity_begin <= now()").Where( + "validity_end >= ?::timestamp", requireddate).Where( + "auth_url = ?", cfg.ACME.AuthURL).Desc( + "id").Get(&Entry) + if !has { err = fmt.Errorf("Entry doesn't exists") } @@ -67,7 +69,7 @@ func (u *User) HandleRegistration(cfg *config.Config, client *lego.Client) (err } // RequestNewCert returns a newly requested certificate to letsencrypt -func (u *User) RequestNewCert(cfg *config.Config, domain string) (certificates *certificate.Resource, err error) { +func (u *User) RequestNewCert(cfg *config.Config, domains []string) (certificates *certificate.Resource, err error) { legoconfig := lego.NewConfig(u) legoconfig.CADirURL = cfg.ACME.AuthURL legoconfig.Certificate.KeyType = certcrypto.RSA2048 @@ -93,7 +95,7 @@ func (u *User) RequestNewCert(cfg *config.Config, domain string) (certificates * } request := certificate.ObtainRequest{ - Domains: []string{domain}, + Domains: domains, Bundle: true, } diff --git a/src/pkiws/server.go b/src/pkiws/server.go index 8a5fafb..87fb940 100644 --- a/src/pkiws/server.go +++ b/src/pkiws/server.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net/http" + "strings" "git.paulbsd.com/paulbsd/pki/src/config" "git.paulbsd.com/paulbsd/pki/src/pki" @@ -27,18 +28,25 @@ func RunServer(cfg *config.Config) (err error) { e.HideBanner = cfg.Options.HideBanner e.GET("/", func(c echo.Context) error { - return c.String(http.StatusOK, "Welcome to PKI software") + return c.String(http.StatusOK, "Welcome to PKI software (https://git.paulbsd.com/paulbsd/pki)") }) - e.GET("/domain/:domain", func(c echo.Context) (err error) { + e.GET("/domain/:domains", func(c echo.Context) (err error) { var result EntryResponse - log.Println(fmt.Sprintf("Providing %s to user %s at %s", c.Param("domain"), c.Get("username"), c.RealIP())) - result, err = GetCertificate(cfg, c.Get("user").(*pki.User), c.Param("domain")) + var domains = strings.Split(c.Param("domains"), ",") + + log.Println(fmt.Sprintf("Providing %s to user %s at %s", domains, c.Get("username"), c.RealIP())) + + result, err = GetCertificate(cfg, c.Get("user").(*pki.User), domains) if err != nil { - return c.String(http.StatusInternalServerError, fmt.Sprintf("%s %s", result, err)) + return c.String(http.StatusInternalServerError, fmt.Sprintf("%s", err)) } return c.JSON(http.StatusOK, result) }) - e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.Switchs.Port))) + e.Logger.Fatal( + e.Start( + fmt.Sprintf(":%d", + cfg.Switchs.Port))) + return } diff --git a/src/pkiws/serverhandle.go b/src/pkiws/serverhandle.go index 8670a05..3255c8d 100644 --- a/src/pkiws/serverhandle.go +++ b/src/pkiws/serverhandle.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "regexp" + "strings" "time" "git.paulbsd.com/paulbsd/pki/src/cert" @@ -14,15 +15,15 @@ import ( ) // GetCertificate get certificate from database if exists, of request it from ACME -func GetCertificate(cfg *config.Config, user *pki.User, domain string) (result EntryResponse, err error) { - err = CheckDomain(domain) +func GetCertificate(cfg *config.Config, user *pki.User, domains []string) (result EntryResponse, err error) { + err = CheckDomains(domains) if err != nil { return result, err } - entry, err := user.GetEntry(cfg, domain) + entry, err := user.GetEntry(cfg, domains) if err != nil { - certs, err := user.RequestNewCert(cfg, domain) + certs, err := user.RequestNewCert(cfg, domains) if err != nil { log.Println(fmt.Sprintf("Error fetching new certificate %s", err)) return result, err @@ -32,7 +33,7 @@ func GetCertificate(cfg *config.Config, user *pki.User, domain string) (result E log.Println("Error where parsing dates") return result, err } - entry := cert.Entry{Domain: domain, + entry := cert.Entry{Domains: strings.Join(domains, ","), Certificate: string(certs.Certificate), PrivateKey: string(certs.PrivateKey), ValidityBegin: NotBefore, @@ -46,11 +47,14 @@ func GetCertificate(cfg *config.Config, user *pki.User, domain string) (result E return } -// CheckDomain check if requested domain is valid -func CheckDomain(domain string) (err error) { - res, err := regexp.Match(`^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$`, []byte(domain)) - if !res { - return fmt.Errorf("Domain has not a valid syntax") +// CheckDomains check if requested domains are valid +func CheckDomains(domains []string) (err error) { + for _, d := range domains { + res, err := regexp.Match(`^[a-z0-9\*]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$`, []byte(d)) + if !res { + fmt.Println(res, err) + return fmt.Errorf(fmt.Sprintf("Domain has not a valid syntax %s, please verify", err)) + } } return } @@ -71,20 +75,22 @@ func GetDates(cert []byte) (NotBefore time.Time, NotAfter time.Time, err error) // convertEntryToResponse converts database ACME entry to JSON ACME entry func convertEntryToResponse(in cert.Entry) (out EntryResponse) { - out.Domain = in.Domain + timeformatstring := "2006-01-02 15:04:05" + + out.Domains = in.Domains out.Certificate = in.Certificate out.PrivateKey = in.PrivateKey - out.ValidityBegin = in.ValidityBegin - out.ValidityEnd = in.ValidityEnd + out.ValidityBegin = in.ValidityBegin.Format(timeformatstring) + out.ValidityEnd = in.ValidityEnd.Format(timeformatstring) return } // EntryResponse is the struct defining JSON response from webservice type EntryResponse struct { - Domain string `json:"domain"` - Certificate string `json:"certificate"` - PrivateKey string `json:"privatekey"` - ValidityBegin time.Time `json:"validitybegin"` - ValidityEnd time.Time `json:"validityend"` + Domains string `json:"domains"` + Certificate string `json:"certificate"` + PrivateKey string `json:"privatekey"` + ValidityBegin string `json:"validitybegin"` + ValidityEnd string `json:"validityend"` } diff --git a/src/pkiws/utils.go b/src/pkiws/utils.go index 21f4681..13eb07c 100644 --- a/src/pkiws/utils.go +++ b/src/pkiws/utils.go @@ -13,6 +13,7 @@ import ( // Auth make authentication to webservice func Auth(cfg *config.Config, username string, password string, c echo.Context) (res bool, user *pki.User, err error) { user = &pki.User{Username: username} + _, err = cfg.Db.Get(user) if err != nil { res = false