Skip to content

Commit

Permalink
feat: initial certmagic update work
Browse files Browse the repository at this point in the history
Still a lot to do. Everywhere that `context`s are used have currently
been passed `context.TODO()`. A logger should be passed to certmagic's
lock cleanup function. Most importantly, though, it doesn't seem to be
possible to map certmagic's LibDNS providers to Lego's. In the next
commit
I will try to see how much work it is to switch to using LibDNS instead,
 which will work natively with Certmagic. The plugin registration will
 need to change, and constructors may need to be written for each
 provider. Best-case, there should be minimal changes to the
 configuration and documentation.
  • Loading branch information
Lemmmy committed Jan 24, 2024
1 parent 5fd2388 commit dd12a8a
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 116 deletions.
19 changes: 12 additions & 7 deletions casket/casketmain/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ package casketmain

import (
"bufio"
"context"
"errors"
"flag"
"fmt"
"go.uber.org/zap"
"io"
"io/ioutil"
"log"
Expand All @@ -29,13 +31,13 @@ import (
"strconv"
"strings"

"github.com/caddyserver/certmagic"
"github.com/google/uuid"
"github.com/klauspost/cpuid"
"github.com/tmpim/casket"
"github.com/tmpim/casket/casketfile"
"github.com/tmpim/casket/caskettls"
"github.com/tmpim/casket/telemetry"
"github.com/tmpim/certmagic"
lumberjack "gopkg.in/natefinch/lumberjack.v2"

_ "github.com/tmpim/casket/caskethttp" // plug in the HTTP server type
Expand All @@ -45,19 +47,19 @@ import (
func init() {
casket.TrapSignals()

flag.BoolVar(&certmagic.Default.Agreed, "agree", true, "Agree to the CA's Subscriber Agreement")
flag.StringVar(&certmagic.Default.CA, "ca", certmagic.Default.CA, "URL to certificate authority's ACME server directory")
flag.BoolVar(&certmagic.DefaultACME.Agreed, "agree", true, "Agree to the CA's Subscriber Agreement")
flag.StringVar(&certmagic.DefaultACME.CA, "ca", certmagic.DefaultACME.CA, "URL to certificate authority's ACME server directory")
flag.StringVar(&certmagic.Default.DefaultServerName, "default-sni", certmagic.Default.DefaultServerName, "If a ClientHello ServerName is empty, use this ServerName to choose a TLS certificate")
flag.BoolVar(&certmagic.Default.DisableHTTPChallenge, "disable-http-challenge", certmagic.Default.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
flag.BoolVar(&certmagic.Default.DisableTLSALPNChallenge, "disable-tls-alpn-challenge", certmagic.Default.DisableTLSALPNChallenge, "Disable the ACME TLS-ALPN challenge")
flag.BoolVar(&certmagic.DefaultACME.DisableHTTPChallenge, "disable-http-challenge", certmagic.DefaultACME.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
flag.BoolVar(&certmagic.DefaultACME.DisableTLSALPNChallenge, "disable-tls-alpn-challenge", certmagic.DefaultACME.DisableTLSALPNChallenge, "Disable the ACME TLS-ALPN challenge")
flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable")
flag.StringVar(&conf, "conf", "", "Casketfile to load (default \""+casket.DefaultConfigFile+"\")")
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
flag.BoolVar(&printEnv, "env", false, "Enable to print environment variables")
flag.StringVar(&envFile, "envfile", "", "Path to file with environment variables to load in KEY=VALUE format")
flag.BoolVar(&fromJSON, "json-to-casketfile", false, "From JSON stdin to Casketfile stdout")
flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
flag.StringVar(&certmagic.Default.Email, "email", "", "Default ACME CA account email address")
flag.StringVar(&certmagic.DefaultACME.Email, "email", "", "Default ACME CA account email address")
flag.DurationVar(&certmagic.HTTPTimeout, "catimeout", certmagic.HTTPTimeout, "Default ACME CA HTTP timeout")
flag.StringVar(&logfile, "log", "", "Process log file")
flag.BoolVar(&logTimestamps, "log-timestamps", true, "Enable timestamps for the process log")
Expand All @@ -84,7 +86,10 @@ func Run() {

casket.AppName = appName
casket.AppVersion = module.Version
casket.OnProcessExit = append(casket.OnProcessExit, certmagic.CleanUpOwnLocks)
casket.OnProcessExit = append(casket.OnProcessExit, func() {
// TODO: Redirect to our own logger instead of zap.NewNop()
certmagic.CleanUpOwnLocks(context.TODO(), zap.NewNop())
})
certmagic.UserAgent = appName + "/" + cleanModVersion

if !logTimestamps {
Expand Down
14 changes: 9 additions & 5 deletions caskethttp/httpserver/https.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
package httpserver

import (
"context"
"fmt"
"net"
"net/http"
"strconv"

"github.com/caddyserver/certmagic"
"github.com/tmpim/casket"
"github.com/tmpim/casket/caskettls"
"github.com/tmpim/certmagic"
)

func activateHTTPS(cctx casket.Context) error {
Expand All @@ -45,7 +46,8 @@ func activateHTTPS(cctx casket.Context) error {
if c.TLS.Manager.OnDemand != nil {
continue // obtain these certificates on-demand instead
}
err := c.TLS.Manager.ObtainCert(c.TLS.Hostname, operatorPresent)

err := c.TLS.Manager.ObtainCertAsync(context.TODO(), c.TLS.Hostname)
if err != nil {
return err
}
Expand All @@ -71,7 +73,7 @@ func activateHTTPS(cctx casket.Context) error {
certCache, ok := ctx.instance.Storage[caskettls.CertCacheInstStorageKey].(*certmagic.Cache)
ctx.instance.StorageMu.RUnlock()
if ok && certCache != nil {
err = certCache.RenewManagedCertificates()
err = certCache.RenewManagedCertificates(context.TODO())
if err != nil {
return err
}
Expand Down Expand Up @@ -116,8 +118,10 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
}
cfg.TLS.Enabled = true
cfg.Addr.Scheme = "https"
if loadCertificates && certmagic.HostQualifies(cfg.TLS.Hostname) {
_, err := cfg.TLS.Manager.CacheManagedCertificate(cfg.TLS.Hostname)

// TODO: SubjectQualifiesForPublicCert behavior may be slightly different in mainline certmagic
if loadCertificates && certmagic.SubjectQualifiesForPublicCert(cfg.TLS.Hostname) {
_, err := cfg.TLS.Manager.CacheManagedCertificate(context.TODO(), cfg.TLS.Hostname)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion caskethttp/httpserver/https_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import (
"strconv"
"testing"

"github.com/caddyserver/certmagic"
"github.com/tmpim/casket/caskettls"
"github.com/tmpim/certmagic"
)

func TestRedirPlaintextHost(t *testing.T) {
Expand Down
16 changes: 9 additions & 7 deletions caskethttp/httpserver/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import (
"strings"
"time"

"github.com/caddyserver/certmagic"
"github.com/tmpim/casket"
"github.com/tmpim/casket/casketfile"
"github.com/tmpim/casket/caskethttp/staticfiles"
"github.com/tmpim/casket/caskettls"
"github.com/tmpim/casket/telemetry"
"github.com/tmpim/certmagic"
)

const serverType = "http"
Expand Down Expand Up @@ -192,13 +192,14 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cask
// Make our caskettls.Config, which has a pointer to the
// instance's certificate cache and enough information
// to use automatic HTTPS when the time comes
caskettlsConfig, err := caskettls.NewConfig(h.instance)
caskettlsConfig, err := caskettls.NewConfig(h.instance, certmagic.ACMEIssuer{
AltHTTPPort: altHTTPPort,
AltTLSALPNPort: altTLSALPNPort,
})
if err != nil {
return nil, fmt.Errorf("creating new caskettls configuration: %v", err)
}
caskettlsConfig.Hostname = addr.Host
caskettlsConfig.Manager.AltHTTPPort = altHTTPPort
caskettlsConfig.Manager.AltTLSALPNPort = altTLSALPNPort

// Save the config to our master list, and key it for lookups
cfg := &SiteConfig{
Expand Down Expand Up @@ -239,7 +240,7 @@ func (h *httpContext) MakeServers() ([]casket.Server, error) {
// trusted CA (obviously not a perfect heuristic)
var looksLikeProductionCA bool
for _, publicCAEndpoint := range caskettls.KnownACMECAs {
if strings.Contains(certmagic.Default.CA, publicCAEndpoint) {
if strings.Contains(certmagic.DefaultACME.CA, publicCAEndpoint) {
looksLikeProductionCA = true
break
}
Expand All @@ -262,7 +263,7 @@ func (h *httpContext) MakeServers() ([]casket.Server, error) {
!casket.IsInternal(cfg.Addr.Host) &&
!casket.IsInternal(cfg.ListenHost) &&
(caskettls.QualifiesForManagedTLS(cfg) ||
certmagic.HostQualifies(cfg.Addr.Host)) {
certmagic.SubjectQualifiesForPublicCert(cfg.Addr.Host)) {
atLeastOneSiteLooksLikeProduction = true
}
}
Expand Down Expand Up @@ -328,7 +329,8 @@ func (h *httpContext) MakeServers() ([]casket.Server, error) {
}

// normalizedKey returns "normalized" key representation:
// scheme and host names are lowered, everything else stays the same
//
// scheme and host names are lowered, everything else stays the same
func normalizedKey(key string) string {
addr, err := standardizeAddress(key)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion caskethttp/httpserver/tplcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (

"os"

"github.com/tmpim/certmagic"
"github.com/caddyserver/certmagic"
"github.com/russross/blackfriday"
"github.com/tmpim/casket/caskettls"
)
Expand Down
44 changes: 31 additions & 13 deletions caskettls/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
package caskettls

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"time"

"github.com/go-acme/lego/v4/certcrypto"
"github.com/caddyserver/certmagic"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/klauspost/cpuid"
"github.com/tmpim/casket"
"github.com/tmpim/certmagic"
)

// Config describes how TLS should be configured and used.
Expand Down Expand Up @@ -84,6 +84,12 @@ type Config struct {
// Manager is how certificates are managed
Manager *certmagic.Config

// Issuer is the configuration for the ACME issuer, which will be the first issuer in Manager.Issuers
Issuer *certmagic.ACMEIssuer

// KeyType is the type of key used for self-signed certificates
KeyType certmagic.KeyType

// NoRedirect will disable the automatic HTTP->HTTPS redirect, regardless
// of whether the site is managed or not.
NoRedirect bool
Expand Down Expand Up @@ -111,7 +117,7 @@ type Config struct {
// NewConfig returns a new Config with a pointer to the instance's
// certificate cache. You will usually need to set other fields on
// the returned Config for successful practical use.
func NewConfig(inst *casket.Instance) (*Config, error) {
func NewConfig(inst *casket.Instance, template certmagic.ACMEIssuer) (*Config, error) {
inst.StorageMu.RLock()
certCache, ok := inst.Storage[CertCacheInstStorageKey].(*certmagic.Cache)
inst.StorageMu.RUnlock()
Expand All @@ -120,18 +126,18 @@ func NewConfig(inst *casket.Instance) (*Config, error) {
return nil, err
}
certCache = certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(cert certmagic.Certificate) (certmagic.Config, error) {
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
inst.StorageMu.RLock()
cfgMap, ok := inst.Storage[configMapKey].(map[string]*Config)
inst.StorageMu.RUnlock()
if ok {
for hostname, cfg := range cfgMap {
if cfg.Manager != nil && hostname == cert.Names[0] {
return *cfg.Manager, nil
return cfg.Manager, nil
}
}
}
return certmagic.Default, nil
return certmagic.NewDefault(), nil
},
})

Expand All @@ -144,9 +150,13 @@ func NewConfig(inst *casket.Instance) (*Config, error) {
storageCleaningTicker.Stop()
return
case <-storageCleaningTicker.C:
certmagic.CleanStorage(certmagic.Default.Storage, certmagic.CleanStorageOptions{
err := certmagic.CleanStorage(context.TODO(), certmagic.Default.Storage, certmagic.CleanStorageOptions{
OCSPStaples: true,
})

if err != nil {
fmt.Println("[ERROR] cleaning storage:", err)
}
}
}
}()
Expand All @@ -161,8 +171,14 @@ func NewConfig(inst *casket.Instance) (*Config, error) {
inst.Storage[CertCacheInstStorageKey] = certCache
inst.StorageMu.Unlock()
}

magic := certmagic.New(certCache, certmagic.Config{})
issuer := certmagic.NewACMEIssuer(magic, template)
magic.Issuers = []certmagic.Issuer{issuer}

return &Config{
Manager: certmagic.New(certCache, certmagic.Config{}),
Manager: magic,
Issuer: issuer,
}, nil
}

Expand Down Expand Up @@ -438,11 +454,13 @@ func SetDefaultTLSParams(config *Config) {
}

// Map of supported key types
var supportedKeyTypes = map[string]certcrypto.KeyType{
"P384": certcrypto.EC384,
"P256": certcrypto.EC256,
"RSA4096": certcrypto.RSA4096,
"RSA2048": certcrypto.RSA2048,
var supportedKeyTypes = map[string]certmagic.KeyType{
"ED25519": certmagic.ED25519,
"P256": certmagic.P256,
"P384": certmagic.P384,
"RSA2048": certmagic.RSA2048,
"RSA4096": certmagic.RSA4096,
"RSA8192": certmagic.RSA8192,
}

// SupportedProtocols is a map of supported protocols.
Expand Down
12 changes: 9 additions & 3 deletions caskettls/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"net"
"strings"

"github.com/tmpim/certmagic"
"github.com/caddyserver/certmagic"
"github.com/tmpim/casket/telemetry"
)

Expand All @@ -40,9 +40,9 @@ type configGroup map[string]*Config
// This function follows nearly the same logic to lookup
// a hostname as the getCertificate function uses.
func (cg configGroup) getConfig(hello *tls.ClientHelloInfo) *Config {
name := certmagic.NormalizedName(hello.ServerName)
name := normalizedName(hello.ServerName)
if name == "" {
name = certmagic.NormalizedName(certmagic.Default.DefaultServerName)
name = normalizedName(certmagic.Default.DefaultServerName)
}

// if SNI is empty, prefer matching IP address (it is
Expand Down Expand Up @@ -174,3 +174,9 @@ func (info ClientHelloInfo) Key() string {
// TLS ClientHellos to telemetry. Disable if doing
// it from a different package.
var ClientHelloTelemetry = true

// normalizedName returns a cleaned form of serverName that is
// used for consistency when referring to a SNI value.
func normalizedName(serverName string) string {
return strings.ToLower(strings.TrimSpace(serverName))
}
23 changes: 5 additions & 18 deletions caskettls/selfsigned.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,27 @@ package caskettls

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"github.com/caddyserver/certmagic"
"math/big"
"net"
"strings"
"time"

"github.com/go-acme/lego/v4/certcrypto"
)

// newSelfSignedCertificate returns a new self-signed certificate.
func newSelfSignedCertificate(ssconfig selfSignedConfig) (tls.Certificate, error) {
// start by generating private key
var privKey interface{}
var err error
switch ssconfig.KeyType {
case "", certcrypto.EC256:
privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case certcrypto.EC384:
privKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case certcrypto.RSA2048:
privKey, err = rsa.GenerateKey(rand.Reader, 2048)
case certcrypto.RSA4096:
privKey, err = rsa.GenerateKey(rand.Reader, 4096)
case certcrypto.RSA8192:
privKey, err = rsa.GenerateKey(rand.Reader, 8192)
default:
return tls.Certificate{}, fmt.Errorf("cannot generate private key; unknown key type %v", ssconfig.KeyType)
}

keyGenerator := certmagic.StandardKeyGenerator{KeyType: ssconfig.KeyType}
privKey, err = keyGenerator.GenerateKey()
if err != nil {
return tls.Certificate{}, fmt.Errorf("failed to generate private key: %v", err)
}
Expand Down Expand Up @@ -98,6 +85,6 @@ func newSelfSignedCertificate(ssconfig selfSignedConfig) (tls.Certificate, error
// selfSignedConfig configures a self-signed certificate.
type selfSignedConfig struct {
SAN []string
KeyType certcrypto.KeyType
KeyType certmagic.KeyType
Expire time.Time
}
Loading

0 comments on commit dd12a8a

Please sign in to comment.