Rename pkg in certmanager
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -1,24 +0,0 @@
|
|||||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
package cert
|
|
||||||
|
|
||||||
// DNSRecordInstruction provides DNS configuration instructions
|
|
||||||
type DNSRecordInstruction struct {
|
|
||||||
Type string `json:"type"` // TXT, CNAME
|
|
||||||
Name string `json:"name"` // Full DNS record name
|
|
||||||
Value string `json:"value"` // Value to set
|
|
||||||
TTL int `json:"ttl"` // Recommended TTL
|
|
||||||
Purpose string `json:"purpose"` // verification, acme_challenge
|
|
||||||
}
|
|
||||||
@@ -12,19 +12,21 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
package cert
|
package certmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/getprobo/probo/pkg/crypto/keys"
|
"github.com/getprobo/probo/pkg/crypto/keys"
|
||||||
cryptopem "github.com/getprobo/probo/pkg/crypto/pem"
|
"github.com/getprobo/probo/pkg/crypto/pem"
|
||||||
"github.com/getprobo/probo/pkg/version"
|
"github.com/getprobo/probo/pkg/version"
|
||||||
"go.gearno.de/kit/httpclient"
|
"go.gearno.de/kit/httpclient"
|
||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
@@ -55,17 +57,28 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewACMEService(email string, keyType keys.Type, directoryURL string, logger *log.Logger) (*ACMEService, error) {
|
func NewACMEService(email string, keyType keys.Type, directoryURL string, insecureTLS bool, logger *log.Logger) (*ACMEService, error) {
|
||||||
accountKey, err := keys.Generate(keyType)
|
accountKey, err := keys.Generate(keyType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot generate account key: %w", err)
|
return nil, fmt.Errorf("cannot generate account key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient := httpclient.DefaultPooledClient(
|
var httpClient *http.Client
|
||||||
|
|
||||||
|
if insecureTLS {
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
httpClient = &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
logger.Warn("ACME service configured with insecure TLS - use only for local testing")
|
||||||
|
} else {
|
||||||
|
httpClient = httpclient.DefaultPooledClient(
|
||||||
httpclient.WithLogger(logger),
|
httpclient.WithLogger(logger),
|
||||||
// httpclient.WithTracerProvider(tp),
|
|
||||||
// httpclient.WithRegisterer(r),
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
client := &acme.Client{
|
client := &acme.Client{
|
||||||
Key: accountKey,
|
Key: accountKey,
|
||||||
@@ -183,8 +196,8 @@ func (s *ACMEService) CompleteHTTPChallenge(
|
|||||||
return nil, fmt.Errorf("cannot parse certificate: %w", err)
|
return nil, fmt.Errorf("cannot parse certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
certPEM := cryptopem.EncodeCertificate(der[0])
|
certPEM := pem.EncodeCertificate(der[0])
|
||||||
keyPEM, err := cryptopem.EncodePrivateKey(certKey)
|
keyPEM, err := pem.EncodePrivateKey(certKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot encode key: %w", err)
|
return nil, fmt.Errorf("cannot encode key: %w", err)
|
||||||
}
|
}
|
||||||
@@ -193,7 +206,7 @@ func (s *ACMEService) CompleteHTTPChallenge(
|
|||||||
if len(der) > 1 {
|
if len(der) > 1 {
|
||||||
chainDER = der[1:]
|
chainDER = der[1:]
|
||||||
}
|
}
|
||||||
chainPEM := cryptopem.EncodeCertificateChain(chainDER)
|
chainPEM := pem.EncodeCertificateChain(chainDER)
|
||||||
|
|
||||||
return &Certificate{
|
return &Certificate{
|
||||||
CertPEM: certPEM,
|
CertPEM: certPEM,
|
||||||
@@ -268,8 +281,8 @@ func (s *ACMEService) renewWithExistingAuth(ctx context.Context, domain string)
|
|||||||
return nil, fmt.Errorf("cannot parse certificate: %w", err)
|
return nil, fmt.Errorf("cannot parse certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
certPEM := cryptopem.EncodeCertificate(der[0])
|
certPEM := pem.EncodeCertificate(der[0])
|
||||||
keyPEM, err := cryptopem.EncodePrivateKey(certKey)
|
keyPEM, err := pem.EncodePrivateKey(certKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot encode key: %w", err)
|
return nil, fmt.Errorf("cannot encode key: %w", err)
|
||||||
}
|
}
|
||||||
@@ -278,7 +291,7 @@ func (s *ACMEService) renewWithExistingAuth(ctx context.Context, domain string)
|
|||||||
if len(der) > 1 {
|
if len(der) > 1 {
|
||||||
chainDER = der[1:]
|
chainDER = der[1:]
|
||||||
}
|
}
|
||||||
chainPEM := cryptopem.EncodeCertificateChain(chainDER)
|
chainPEM := pem.EncodeCertificateChain(chainDER)
|
||||||
|
|
||||||
return &Certificate{
|
return &Certificate{
|
||||||
CertPEM: certPEM,
|
CertPEM: certPEM,
|
||||||
100
pkg/certmanager/acme_challenge_handler.go
Normal file
100
pkg/certmanager/acme_challenge_handler.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package certmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/crypto/cipher"
|
||||||
|
"go.gearno.de/kit/log"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ACMEChallengeHandler struct {
|
||||||
|
pg *pg.Client
|
||||||
|
encryptionKey cipher.EncryptionKey
|
||||||
|
logger *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewACMEChallengeHandler(
|
||||||
|
pg *pg.Client,
|
||||||
|
encryptionKey cipher.EncryptionKey,
|
||||||
|
logger *log.Logger,
|
||||||
|
) *ACMEChallengeHandler {
|
||||||
|
return &ACMEChallengeHandler{
|
||||||
|
pg: pg,
|
||||||
|
encryptionKey: encryptionKey,
|
||||||
|
logger: logger.Named("acme-challenge-handler"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ACMEChallengeHandler) Handle(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := strings.TrimPrefix(r.URL.Path, "/.well-known/acme-challenge/")
|
||||||
|
if token == "" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyAuth, err := h.getKeyAuthForToken(r.Context(), token)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.WarnCtx(
|
||||||
|
r.Context(),
|
||||||
|
"cannot get key auth for token",
|
||||||
|
log.String("token", token),
|
||||||
|
log.Error(err),
|
||||||
|
)
|
||||||
|
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(keyAuth))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ACMEChallengeHandler) getKeyAuthForToken(ctx context.Context, token string) (string, error) {
|
||||||
|
var keyAuth string
|
||||||
|
|
||||||
|
err := h.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
domain := &coredata.CustomDomain{}
|
||||||
|
if err := domain.LoadByHTTPChallengeToken(ctx, conn, coredata.NewNoScope(), h.encryptionKey, token); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if domain.HTTPChallengeKeyAuth == nil {
|
||||||
|
return http.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
keyAuth = *domain.HTTPChallengeKeyAuth
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return keyAuth, err
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
package cert
|
package certmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -26,26 +26,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CacheWarmer struct {
|
CacheStore struct {
|
||||||
pg *pg.Client
|
pg *pg.Client
|
||||||
encryptionKey cipher.EncryptionKey
|
encryptionKey cipher.EncryptionKey
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCacheWarmer(
|
func NewCacheStore(
|
||||||
pg *pg.Client,
|
pg *pg.Client,
|
||||||
encryptionKey cipher.EncryptionKey,
|
encryptionKey cipher.EncryptionKey,
|
||||||
logger *log.Logger,
|
logger *log.Logger,
|
||||||
) *CacheWarmer {
|
) *CacheStore {
|
||||||
return &CacheWarmer{
|
return &CacheStore{
|
||||||
pg: pg,
|
pg: pg,
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
logger: logger.Named("cert.cache_warmer"),
|
logger: logger.Named("certmanager.cache-store"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *CacheWarmer) WarmCache(ctx context.Context) error {
|
func (w *CacheStore) WarmCache(ctx context.Context) error {
|
||||||
w.logger.InfoCtx(ctx, "warming certificate cache")
|
w.logger.InfoCtx(ctx, "warming certificate cache")
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ func (w *CacheWarmer) WarmCache(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *CacheWarmer) warmDomain(ctx context.Context, conn pg.Conn, domain *coredata.CustomDomain) error {
|
func (w *CacheStore) warmDomain(ctx context.Context, conn pg.Conn, domain *coredata.CustomDomain) error {
|
||||||
var loadedDomain coredata.CustomDomain
|
var loadedDomain coredata.CustomDomain
|
||||||
scope := coredata.NewScope(domain.OrganizationID.TenantID())
|
scope := coredata.NewScope(domain.OrganizationID.TenantID())
|
||||||
if err := loadedDomain.LoadByID(ctx, conn, scope, w.encryptionKey, domain.ID); err != nil {
|
if err := loadedDomain.LoadByID(ctx, conn, scope, w.encryptionKey, domain.ID); err != nil {
|
||||||
@@ -137,7 +137,7 @@ func (w *CacheWarmer) warmDomain(ctx context.Context, conn pg.Conn, domain *core
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *CacheWarmer) RefreshCache(ctx context.Context) error {
|
func (w *CacheStore) RefreshCache(ctx context.Context) error {
|
||||||
w.logger.InfoCtx(ctx, "refreshing certificate cache")
|
w.logger.InfoCtx(ctx, "refreshing certificate cache")
|
||||||
|
|
||||||
return w.pg.WithConn(
|
return w.pg.WithConn(
|
||||||
@@ -153,7 +153,7 @@ func (w *CacheWarmer) RefreshCache(ctx context.Context) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *CacheWarmer) WarmSingleDomain(ctx context.Context, domainName string) error {
|
func (w *CacheStore) WarmSingleDomain(ctx context.Context, domainName string) error {
|
||||||
return w.pg.WithConn(
|
return w.pg.WithConn(
|
||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
func(conn pg.Conn) error {
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
package cert
|
package certmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -26,7 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CertificateProvisioner struct {
|
Provisioner struct {
|
||||||
pg *pg.Client
|
pg *pg.Client
|
||||||
acmeService *ACMEService
|
acmeService *ACMEService
|
||||||
encryptionKey cipher.EncryptionKey
|
encryptionKey cipher.EncryptionKey
|
||||||
@@ -35,23 +35,23 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCertificateProvisioner(
|
func NewProvisioner(
|
||||||
pg *pg.Client,
|
pg *pg.Client,
|
||||||
acmeService *ACMEService,
|
acmeService *ACMEService,
|
||||||
encryptionKey cipher.EncryptionKey,
|
encryptionKey cipher.EncryptionKey,
|
||||||
interval time.Duration,
|
interval time.Duration,
|
||||||
logger *log.Logger,
|
logger *log.Logger,
|
||||||
) *CertificateProvisioner {
|
) *Provisioner {
|
||||||
return &CertificateProvisioner{
|
return &Provisioner{
|
||||||
pg: pg,
|
pg: pg,
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
interval: interval,
|
interval: interval,
|
||||||
logger: logger.Named("cert.provisioner"),
|
logger: logger.Named("certmanager.provisioner"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CertificateProvisioner) Run(ctx context.Context) error {
|
func (p *Provisioner) Run(ctx context.Context) error {
|
||||||
p.logger.InfoCtx(ctx, "certificate provisioner starting", log.Duration("interval", p.interval))
|
p.logger.InfoCtx(ctx, "certificate provisioner starting", log.Duration("interval", p.interval))
|
||||||
|
|
||||||
if err := p.checkPendingDomains(ctx); err != nil {
|
if err := p.checkPendingDomains(ctx); err != nil {
|
||||||
@@ -74,7 +74,7 @@ func (p *CertificateProvisioner) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CertificateProvisioner) checkPendingDomains(ctx context.Context) error {
|
func (p *Provisioner) checkPendingDomains(ctx context.Context) error {
|
||||||
return p.pg.WithConn(ctx, func(conn pg.Conn) error {
|
return p.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
var domains coredata.CustomDomains
|
var domains coredata.CustomDomains
|
||||||
if err := domains.ListDomainsWithPendingHTTPChallenges(ctx, conn, coredata.NewNoScope()); err != nil {
|
if err := domains.ListDomainsWithPendingHTTPChallenges(ctx, conn, coredata.NewNoScope()); err != nil {
|
||||||
@@ -108,7 +108,7 @@ func (p *CertificateProvisioner) checkPendingDomains(ctx context.Context) error
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CertificateProvisioner) completeDomainCertificate(
|
func (p *Provisioner) completeDomainCertificate(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Conn,
|
conn pg.Conn,
|
||||||
domain *coredata.CustomDomain,
|
domain *coredata.CustomDomain,
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
package cert
|
package certmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CertificateRenewer struct {
|
Renewer struct {
|
||||||
pg *pg.Client
|
pg *pg.Client
|
||||||
acmeService *ACMEService
|
acmeService *ACMEService
|
||||||
encryptionKey cipher.EncryptionKey
|
encryptionKey cipher.EncryptionKey
|
||||||
@@ -37,24 +37,24 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCertificateRenewer(
|
func NewRenewer(
|
||||||
pg *pg.Client,
|
pg *pg.Client,
|
||||||
acmeService *ACMEService,
|
acmeService *ACMEService,
|
||||||
encryptionKey cipher.EncryptionKey,
|
encryptionKey cipher.EncryptionKey,
|
||||||
interval time.Duration,
|
interval time.Duration,
|
||||||
logger *log.Logger,
|
logger *log.Logger,
|
||||||
) *CertificateRenewer {
|
) *Renewer {
|
||||||
return &CertificateRenewer{
|
return &Renewer{
|
||||||
pg: pg,
|
pg: pg,
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
interval: interval,
|
interval: interval,
|
||||||
logger: logger.Named("cert.certificate-renewer"),
|
logger: logger.Named("certmanager.renewer"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *CertificateRenewer) Run(ctx context.Context) error {
|
func (r *Renewer) Run(ctx context.Context) error {
|
||||||
r.logger.InfoCtx(ctx, "certificate certificate-renewer starting")
|
r.logger.InfoCtx(ctx, "certificate renewer starting")
|
||||||
|
|
||||||
if err := r.checkAndRenew(ctx); err != nil {
|
if err := r.checkAndRenew(ctx); err != nil {
|
||||||
r.logger.ErrorCtx(ctx, "cannot perform initial renewal check", log.Error(err))
|
r.logger.ErrorCtx(ctx, "cannot perform initial renewal check", log.Error(err))
|
||||||
@@ -63,7 +63,7 @@ func (r *CertificateRenewer) Run(ctx context.Context) error {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
r.logger.InfoCtx(ctx, "certificate certificate-renewer shutting down")
|
r.logger.InfoCtx(ctx, "certificate renewer shutting down")
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
case <-time.After(r.interval):
|
case <-time.After(r.interval):
|
||||||
if err := r.checkAndRenew(ctx); err != nil {
|
if err := r.checkAndRenew(ctx); err != nil {
|
||||||
@@ -73,7 +73,7 @@ func (r *CertificateRenewer) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *CertificateRenewer) checkAndRenew(ctx context.Context) error {
|
func (r *Renewer) checkAndRenew(ctx context.Context) error {
|
||||||
return r.pg.WithConn(
|
return r.pg.WithConn(
|
||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
func(conn pg.Conn) error {
|
||||||
@@ -84,7 +84,7 @@ func (r *CertificateRenewer) checkAndRenew(ctx context.Context) error {
|
|||||||
} else if cacheCount == 0 {
|
} else if cacheCount == 0 {
|
||||||
r.logger.InfoCtx(ctx, "certificate cache is empty, rebuilding from custom_domains")
|
r.logger.InfoCtx(ctx, "certificate cache is empty, rebuilding from custom_domains")
|
||||||
|
|
||||||
warmer := NewCacheWarmer(r.pg, r.encryptionKey, r.logger)
|
warmer := NewCacheStore(r.pg, r.encryptionKey, r.logger)
|
||||||
if err := warmer.WarmCache(ctx); err != nil {
|
if err := warmer.WarmCache(ctx); err != nil {
|
||||||
r.logger.ErrorCtx(ctx, "cannot rebuild certificate cache", log.Error(err))
|
r.logger.ErrorCtx(ctx, "cannot rebuild certificate cache", log.Error(err))
|
||||||
} else {
|
} else {
|
||||||
@@ -128,7 +128,7 @@ func (r *CertificateRenewer) checkAndRenew(ctx context.Context) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *CertificateRenewer) renewDomain(ctx context.Context, conn pg.Conn, domain *coredata.CustomDomain) error {
|
func (r *Renewer) renewDomain(ctx context.Context, conn pg.Conn, domain *coredata.CustomDomain) error {
|
||||||
scope := coredata.NewScope(domain.OrganizationID.TenantID())
|
scope := coredata.NewScope(domain.OrganizationID.TenantID())
|
||||||
|
|
||||||
lockedDomain := &coredata.CustomDomain{}
|
lockedDomain := &coredata.CustomDomain{}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
package cert
|
package certmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -30,37 +30,27 @@ type (
|
|||||||
Selector struct {
|
Selector struct {
|
||||||
pg *pg.Client
|
pg *pg.Client
|
||||||
cache sync.Map
|
cache sync.Map
|
||||||
defaultDomain string
|
|
||||||
encryptionKey cipher.EncryptionKey
|
encryptionKey cipher.EncryptionKey
|
||||||
defaultCert *tls.Certificate
|
|
||||||
defaultCertMutex sync.RWMutex
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewSelector(
|
func NewSelector(
|
||||||
pg *pg.Client,
|
pg *pg.Client,
|
||||||
defaultDomain string,
|
|
||||||
encryptionKey cipher.EncryptionKey,
|
encryptionKey cipher.EncryptionKey,
|
||||||
) *Selector {
|
) *Selector {
|
||||||
return &Selector{
|
return &Selector{
|
||||||
pg: pg,
|
pg: pg,
|
||||||
defaultDomain: defaultDomain,
|
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Selector) SetDefaultCertificate(cert *tls.Certificate) {
|
|
||||||
s.defaultCertMutex.Lock()
|
|
||||||
defer s.defaultCertMutex.Unlock()
|
|
||||||
s.defaultCert = cert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Selector) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (s *Selector) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
domain := hello.ServerName
|
domain := hello.ServerName
|
||||||
|
|
||||||
// Empty domain, use default
|
// Empty domain, return error
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
return s.getDefaultCertificate()
|
return nil, fmt.Errorf("no SNI provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cached, ok := s.cache.Load(domain); ok {
|
if cached, ok := s.cache.Load(domain); ok {
|
||||||
@@ -71,7 +61,7 @@ func (s *Selector) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate,
|
|||||||
|
|
||||||
cert, err := s.loadFromDatabase(domain)
|
cert, err := s.loadFromDatabase(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s.getDefaultCertificate()
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.cache.Store(domain, cert)
|
s.cache.Store(domain, cert)
|
||||||
@@ -163,17 +153,6 @@ func (s *Selector) rebuildCacheEntry(ctx context.Context, conn pg.Conn, domain s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDefaultCertificate returns the default wildcard certificate
|
|
||||||
func (s *Selector) getDefaultCertificate() (*tls.Certificate, error) {
|
|
||||||
s.defaultCertMutex.RLock()
|
|
||||||
defer s.defaultCertMutex.RUnlock()
|
|
||||||
|
|
||||||
if s.defaultCert == nil {
|
|
||||||
return nil, fmt.Errorf("no default certificate configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.defaultCert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Selector) ClearCache() {
|
func (s *Selector) ClearCache() {
|
||||||
s.cache.Range(
|
s.cache.Range(
|
||||||
Reference in New Issue
Block a user