mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-19 00:36:38 +00:00
add cert manager with self signed cert support
This commit is contained in:
113
proxy/internal/reverseproxy/certmanager/letsencrypt.go
Normal file
113
proxy/internal/reverseproxy/certmanager/letsencrypt.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package certmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
// LetsEncryptManager handles TLS certificate issuance via Let's Encrypt
|
||||
type LetsEncryptManager struct {
|
||||
autocertManager *autocert.Manager
|
||||
allowedHosts map[string]bool
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// LetsEncryptConfig holds Let's Encrypt certificate manager configuration
|
||||
type LetsEncryptConfig struct {
|
||||
// Email for Let's Encrypt registration (required)
|
||||
Email string
|
||||
|
||||
// CertCacheDir is the directory to cache certificates
|
||||
CertCacheDir string
|
||||
}
|
||||
|
||||
// NewLetsEncrypt creates a new Let's Encrypt certificate manager
|
||||
func NewLetsEncrypt(config LetsEncryptConfig) *LetsEncryptManager {
|
||||
m := &LetsEncryptManager{
|
||||
allowedHosts: make(map[string]bool),
|
||||
}
|
||||
|
||||
m.autocertManager = &autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: m.hostPolicy,
|
||||
Cache: autocert.DirCache(config.CertCacheDir),
|
||||
Email: config.Email,
|
||||
RenewBefore: 0, // Use default
|
||||
}
|
||||
|
||||
log.Info("Let's Encrypt certificate manager initialized")
|
||||
return m
|
||||
}
|
||||
|
||||
// IsEnabled returns whether certificate management is enabled
|
||||
func (m *LetsEncryptManager) IsEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// AddDomain adds a domain to the allowed hosts list
|
||||
func (m *LetsEncryptManager) AddDomain(domain string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.allowedHosts[domain] = true
|
||||
log.Infof("Added domain to Let's Encrypt manager: %s", domain)
|
||||
}
|
||||
|
||||
// RemoveDomain removes a domain from the allowed hosts list
|
||||
func (m *LetsEncryptManager) RemoveDomain(domain string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
delete(m.allowedHosts, domain)
|
||||
log.Infof("Removed domain from Let's Encrypt manager: %s", domain)
|
||||
}
|
||||
|
||||
// IssueCertificate eagerly issues a Let's Encrypt certificate for a domain
|
||||
func (m *LetsEncryptManager) IssueCertificate(ctx context.Context, domain string) error {
|
||||
log.Infof("Issuing Let's Encrypt certificate for domain: %s", domain)
|
||||
|
||||
// Use GetCertificate to trigger certificate issuance
|
||||
// This will go through the ACME challenge flow
|
||||
hello := &tls.ClientHelloInfo{
|
||||
ServerName: domain,
|
||||
}
|
||||
|
||||
cert, err := m.autocertManager.GetCertificate(hello)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to issue certificate for domain %s: %w", domain, err)
|
||||
}
|
||||
|
||||
log.Infof("Successfully issued Let's Encrypt certificate for domain: %s (expires: %s)",
|
||||
domain, cert.Leaf.NotAfter.Format(time.RFC3339))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TLSConfig returns the TLS configuration for the HTTPS server
|
||||
func (m *LetsEncryptManager) TLSConfig() *tls.Config {
|
||||
return m.autocertManager.TLSConfig()
|
||||
}
|
||||
|
||||
// HTTPHandler returns the HTTP handler for ACME challenges
|
||||
func (m *LetsEncryptManager) HTTPHandler(fallback http.Handler) http.Handler {
|
||||
return m.autocertManager.HTTPHandler(fallback)
|
||||
}
|
||||
|
||||
// hostPolicy validates that a requested host is in the allowed hosts list
|
||||
func (m *LetsEncryptManager) hostPolicy(ctx context.Context, host string) error {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
if m.allowedHosts[host] {
|
||||
log.Debugf("ACME challenge accepted for domain: %s", host)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Warnf("ACME challenge rejected for unconfigured domain: %s", host)
|
||||
return fmt.Errorf("host %s not configured", host)
|
||||
}
|
||||
Reference in New Issue
Block a user