feat: add powerdns config, domain check, api changes
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
dadb907740
commit
90bfc25975
@ -2,10 +2,13 @@ package cert
|
||||
|
||||
import "time"
|
||||
|
||||
func (e *Entry) Z() {
|
||||
}
|
||||
|
||||
// Entry is the main struct for stored certificates
|
||||
type Entry struct {
|
||||
ID int `xorm:"pk autoincr"`
|
||||
Domains string `xorm:"notnull"`
|
||||
Domain string `xorm:"notnull"`
|
||||
Certificate string `xorm:"text notnull"`
|
||||
PrivateKey string `xorm:"text notnull"`
|
||||
AuthURL string `xorm:"notnull"`
|
||||
|
@ -60,10 +60,13 @@ func (cfg *Config) GetConfig() error {
|
||||
options["ovhas"] = pkisection.Key("ovhas").MustString("")
|
||||
options["ovhck"] = pkisection.Key("ovhck").MustString("")
|
||||
|
||||
options["pdnsapiurl"] = pkisection.Key("pdnsapiurl").MustString("")
|
||||
options["pdnsapikey"] = pkisection.Key("pdnsapikey").MustString("")
|
||||
|
||||
cfg.ACME.ProviderOptions = options
|
||||
for k, v := range options {
|
||||
if v == "" {
|
||||
utils.Advice(fmt.Sprintf("OVH provider parameter %s not set", k))
|
||||
for key, value := range options {
|
||||
if value == "" {
|
||||
utils.Advice(fmt.Sprintf("Provider parameter %s not set", key))
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,6 +75,8 @@ func (cfg *Config) GetConfig() error {
|
||||
cfg.ACME.AuthURL = lego.LEDirectoryProduction
|
||||
case "staging":
|
||||
cfg.ACME.AuthURL = lego.LEDirectoryStaging
|
||||
default:
|
||||
cfg.ACME.AuthURL = lego.LEDirectoryStaging
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"git.paulbsd.com/paulbsd/pki/src/cert"
|
||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||
"git.paulbsd.com/paulbsd/pki/src/domain"
|
||||
"git.paulbsd.com/paulbsd/pki/src/pki"
|
||||
_ "github.com/lib/pq"
|
||||
"xorm.io/xorm"
|
||||
@ -17,7 +18,7 @@ import (
|
||||
func Init(cfg *config.Config) (err error) {
|
||||
var databaseEngine = "postgres"
|
||||
tables := []interface{}{cert.Entry{},
|
||||
pki.User{}}
|
||||
pki.User{}, domain.Domain{}}
|
||||
|
||||
cfg.Db, err = xorm.NewEngine(databaseEngine,
|
||||
fmt.Sprintf("%s://%s:%s@%s/%s",
|
||||
|
12
src/domain/main.go
Normal file
12
src/domain/main.go
Normal file
@ -0,0 +1,12 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
// Domain describes a domain
|
||||
type Domain struct {
|
||||
ID int `xorm:"pk autoincr"`
|
||||
Domain string `xorm:"text notnull unique(domain_provider)"`
|
||||
Provider string `xorm:"text notnull unique(domain_provider)"`
|
||||
Created time.Time `xorm:"created notnull"`
|
||||
Updated time.Time `xorm:"updated notnull"`
|
||||
}
|
@ -9,12 +9,13 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"git.paulbsd.com/paulbsd/pki/src/cert"
|
||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||
"git.paulbsd.com/paulbsd/pki/src/domain"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
@ -29,9 +30,8 @@ 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, domains []string) (Entry cert.Entry, err error) {
|
||||
|
||||
has, err := cfg.Db.Where("domains = ?", strings.Join(domains, ",")).And(
|
||||
func (u *User) GetEntry(cfg *config.Config, domain *string) (Entry cert.Entry, err error) {
|
||||
has, err := cfg.Db.Where("domain = ?", domain).And(
|
||||
"auth_url = ?", cfg.ACME.AuthURL).And(
|
||||
fmt.Sprintf("validity_end::timestamp-'%d DAY'::INTERVAL >= now()", cfg.ACME.MaxDaysBefore)).Desc(
|
||||
"id").Get(&Entry)
|
||||
@ -66,12 +66,27 @@ 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, domains []string) (certificates *certificate.Resource, err error) {
|
||||
func (u *User) RequestNewCert(cfg *config.Config, domainname *string) (certs *certificate.Resource, err error) {
|
||||
legoconfig := lego.NewConfig(u)
|
||||
legoconfig.CADirURL = cfg.ACME.AuthURL
|
||||
legoconfig.Certificate.KeyType = certcrypto.RSA2048
|
||||
|
||||
ovhprovider, err := initProvider(cfg)
|
||||
dom := domain.Domain{Domain: *domainname}
|
||||
_, err = cfg.Db.Get(&dom)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
var provider challenge.Provider
|
||||
|
||||
switch dom.Provider {
|
||||
case "ovh":
|
||||
provider, err = initOVHProvider(cfg)
|
||||
case "pdns":
|
||||
provider, err = initPowerDNSProvider(cfg)
|
||||
default:
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
@ -81,7 +96,7 @@ func (u *User) RequestNewCert(cfg *config.Config, domains []string) (certificate
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(ovhprovider)
|
||||
err = client.Challenge.SetDNS01Provider(provider)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
@ -95,14 +110,15 @@ func (u *User) RequestNewCert(cfg *config.Config, domains []string) (certificate
|
||||
}
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: domains,
|
||||
Domains: []string{*domainname, fmt.Sprintf(`*.%s`, *domainname)},
|
||||
Bundle: true,
|
||||
}
|
||||
|
||||
certificates, err = client.Certificate.Obtain(request)
|
||||
certs, err = client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,16 @@
|
||||
package pki
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/url"
|
||||
|
||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
||||
"github.com/go-acme/lego/v4/providers/dns/pdns"
|
||||
)
|
||||
|
||||
// initProvider initialize DNS provider configuration
|
||||
func initProvider(cfg *config.Config) (ovhprovider *ovh.DNSProvider, err error) {
|
||||
// initOVHProvider initialize DNS provider configuration
|
||||
func initOVHProvider(cfg *config.Config) (ovhprovider *ovh.DNSProvider, err error) {
|
||||
ovhconfig := ovh.NewDefaultConfig()
|
||||
|
||||
ovhconfig.APIEndpoint = cfg.ACME.ProviderOptions["ovhendpoint"]
|
||||
@ -15,6 +19,24 @@ func initProvider(cfg *config.Config) (ovhprovider *ovh.DNSProvider, err error)
|
||||
ovhconfig.ConsumerKey = cfg.ACME.ProviderOptions["ovhck"]
|
||||
|
||||
ovhprovider, err = ovh.NewDNSProviderConfig(ovhconfig)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// initPowerDNSProvider initialize DNS provider configuration
|
||||
func initPowerDNSProvider(cfg *config.Config) (pdnsprovider *pdns.DNSProvider, err error) {
|
||||
pdnsconfig := pdns.NewDefaultConfig()
|
||||
|
||||
pdnsconfig.Host, err = url.Parse(cfg.ACME.ProviderOptions["pdnsapiurl"])
|
||||
pdnsconfig.APIKey = cfg.ACME.ProviderOptions["pdnsapikey"]
|
||||
|
||||
pdnsprovider, err = pdns.NewDNSProviderConfig(pdnsconfig)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||
"git.paulbsd.com/paulbsd/pki/src/pki"
|
||||
@ -30,13 +29,17 @@ func RunServer(cfg *config.Config) (err error) {
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Welcome to PKI software (https://git.paulbsd.com/paulbsd/pki)")
|
||||
})
|
||||
e.GET("/domain/:domains", func(c echo.Context) (err error) {
|
||||
var result EntryResponse
|
||||
var domains = strings.Split(c.Param("domains"), ",")
|
||||
e.POST("/cert", func(c echo.Context) (err error) {
|
||||
var request EntryRequest
|
||||
var result = make(map[string]EntryResponse)
|
||||
err = c.Bind(&request)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, "error parsing request")
|
||||
}
|
||||
|
||||
log.Println(fmt.Sprintf("Providing %s to user %s at %s", domains, c.Get("username"), c.RealIP()))
|
||||
log.Printf("Providing %s to user %s at %s\n", request.Domains, c.Get("username"), c.RealIP())
|
||||
|
||||
result, err = GetCertificate(cfg, c.Get("user").(*pki.User), domains)
|
||||
result, err = GetCertificate(cfg, c.Get("user").(*pki.User), &request.Domains)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, fmt.Sprintf("%s", err))
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.paulbsd.com/paulbsd/pki/src/cert"
|
||||
@ -14,56 +13,66 @@ import (
|
||||
"git.paulbsd.com/paulbsd/pki/src/pki"
|
||||
)
|
||||
|
||||
const timeformatstring string = "2006-01-02 15:04:05"
|
||||
|
||||
var domainRegex, err = regexp.Compile(`^[a-z0-9\*]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$`)
|
||||
|
||||
// GetCertificate get certificate from database if exists, of request it from ACME
|
||||
func GetCertificate(cfg *config.Config, user *pki.User, domains []string) (result EntryResponse, err error) {
|
||||
func GetCertificate(cfg *config.Config, user *pki.User, domains *[]string) (result map[string]EntryResponse, err error) {
|
||||
err = CheckDomains(domains)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = make(map[string]EntryResponse)
|
||||
|
||||
entry, err := user.GetEntry(cfg, domains)
|
||||
if err != nil {
|
||||
certs, err := user.RequestNewCert(cfg, domains)
|
||||
for _, domain := range *domains {
|
||||
entry, err := user.GetEntry(cfg, &domain)
|
||||
if err != nil {
|
||||
log.Println(fmt.Sprintf("Error fetching new certificate %s", err))
|
||||
certs, err := user.RequestNewCert(cfg, &domain)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching new certificate %s\n", err)
|
||||
return result, err
|
||||
}
|
||||
NotBefore, NotAfter, err := GetDates(certs.Certificate)
|
||||
if err != nil {
|
||||
log.Println("Error where parsing dates")
|
||||
return result, err
|
||||
}
|
||||
entry := cert.Entry{Domain: certs.Domain,
|
||||
Certificate: string(certs.Certificate),
|
||||
PrivateKey: string(certs.PrivateKey),
|
||||
ValidityBegin: NotBefore,
|
||||
ValidityEnd: NotAfter,
|
||||
AuthURL: cfg.ACME.AuthURL}
|
||||
cfg.Db.Insert(&entry)
|
||||
result[domain] = convertEntryToResponse(entry)
|
||||
return result, err
|
||||
}
|
||||
NotBefore, NotAfter, err := GetDates(certs.Certificate)
|
||||
if err != nil {
|
||||
log.Println("Error where parsing dates")
|
||||
return result, err
|
||||
}
|
||||
entry := cert.Entry{Domains: strings.Join(domains, ","),
|
||||
Certificate: string(certs.Certificate),
|
||||
PrivateKey: string(certs.PrivateKey),
|
||||
ValidityBegin: NotBefore,
|
||||
ValidityEnd: NotAfter,
|
||||
AuthURL: cfg.ACME.AuthURL}
|
||||
cfg.Db.Insert(&entry)
|
||||
result = convertEntryToResponse(entry)
|
||||
return result, err
|
||||
result[domain] = convertEntryToResponse(entry)
|
||||
}
|
||||
result = convertEntryToResponse(entry)
|
||||
return
|
||||
}
|
||||
|
||||
// CheckDomains check if requested domains are valid
|
||||
func CheckDomains(domains []string) (err error) {
|
||||
domainRegex, err := regexp.Compile(`^[a-z0-9\*]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$`)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, d := range domains {
|
||||
res := domainRegex.Match([]byte(d))
|
||||
if !res {
|
||||
return fmt.Errorf(fmt.Sprintf("Domain %s has not a valid syntax %s, please verify", d, err))
|
||||
func CheckDomains(domains *[]string) (err error) {
|
||||
for _, domain := range *domains {
|
||||
err = CheckDomain(&domain)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CheckDomain check if requested domain are valid
|
||||
func CheckDomain(domain *string) (err error) {
|
||||
res := domainRegex.Match([]byte(*domain))
|
||||
if !res {
|
||||
return fmt.Errorf("Domain %s has not a valid syntax %s, please verify", *domain, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetDates decodes NotBefore and NotAfter date of cert
|
||||
func GetDates(cert []byte) (NotBefore time.Time, NotAfter time.Time, err error) {
|
||||
block, _ := pem.Decode(cert)
|
||||
@ -80,9 +89,7 @@ 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) {
|
||||
timeformatstring := "2006-01-02 15:04:05"
|
||||
|
||||
out.Domains = in.Domains
|
||||
out.Domains = append(out.Domains, in.Domain)
|
||||
out.Certificate = in.Certificate
|
||||
out.PrivateKey = in.PrivateKey
|
||||
out.ValidityBegin = in.ValidityBegin.Format(timeformatstring)
|
||||
@ -91,11 +98,16 @@ func convertEntryToResponse(in cert.Entry) (out EntryResponse) {
|
||||
return
|
||||
}
|
||||
|
||||
// EntryRequest
|
||||
type EntryRequest struct {
|
||||
Domains []string `json:"domains"`
|
||||
}
|
||||
|
||||
// EntryResponse is the struct defining JSON response from webservice
|
||||
type EntryResponse struct {
|
||||
Domains string `json:"domains"`
|
||||
Certificate string `json:"certificate"`
|
||||
PrivateKey string `json:"privatekey"`
|
||||
ValidityBegin string `json:"validitybegin"`
|
||||
ValidityEnd string `json:"validityend"`
|
||||
Domains []string `json:"domains"`
|
||||
Certificate string `json:"certificate"`
|
||||
PrivateKey string `json:"privatekey"`
|
||||
ValidityBegin string `json:"validitybegin"`
|
||||
ValidityEnd string `json:"validityend"`
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user