2021-12-05 14:21:05 +00:00
package certificates
2021-03-16 23:34:31 +00:00
import (
2021-12-05 17:26:54 +00:00
"context"
2021-07-13 13:45:28 +00:00
"crypto/ecdsa"
"crypto/elliptic"
2021-07-08 23:15:42 +00:00
"crypto/rand"
2021-03-16 23:34:31 +00:00
"crypto/tls"
2021-07-08 23:15:42 +00:00
"crypto/x509"
2021-11-20 14:54:52 +00:00
"encoding/json"
2021-07-13 13:45:28 +00:00
"errors"
2021-12-05 22:20:34 +00:00
"fmt"
2021-07-13 13:45:28 +00:00
"os"
2021-12-01 21:49:48 +00:00
"strconv"
2021-07-08 23:15:42 +00:00
"strings"
2021-11-20 14:54:52 +00:00
"sync"
2021-07-08 23:15:42 +00:00
"time"
2021-07-13 13:45:28 +00:00
"github.com/go-acme/lego/v4/certcrypto"
2021-12-03 02:44:21 +00:00
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
2021-07-13 13:45:28 +00:00
"github.com/go-acme/lego/v4/lego"
2021-12-03 02:44:21 +00:00
"github.com/go-acme/lego/v4/providers/dns"
2021-07-13 13:45:28 +00:00
"github.com/go-acme/lego/v4/registration"
2021-12-05 15:33:56 +00:00
"github.com/reugn/equalizer"
"github.com/rs/zerolog/log"
2021-12-03 02:44:21 +00:00
2021-12-03 03:15:48 +00:00
"codeberg.org/codeberg/pages/server/cache"
2021-12-03 02:44:21 +00:00
"codeberg.org/codeberg/pages/server/database"
2021-12-05 14:21:05 +00:00
dnsutils "codeberg.org/codeberg/pages/server/dns"
2022-06-11 21:02:06 +00:00
"codeberg.org/codeberg/pages/server/gitea"
2021-12-05 14:21:05 +00:00
"codeberg.org/codeberg/pages/server/upstream"
2021-03-16 23:34:31 +00:00
)
2021-12-03 03:15:48 +00:00
// TLSConfig returns the configuration for generating, serving and cleaning up Let's Encrypt certificates.
2022-11-12 19:37:20 +00:00
func TLSConfig ( mainDomainSuffix string ,
2022-06-11 21:02:06 +00:00
giteaClient * gitea . Client ,
dnsProvider string ,
2021-12-05 14:02:44 +00:00
acmeUseRateLimits bool ,
keyCache , challengeCache , dnsLookupCache , canonicalDomainCache cache . SetGetKey ,
2022-03-27 19:54:06 +00:00
certDB database . CertDB ,
) * tls . Config {
2021-12-05 13:45:17 +00:00
return & tls . Config {
// check DNS name & get certificate from Let's Encrypt
GetCertificate : func ( info * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
sni := strings . ToLower ( strings . TrimSpace ( info . ServerName ) )
if len ( sni ) < 1 {
return nil , errors . New ( "missing sni" )
}
2021-07-13 13:45:28 +00:00
2021-12-05 13:45:17 +00:00
if info . SupportedProtos != nil {
for _ , proto := range info . SupportedProtos {
2022-11-15 15:15:11 +00:00
if proto != tlsalpn01 . ACMETLS1Protocol {
continue
2021-07-13 13:45:28 +00:00
}
2022-11-15 15:15:11 +00:00
challenge , ok := challengeCache . Get ( sni )
if ! ok {
return nil , errors . New ( "no challenge for this domain" )
}
cert , err := tlsalpn01 . ChallengeCert ( sni , challenge . ( string ) )
if err != nil {
return nil , err
}
return cert , nil
2021-07-13 13:45:28 +00:00
}
}
2021-12-05 13:45:17 +00:00
targetOwner := ""
2023-02-10 01:38:15 +00:00
mayObtainCert := true
2022-11-12 19:37:20 +00:00
if strings . HasSuffix ( sni , mainDomainSuffix ) || strings . EqualFold ( sni , mainDomainSuffix [ 1 : ] ) {
2021-12-05 13:45:17 +00:00
// deliver default certificate for the main domain (*.codeberg.page)
2022-11-12 19:37:20 +00:00
sni = mainDomainSuffix
2021-11-20 14:30:58 +00:00
} else {
2021-12-05 13:45:17 +00:00
var targetRepo , targetBranch string
2022-11-12 19:37:20 +00:00
targetOwner , targetRepo , targetBranch = dnsutils . GetTargetFromDNS ( sni , mainDomainSuffix , dnsLookupCache )
2021-12-05 13:45:17 +00:00
if targetOwner == "" {
// DNS not set up, return main certificate to redirect to the docs
2022-11-12 19:37:20 +00:00
sni = mainDomainSuffix
2021-12-05 13:45:17 +00:00
} else {
2022-11-12 19:43:44 +00:00
targetOpt := & upstream . Options {
TargetOwner : targetOwner ,
TargetRepo : targetRepo ,
TargetBranch : targetBranch ,
}
_ , valid := targetOpt . CheckCanonicalDomain ( giteaClient , sni , mainDomainSuffix , canonicalDomainCache )
2021-12-05 13:45:17 +00:00
if ! valid {
2023-02-10 01:38:15 +00:00
// We shouldn't obtain a certificate when we cannot check if the
// repository has specified this domain in the `.domains` file.
mayObtainCert = false
2021-12-05 13:45:17 +00:00
}
2021-11-20 14:30:58 +00:00
}
2021-07-13 13:45:28 +00:00
}
2021-12-05 13:45:17 +00:00
if tlsCertificate , ok := keyCache . Get ( sni ) ; ok {
// we can use an existing certificate object
return tlsCertificate . ( * tls . Certificate ) , nil
}
2023-02-10 03:00:14 +00:00
var tlsCertificate * tls . Certificate
2021-12-05 13:45:17 +00:00
var err error
2023-02-10 03:00:14 +00:00
if tlsCertificate , err = retrieveCertFromDB ( sni , mainDomainSuffix , dnsProvider , acmeUseRateLimits , certDB ) ; err != nil {
2021-12-05 13:45:17 +00:00
// request a new certificate
2022-11-12 19:37:20 +00:00
if strings . EqualFold ( sni , mainDomainSuffix ) {
2021-12-05 13:45:17 +00:00
return nil , errors . New ( "won't request certificate for main domain, something really bad has happened" )
}
2021-11-20 14:30:58 +00:00
2023-02-10 01:38:15 +00:00
if ! mayObtainCert {
return nil , fmt . Errorf ( "won't request certificate for %q" , sni )
}
2021-12-05 18:02:26 +00:00
tlsCertificate , err = obtainCert ( acmeClient , [ ] string { sni } , nil , targetOwner , dnsProvider , mainDomainSuffix , acmeUseRateLimits , certDB )
2021-12-05 13:45:17 +00:00
if err != nil {
return nil , err
}
2021-07-13 13:45:28 +00:00
}
2021-11-20 14:30:58 +00:00
2023-02-10 03:00:14 +00:00
if err := keyCache . Set ( sni , tlsCertificate , 15 * time . Minute ) ; err != nil {
2021-12-05 22:20:34 +00:00
return nil , err
2021-07-13 13:45:28 +00:00
}
2023-02-10 03:00:14 +00:00
return tlsCertificate , nil
2021-12-05 13:45:17 +00:00
} ,
NextProtos : [ ] string {
2022-11-12 21:25:20 +00:00
"h2" ,
2021-12-05 13:45:17 +00:00
"http/1.1" ,
tlsalpn01 . ACMETLS1Protocol ,
} ,
2021-07-13 13:45:28 +00:00
2021-12-05 13:45:17 +00:00
// generated 2021-07-13, Mozilla Guideline v5.6, Go 1.14.4, intermediate configuration
// https://ssl-config.mozilla.org/#server=go&version=1.14.4&config=intermediate&guideline=5.6
MinVersion : tls . VersionTLS12 ,
CipherSuites : [ ] uint16 {
tls . TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ,
tls . TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 ,
tls . TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,
tls . TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 ,
tls . TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 ,
} ,
}
2021-07-13 08:28:36 +00:00
}
2021-12-05 15:24:26 +00:00
func checkUserLimit ( user string ) error {
2021-11-20 14:30:58 +00:00
userLimit , ok := acmeClientCertificateLimitPerUser [ user ]
if ! ok {
// Each Codeberg user can only add 10 new domains per day.
2021-11-25 15:12:28 +00:00
userLimit = equalizer . NewTokenBucket ( 10 , time . Hour * 24 )
2021-11-20 14:30:58 +00:00
acmeClientCertificateLimitPerUser [ user ] = userLimit
2021-07-13 08:28:36 +00:00
}
2021-11-20 14:30:58 +00:00
if ! userLimit . Ask ( ) {
return errors . New ( "rate limit exceeded: 10 certificates per user per 24 hours" )
2021-07-13 08:28:36 +00:00
}
2021-11-20 14:30:58 +00:00
return nil
2021-07-13 08:28:36 +00:00
}
2021-07-13 13:45:28 +00:00
2022-03-27 19:54:06 +00:00
var (
acmeClient , mainDomainAcmeClient * lego . Client
acmeClientCertificateLimitPerUser = map [ string ] * equalizer . TokenBucket { }
)
2021-07-13 13:45:28 +00:00
2021-11-20 18:36:12 +00:00
// rate limit is 300 / 3 hours, we want 200 / 2 hours but to refill more often, so that's 25 new domains every 15 minutes
// TODO: when this is used a lot, we probably have to think of a somewhat better solution?
2021-11-25 15:12:28 +00:00
var acmeClientOrderLimit = equalizer . NewTokenBucket ( 25 , 15 * time . Minute )
2021-11-20 18:36:12 +00:00
2021-12-01 15:23:37 +00:00
// rate limit is 20 / second, we want 5 / second (especially as one cert takes at least two requests)
var acmeClientRequestLimit = equalizer . NewTokenBucket ( 5 , 1 * time . Second )
2021-11-20 18:36:12 +00:00
2023-01-04 05:26:14 +00:00
// rate limit is 5 / hour https://letsencrypt.org/docs/failed-validation-limit/
var acmeClientFailLimit = equalizer . NewTokenBucket ( 5 , 1 * time . Hour )
2021-12-03 03:32:30 +00:00
type AcmeTLSChallengeProvider struct {
challengeCache cache . SetGetKey
}
2021-11-25 15:12:28 +00:00
2021-12-03 03:32:30 +00:00
// make sure AcmeTLSChallengeProvider match Provider interface
2021-07-13 13:45:28 +00:00
var _ challenge . Provider = AcmeTLSChallengeProvider { }
2021-11-25 15:12:28 +00:00
2021-07-13 13:45:28 +00:00
func ( a AcmeTLSChallengeProvider ) Present ( domain , _ , keyAuth string ) error {
2021-12-03 03:32:30 +00:00
return a . challengeCache . Set ( domain , keyAuth , 1 * time . Hour )
2021-07-13 13:45:28 +00:00
}
2022-03-27 19:54:06 +00:00
2021-07-13 13:45:28 +00:00
func ( a AcmeTLSChallengeProvider ) CleanUp ( domain , _ , _ string ) error {
2021-12-03 03:32:30 +00:00
a . challengeCache . Remove ( domain )
2021-07-13 13:45:28 +00:00
return nil
}
2021-11-25 15:12:28 +00:00
2021-12-03 03:32:30 +00:00
type AcmeHTTPChallengeProvider struct {
challengeCache cache . SetGetKey
}
2021-11-25 15:12:28 +00:00
2021-12-03 03:32:30 +00:00
// make sure AcmeHTTPChallengeProvider match Provider interface
2021-11-20 20:10:46 +00:00
var _ challenge . Provider = AcmeHTTPChallengeProvider { }
2021-11-25 15:12:28 +00:00
2021-11-20 20:10:46 +00:00
func ( a AcmeHTTPChallengeProvider ) Present ( domain , token , keyAuth string ) error {
2021-12-03 03:32:30 +00:00
return a . challengeCache . Set ( domain + "/" + token , keyAuth , 1 * time . Hour )
2021-11-20 20:10:46 +00:00
}
2022-03-27 19:54:06 +00:00
2021-11-20 20:10:46 +00:00
func ( a AcmeHTTPChallengeProvider ) CleanUp ( domain , token , _ string ) error {
2021-12-03 03:32:30 +00:00
a . challengeCache . Remove ( domain + "/" + token )
2021-11-20 20:10:46 +00:00
return nil
}
2021-07-13 13:45:28 +00:00
2023-02-10 03:00:14 +00:00
func retrieveCertFromDB ( sni , mainDomainSuffix , dnsProvider string , acmeUseRateLimits bool , certDB database . CertDB ) ( * tls . Certificate , error ) {
2021-11-20 14:54:52 +00:00
// parse certificate from database
2022-11-15 15:15:11 +00:00
res , err := certDB . Get ( sni )
2021-12-05 18:00:57 +00:00
if err != nil {
2023-02-10 03:00:14 +00:00
return nil , err
} else if res == nil {
return nil , database . ErrNotFound
2021-11-20 14:54:52 +00:00
}
2021-11-20 18:36:12 +00:00
tlsCertificate , err := tls . X509KeyPair ( res . Certificate , res . PrivateKey )
2021-11-20 14:54:52 +00:00
if err != nil {
2023-02-10 03:00:14 +00:00
return nil , err
2021-11-20 14:54:52 +00:00
}
2021-11-20 18:36:12 +00:00
2021-12-02 18:12:45 +00:00
// TODO: document & put into own function
2022-11-12 19:37:20 +00:00
if ! strings . EqualFold ( sni , mainDomainSuffix ) {
2021-11-20 18:36:12 +00:00
tlsCertificate . Leaf , err = x509 . ParseCertificate ( tlsCertificate . Certificate [ 0 ] )
if err != nil {
2023-02-10 03:00:14 +00:00
return nil , fmt . Errorf ( "error parsin leaf tlsCert: %w" , err )
2021-11-20 18:36:12 +00:00
}
// renew certificates 7 days before they expire
2022-11-15 15:15:11 +00:00
if tlsCertificate . Leaf . NotAfter . Before ( time . Now ( ) . Add ( 7 * 24 * time . Hour ) ) {
2023-02-10 03:00:14 +00:00
// TODO: use ValidTill of custom cert struct
2021-12-01 21:49:48 +00:00
if res . CSR != nil && len ( res . CSR ) > 0 {
// CSR stores the time when the renewal shall be tried again
nextTryUnix , err := strconv . ParseInt ( string ( res . CSR ) , 10 , 64 )
if err == nil && time . Now ( ) . Before ( time . Unix ( nextTryUnix , 0 ) ) {
2023-02-10 03:00:14 +00:00
return & tlsCertificate , nil
2021-12-01 21:49:48 +00:00
}
}
2023-02-10 03:00:14 +00:00
// TODO: make a queue ?
2021-11-20 18:36:12 +00:00
go ( func ( ) {
2021-12-01 21:49:48 +00:00
res . CSR = nil // acme client doesn't like CSR to be set
2023-02-10 03:00:14 +00:00
if _ , err := obtainCert ( acmeClient , [ ] string { sni } , res , "" , dnsProvider , mainDomainSuffix , acmeUseRateLimits , certDB ) ; err != nil {
2022-11-15 15:15:11 +00:00
log . Error ( ) . Msgf ( "Couldn't renew certificate for %s: %v" , sni , err )
2021-11-20 18:36:12 +00:00
}
} ) ( )
}
}
2023-02-10 03:00:14 +00:00
return & tlsCertificate , nil
2021-11-20 14:54:52 +00:00
}
var obtainLocks = sync . Map { }
2021-11-25 15:12:28 +00:00
2023-02-10 03:00:14 +00:00
func obtainCert ( acmeClient * lego . Client , domains [ ] string , renew * certificate . Resource , user , dnsProvider , mainDomainSuffix string , acmeUseRateLimits bool , keyDatabase database . CertDB ) ( * tls . Certificate , error ) {
2021-11-20 14:54:52 +00:00
name := strings . TrimPrefix ( domains [ 0 ] , "*" )
2021-12-03 02:34:50 +00:00
if dnsProvider == "" && len ( domains [ 0 ] ) > 0 && domains [ 0 ] [ 0 ] == '*' {
2021-11-20 14:30:58 +00:00
domains = domains [ 1 : ]
2021-08-22 15:59:30 +00:00
}
2021-07-13 13:45:28 +00:00
2021-11-20 14:54:52 +00:00
// lock to avoid simultaneous requests
_ , working := obtainLocks . LoadOrStore ( name , struct { } { } )
if working {
for working {
time . Sleep ( 100 * time . Millisecond )
_ , working = obtainLocks . Load ( name )
}
2023-02-10 03:00:14 +00:00
cert , err := retrieveCertFromDB ( name , mainDomainSuffix , dnsProvider , acmeUseRateLimits , keyDatabase )
if err != nil {
return nil , fmt . Errorf ( "certificate failed in synchronous request: %w" , err )
2021-11-20 14:54:52 +00:00
}
return cert , nil
}
defer obtainLocks . Delete ( name )
2021-12-01 15:23:37 +00:00
if acmeClient == nil {
2023-02-10 03:00:14 +00:00
return mockCert ( domains [ 0 ] , "ACME client uninitialized. This is a server error, please report!" , mainDomainSuffix , keyDatabase )
2021-12-01 15:23:37 +00:00
}
2021-11-20 18:36:12 +00:00
// request actual cert
var res * certificate . Resource
var err error
2021-12-01 15:23:37 +00:00
if renew != nil && renew . CertURL != "" {
2021-12-03 02:34:50 +00:00
if acmeUseRateLimits {
2021-11-20 20:12:28 +00:00
acmeClientRequestLimit . Take ( )
}
2022-08-12 03:06:26 +00:00
log . Debug ( ) . Msgf ( "Renewing certificate for: %v" , domains )
2021-11-20 18:36:12 +00:00
res , err = acmeClient . Certificate . Renew ( * renew , true , false , "" )
2021-12-01 15:23:37 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msgf ( "Couldn't renew certificate for %v, trying to request a new one" , domains )
2023-01-04 05:26:14 +00:00
if acmeUseRateLimits {
acmeClientFailLimit . Take ( )
}
2021-12-01 15:23:37 +00:00
res = nil
}
}
if res == nil {
2021-11-20 20:39:40 +00:00
if user != "" {
2021-12-05 15:24:26 +00:00
if err := checkUserLimit ( user ) ; err != nil {
2023-02-10 03:00:14 +00:00
return nil , err
2021-11-20 20:39:40 +00:00
}
}
2021-12-03 02:34:50 +00:00
if acmeUseRateLimits {
2021-11-20 20:12:28 +00:00
acmeClientOrderLimit . Take ( )
acmeClientRequestLimit . Take ( )
}
2022-08-12 03:06:26 +00:00
log . Debug ( ) . Msgf ( "Re-requesting new certificate for %v" , domains )
2021-11-20 18:36:12 +00:00
res , err = acmeClient . Certificate . Obtain ( certificate . ObtainRequest {
Domains : domains ,
Bundle : true ,
MustStaple : false ,
} )
2023-01-04 05:26:14 +00:00
if acmeUseRateLimits && err != nil {
acmeClientFailLimit . Take ( )
}
2021-11-20 18:36:12 +00:00
}
2021-11-20 14:30:58 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msgf ( "Couldn't obtain again a certificate or %v" , domains )
2021-12-01 21:49:48 +00:00
if renew != nil && renew . CertURL != "" {
tlsCertificate , err := tls . X509KeyPair ( renew . Certificate , renew . PrivateKey )
2023-01-04 04:51:27 +00:00
if err != nil {
2023-02-10 03:00:14 +00:00
mockC , err2 := mockCert ( domains [ 0 ] , err . Error ( ) , mainDomainSuffix , keyDatabase )
if err2 != nil {
return nil , errors . Join ( err , err2 )
}
return mockC , err
2023-01-04 04:51:27 +00:00
}
leaf , err := leaf ( & tlsCertificate )
if err == nil && leaf . NotAfter . After ( time . Now ( ) ) {
2021-12-01 21:49:48 +00:00
// avoid sending a mock cert instead of a still valid cert, instead abuse CSR field to store time to try again at
2021-12-01 21:59:52 +00:00
renew . CSR = [ ] byte ( strconv . FormatInt ( time . Now ( ) . Add ( 6 * time . Hour ) . Unix ( ) , 10 ) )
2021-12-05 18:00:57 +00:00
if err := keyDatabase . Put ( name , renew ) ; err != nil {
2023-02-10 03:00:14 +00:00
mockC , err2 := mockCert ( domains [ 0 ] , err . Error ( ) , mainDomainSuffix , keyDatabase )
if err2 != nil {
return nil , errors . Join ( err , err2 )
}
return mockC , err
2021-12-05 18:00:57 +00:00
}
2023-02-10 03:00:14 +00:00
return & tlsCertificate , nil
2021-12-01 21:49:48 +00:00
}
}
2023-02-10 03:00:14 +00:00
return mockCert ( domains [ 0 ] , err . Error ( ) , mainDomainSuffix , keyDatabase )
2021-08-22 15:59:30 +00:00
}
2022-08-12 03:06:26 +00:00
log . Debug ( ) . Msgf ( "Obtained certificate for %v" , domains )
2021-08-22 15:59:30 +00:00
2021-12-05 18:00:57 +00:00
if err := keyDatabase . Put ( name , res ) ; err != nil {
2023-02-10 03:00:14 +00:00
return nil , err
2021-12-05 18:00:57 +00:00
}
2021-11-20 14:30:58 +00:00
tlsCertificate , err := tls . X509KeyPair ( res . Certificate , res . PrivateKey )
2021-07-13 13:45:28 +00:00
if err != nil {
2023-02-10 03:00:14 +00:00
return nil , err
2021-07-13 13:45:28 +00:00
}
2023-02-10 03:00:14 +00:00
return & tlsCertificate , nil
2021-08-22 15:59:30 +00:00
}
2021-12-05 15:33:56 +00:00
func SetupAcmeConfig ( acmeAPI , acmeMail , acmeEabHmac , acmeEabKID string , acmeAcceptTerms bool ) ( * lego . Config , error ) {
2023-02-10 03:00:14 +00:00
// TODO: make it a config flag
2021-12-05 15:33:56 +00:00
const configFile = "acme-account.json"
var myAcmeAccount AcmeAccount
var myAcmeConfig * lego . Config
2021-12-01 15:23:37 +00:00
2022-08-08 13:25:31 +00:00
if account , err := os . ReadFile ( configFile ) ; err == nil {
2021-12-05 22:20:34 +00:00
if err := json . Unmarshal ( account , & myAcmeAccount ) ; err != nil {
return nil , err
2021-11-20 14:54:52 +00:00
}
myAcmeAccount . Key , err = certcrypto . ParsePEMPrivateKey ( [ ] byte ( myAcmeAccount . KeyPEM ) )
if err != nil {
2021-12-05 22:20:34 +00:00
return nil , err
2021-11-20 14:54:52 +00:00
}
myAcmeConfig = lego . NewConfig ( & myAcmeAccount )
2021-12-03 02:05:38 +00:00
myAcmeConfig . CADirURL = acmeAPI
2021-11-20 14:54:52 +00:00
myAcmeConfig . Certificate . KeyType = certcrypto . RSA2048
2021-12-05 15:33:56 +00:00
// Validate Config
2021-12-01 15:23:37 +00:00
_ , err := lego . NewClient ( myAcmeConfig )
if err != nil {
2021-12-05 15:33:56 +00:00
// TODO: should we fail hard instead?
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't create ACME client, continuing with mock certs only" )
2021-12-01 15:23:37 +00:00
}
2021-12-05 15:33:56 +00:00
return myAcmeConfig , nil
} else if ! os . IsNotExist ( err ) {
return nil , err
}
privateKey , err := ecdsa . GenerateKey ( elliptic . P256 ( ) , rand . Reader )
if err != nil {
2021-12-05 22:20:34 +00:00
return nil , err
2021-12-05 15:33:56 +00:00
}
myAcmeAccount = AcmeAccount {
Email : acmeMail ,
Key : privateKey ,
KeyPEM : string ( certcrypto . PEMEncode ( privateKey ) ) ,
}
myAcmeConfig = lego . NewConfig ( & myAcmeAccount )
myAcmeConfig . CADirURL = acmeAPI
myAcmeConfig . Certificate . KeyType = certcrypto . RSA2048
tempClient , err := lego . NewClient ( myAcmeConfig )
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't create ACME client, continuing with mock certs only" )
2021-12-05 15:33:56 +00:00
} else {
// accept terms & log in to EAB
if acmeEabKID == "" || acmeEabHmac == "" {
reg , err := tempClient . Registration . Register ( registration . RegisterOptions { TermsOfServiceAgreed : acmeAcceptTerms } )
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't register ACME account, continuing with mock certs only" )
2021-12-05 15:33:56 +00:00
} else {
myAcmeAccount . Registration = reg
}
2021-11-20 14:54:52 +00:00
} else {
2021-12-05 15:33:56 +00:00
reg , err := tempClient . Registration . RegisterWithExternalAccountBinding ( registration . RegisterEABOptions {
TermsOfServiceAgreed : acmeAcceptTerms ,
Kid : acmeEabKID ,
HmacEncoded : acmeEabHmac ,
} )
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't register ACME account, continuing with mock certs only" )
2021-12-01 15:23:37 +00:00
} else {
2021-12-05 15:33:56 +00:00
myAcmeAccount . Registration = reg
2021-11-20 14:54:52 +00:00
}
2021-12-05 15:33:56 +00:00
}
2021-11-20 14:54:52 +00:00
2021-12-05 15:33:56 +00:00
if myAcmeAccount . Registration != nil {
2021-12-05 22:20:34 +00:00
acmeAccountJSON , err := json . Marshal ( myAcmeAccount )
2021-12-05 15:33:56 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "json.Marshalfailed, waiting for manual restart to avoid rate limits" )
2021-12-05 15:33:56 +00:00
select { }
}
2022-08-08 13:25:31 +00:00
err = os . WriteFile ( configFile , acmeAccountJSON , 0 o600 )
2021-12-05 15:33:56 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "os.WriteFile failed, waiting for manual restart to avoid rate limits" )
2021-12-05 15:33:56 +00:00
select { }
2021-12-01 15:23:37 +00:00
}
2021-11-20 14:54:52 +00:00
}
2021-12-05 15:33:56 +00:00
}
return myAcmeConfig , nil
}
2022-11-12 19:37:20 +00:00
func SetupCertificates ( mainDomainSuffix , dnsProvider string , acmeConfig * lego . Config , acmeUseRateLimits , enableHTTPServer bool , challengeCache cache . SetGetKey , certDB database . CertDB ) error {
2021-12-05 22:20:34 +00:00
// getting main cert before ACME account so that we can fail here without hitting rate limits
2022-11-15 15:15:11 +00:00
mainCertBytes , err := certDB . Get ( mainDomainSuffix )
2023-02-10 03:00:14 +00:00
if err != nil && ! errors . Is ( err , database . ErrNotFound ) {
return fmt . Errorf ( "cert database is not working: %w" , err )
2021-11-20 14:54:52 +00:00
}
2021-12-05 15:33:56 +00:00
acmeClient , err = lego . NewClient ( acmeConfig )
2021-12-01 15:23:37 +00:00
if err != nil {
2022-11-07 15:27:37 +00:00
log . Fatal ( ) . Err ( err ) . Msg ( "Can't create ACME client, continuing with mock certs only" )
2021-12-01 15:23:37 +00:00
} else {
2021-12-03 03:32:30 +00:00
err = acmeClient . Challenge . SetTLSALPN01Provider ( AcmeTLSChallengeProvider { challengeCache } )
2021-11-20 20:10:46 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't create TLS-ALPN-01 provider" )
2021-11-20 20:10:46 +00:00
}
2021-12-03 02:34:50 +00:00
if enableHTTPServer {
2021-12-03 03:32:30 +00:00
err = acmeClient . Challenge . SetHTTP01Provider ( AcmeHTTPChallengeProvider { challengeCache } )
2021-12-01 15:23:37 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't create HTTP-01 provider" )
2021-12-01 15:23:37 +00:00
}
2021-11-20 20:10:46 +00:00
}
2021-12-01 15:23:37 +00:00
}
2021-12-05 15:33:56 +00:00
mainDomainAcmeClient , err = lego . NewClient ( acmeConfig )
2021-12-01 15:23:37 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't create ACME client, continuing with mock certs only" )
2021-12-01 15:23:37 +00:00
} else {
2021-12-03 02:34:50 +00:00
if dnsProvider == "" {
2021-11-20 14:54:52 +00:00
// using mock server, don't use wildcard certs
2021-12-03 03:32:30 +00:00
err := mainDomainAcmeClient . Challenge . SetTLSALPN01Provider ( AcmeTLSChallengeProvider { challengeCache } )
2021-12-01 15:23:37 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't create TLS-ALPN-01 provider" )
2021-12-01 15:23:37 +00:00
}
} else {
2021-12-03 02:34:50 +00:00
provider , err := dns . NewDNSChallengeProviderByName ( dnsProvider )
2021-12-01 15:23:37 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't create DNS Challenge provider" )
2021-12-01 15:23:37 +00:00
}
err = mainDomainAcmeClient . Challenge . SetDNS01Provider ( provider )
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Can't create DNS-01 provider" )
2021-12-01 15:23:37 +00:00
}
2021-11-20 14:54:52 +00:00
}
2021-12-01 15:23:37 +00:00
}
2021-11-20 14:54:52 +00:00
2021-12-01 15:23:37 +00:00
if mainCertBytes == nil {
2022-11-15 15:15:11 +00:00
_ , err = obtainCert ( mainDomainAcmeClient , [ ] string { "*" + mainDomainSuffix , mainDomainSuffix [ 1 : ] } , nil , "" , dnsProvider , mainDomainSuffix , acmeUseRateLimits , certDB )
2021-11-20 18:36:12 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Couldn't renew main domain certificate, continuing with mock certs only" )
2021-11-20 18:36:12 +00:00
}
}
2021-12-05 22:20:34 +00:00
return nil
2021-12-05 16:44:10 +00:00
}
2021-11-20 18:36:12 +00:00
2022-11-12 19:37:20 +00:00
func MaintainCertDB ( ctx context . Context , interval time . Duration , mainDomainSuffix , dnsProvider string , acmeUseRateLimits bool , certDB database . CertDB ) {
2021-12-05 16:44:10 +00:00
for {
2023-02-10 03:00:14 +00:00
// delete expired certs that will be invalid until next clean up
threshold := time . Now ( ) . Add ( interval )
2021-12-05 16:44:10 +00:00
expiredCertCount := 0
2021-11-20 18:36:12 +00:00
2023-02-10 03:00:14 +00:00
certs , err := certDB . Items ( 0 , 0 )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "could not get certs from list" )
} else {
for _ , cert := range certs {
if ! strings . EqualFold ( cert . Domain , strings . TrimPrefix ( mainDomainSuffix , "." ) ) {
if time . Unix ( cert . ValidTill , 0 ) . Before ( threshold ) {
err := certDB . Delete ( cert . Domain )
if err != nil {
log . Error ( ) . Err ( err ) . Msgf ( "Deleting expired certificate for %q failed" , cert . Domain )
} else {
expiredCertCount ++
}
2021-11-20 14:30:58 +00:00
}
}
}
2023-02-10 03:00:14 +00:00
log . Debug ( ) . Msgf ( "Removed %d expired certificates from the database" , expiredCertCount )
2021-11-20 14:30:58 +00:00
2023-02-10 03:00:14 +00:00
// compact the database
msg , err := certDB . Compact ( )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "Compacting key database failed" )
} else {
log . Debug ( ) . Msgf ( "Compacted key database: %s" , msg )
}
2021-12-05 16:44:10 +00:00
}
2021-11-20 14:30:58 +00:00
2021-12-05 16:44:10 +00:00
// update main cert
2022-11-15 15:15:11 +00:00
res , err := certDB . Get ( mainDomainSuffix )
2021-12-05 18:00:57 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Msgf ( "Couldn't get cert for domain %q" , mainDomainSuffix )
2021-12-05 18:00:57 +00:00
} else if res == nil {
2022-11-15 15:15:11 +00:00
log . Error ( ) . Msgf ( "Couldn't renew certificate for main domain %q expected main domain cert to exist, but it's missing - seems like the database is corrupted" , mainDomainSuffix )
2021-12-05 16:44:10 +00:00
} else {
tlsCertificates , err := certcrypto . ParsePEMBundle ( res . Certificate )
2023-02-10 03:00:14 +00:00
if err != nil {
log . Error ( ) . Err ( fmt . Errorf ( "could not parse cert for mainDomainSuffix: %w" , err ) )
} else if tlsCertificates [ 0 ] . NotAfter . Before ( time . Now ( ) . Add ( 30 * 24 * time . Hour ) ) {
// renew main certificate 30 days before it expires
2021-12-05 16:44:10 +00:00
go ( func ( ) {
2022-11-15 15:15:11 +00:00
_ , err = obtainCert ( mainDomainAcmeClient , [ ] string { "*" + mainDomainSuffix , mainDomainSuffix [ 1 : ] } , res , "" , dnsProvider , mainDomainSuffix , acmeUseRateLimits , certDB )
2021-12-05 16:44:10 +00:00
if err != nil {
2022-08-12 03:06:26 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Couldn't renew certificate for main domain" )
2021-12-05 16:44:10 +00:00
}
} ) ( )
2021-11-20 14:30:58 +00:00
}
2021-07-13 13:45:28 +00:00
}
2021-12-05 16:44:10 +00:00
2021-12-05 17:26:54 +00:00
select {
case <- ctx . Done ( ) :
return
case <- time . After ( interval ) :
}
2021-12-05 16:44:10 +00:00
}
2021-07-13 13:45:28 +00:00
}
2023-01-04 04:51:27 +00:00
// leaf returns the parsed leaf certificate, either from c.leaf or by parsing
// the corresponding c.Certificate[0].
func leaf ( c * tls . Certificate ) ( * x509 . Certificate , error ) {
if c . Leaf != nil {
return c . Leaf , nil
}
return x509 . ParseCertificate ( c . Certificate [ 0 ] )
}