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-03-16 23:34:31 +00:00
"crypto/tls"
2021-07-08 23:15:42 +00:00
"crypto/x509"
2021-07-13 13:45:28 +00:00
"errors"
2021-12-05 22:20:34 +00:00
"fmt"
2021-12-01 21:49:48 +00:00
"strconv"
2021-07-08 23:15:42 +00:00
"strings"
"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/tlsalpn01"
2021-07-13 13:45:28 +00:00
"github.com/go-acme/lego/v4/lego"
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
)
2023-02-11 02:29:08 +00:00
var ErrUserRateLimitExceeded = errors . New ( "rate limit exceeded: 10 certificates per user per 24 hours" )
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 ,
2023-02-11 02:29:08 +00:00
acmeClient * AcmeClient ,
2021-12-05 14:02:44 +00:00
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-11 02:29:08 +00:00
if tlsCertificate , err = acmeClient . retrieveCertFromDB ( sni , mainDomainSuffix , false , 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 )
}
2023-02-11 02:29:08 +00:00
tlsCertificate , err = acmeClient . obtainCert ( acmeClient . legoClient , [ ] string { sni } , nil , targetOwner , false , mainDomainSuffix , 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
}
2023-02-11 02:29:08 +00:00
func ( c * AcmeClient ) checkUserLimit ( user string ) error {
userLimit , ok := c . acmeClientCertificateLimitPerUser [ user ]
2021-11-20 14:30:58 +00:00
if ! ok {
2023-02-11 02:29:08 +00:00
// Each user can only add 10 new domains per day.
2021-11-25 15:12:28 +00:00
userLimit = equalizer . NewTokenBucket ( 10 , time . Hour * 24 )
2023-02-11 02:29:08 +00:00
c . acmeClientCertificateLimitPerUser [ user ] = userLimit
2021-07-13 08:28:36 +00:00
}
2021-11-20 14:30:58 +00:00
if ! userLimit . Ask ( ) {
2023-02-11 02:29:08 +00:00
return fmt . Errorf ( "user '%s' error: %w" , user , ErrUserRateLimitExceeded )
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
2023-02-11 02:29:08 +00:00
func ( c * AcmeClient ) retrieveCertFromDB ( sni , mainDomainSuffix string , useDnsProvider 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-11 02:29:08 +00:00
if _ , err := c . obtainCert ( c . legoClient , [ ] string { sni } , res , "" , useDnsProvider , mainDomainSuffix , 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
}
2023-02-11 02:29:08 +00:00
func ( c * AcmeClient ) obtainCert ( acmeClient * lego . Client , domains [ ] string , renew * certificate . Resource , user string , useDnsProvider bool , mainDomainSuffix string , keyDatabase database . CertDB ) ( * tls . Certificate , error ) {
2021-11-20 14:54:52 +00:00
name := strings . TrimPrefix ( domains [ 0 ] , "*" )
2023-02-11 02:29:08 +00:00
if useDnsProvider && 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
2023-02-11 02:29:08 +00:00
_ , working := c . obtainLocks . LoadOrStore ( name , struct { } { } )
2021-11-20 14:54:52 +00:00
if working {
for working {
time . Sleep ( 100 * time . Millisecond )
2023-02-11 02:29:08 +00:00
_ , working = c . obtainLocks . Load ( name )
2021-11-20 14:54:52 +00:00
}
2023-02-11 02:29:08 +00:00
cert , err := c . retrieveCertFromDB ( name , mainDomainSuffix , useDnsProvider , keyDatabase )
2023-02-10 03:00:14 +00:00
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
}
2023-02-11 02:29:08 +00:00
defer c . obtainLocks . Delete ( name )
2021-11-20 14:54:52 +00:00
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 != "" {
2023-02-11 02:29:08 +00:00
if c . acmeUseRateLimits {
c . acmeClientRequestLimit . Take ( )
2021-11-20 20:12:28 +00:00
}
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-02-11 02:29:08 +00:00
if c . acmeUseRateLimits {
c . acmeClientFailLimit . Take ( )
2023-01-04 05:26:14 +00:00
}
2021-12-01 15:23:37 +00:00
res = nil
}
}
if res == nil {
2021-11-20 20:39:40 +00:00
if user != "" {
2023-02-11 02:29:08 +00:00
if err := c . checkUserLimit ( user ) ; err != nil {
2023-02-10 03:00:14 +00:00
return nil , err
2021-11-20 20:39:40 +00:00
}
}
2023-02-11 02:29:08 +00:00
if c . acmeUseRateLimits {
c . acmeClientOrderLimit . Take ( )
c . acmeClientRequestLimit . Take ( )
2021-11-20 20:12:28 +00:00
}
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-02-11 02:29:08 +00:00
if c . acmeUseRateLimits && err != nil {
c . acmeClientFailLimit . Take ( )
2023-01-04 05:26:14 +00:00
}
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
}
2023-02-11 02:29:08 +00:00
func SetupMainDomainCertificates ( mainDomainSuffix string , acmeClient * AcmeClient , 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-01 15:23:37 +00:00
if mainCertBytes == nil {
2023-02-11 02:29:08 +00:00
_ , err = acmeClient . obtainCert ( acmeClient . dnsChallengerLegoClient , [ ] string { "*" + mainDomainSuffix , mainDomainSuffix [ 1 : ] } , nil , "" , true , mainDomainSuffix , 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
2023-02-11 02:29:08 +00:00
func MaintainCertDB ( ctx context . Context , interval time . Duration , acmeClient * AcmeClient , mainDomainSuffix string , 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-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 ( ) {
2023-02-11 02:29:08 +00:00
_ , err = acmeClient . obtainCert ( acmeClient . dnsChallengerLegoClient , [ ] string { "*" + mainDomainSuffix , mainDomainSuffix [ 1 : ] } , res , "" , true , mainDomainSuffix , 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 ] )
}