2021-01-25 02:37:35 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cmd
import (
2022-02-08 08:45:35 +03:00
"crypto/x509"
"encoding/pem"
"fmt"
2021-01-25 02:37:35 +03:00
"net/http"
2022-02-08 08:45:35 +03:00
"os"
2021-05-12 22:58:55 +03:00
"strconv"
2021-01-25 02:37:35 +03:00
"strings"
2022-01-01 12:43:28 +03:00
"code.gitea.io/gitea/modules/graceful"
2021-01-25 02:37:35 +03:00
"code.gitea.io/gitea/modules/log"
2022-03-31 20:01:43 +03:00
"code.gitea.io/gitea/modules/process"
2021-01-25 02:37:35 +03:00
"code.gitea.io/gitea/modules/setting"
"github.com/caddyserver/certmagic"
)
2022-02-08 08:45:35 +03:00
func getCARoot ( path string ) ( * x509 . CertPool , error ) {
r , err := os . ReadFile ( path )
if err != nil {
return nil , err
}
block , _ := pem . Decode ( r )
if block == nil {
return nil , fmt . Errorf ( "no PEM found in the file %s" , path )
}
caRoot , err := x509 . ParseCertificate ( block . Bytes )
if err != nil {
return nil , err
}
certPool := x509 . NewCertPool ( )
certPool . AddCert ( caRoot )
return certPool , nil
}
func runACME ( listenAddr string , m http . Handler ) error {
2021-01-25 02:37:35 +03:00
// If HTTP Challenge enabled, needs to be serving on port 80. For TLSALPN needs 443.
2021-07-08 14:38:13 +03:00
// Due to docker port mapping this can't be checked programmatically
2021-01-25 02:37:35 +03:00
// TODO: these are placeholders until we add options for each in settings with appropriate warning
enableHTTPChallenge := true
enableTLSALPNChallenge := true
2021-05-12 22:58:55 +03:00
altHTTPPort := 0
2021-05-14 17:39:10 +03:00
altTLSALPNPort := 0
2021-05-12 22:58:55 +03:00
if p , err := strconv . Atoi ( setting . PortToRedirect ) ; err == nil {
altHTTPPort = p
}
2021-05-14 17:39:10 +03:00
if p , err := strconv . Atoi ( setting . HTTPPort ) ; err == nil {
altTLSALPNPort = p
}
2021-01-25 02:37:35 +03:00
magic := certmagic . NewDefault ( )
2022-02-08 08:45:35 +03:00
magic . Storage = & certmagic . FileStorage { Path : setting . AcmeLiveDirectory }
// Try to use private CA root if provided, otherwise defaults to system's trust
var certPool * x509 . CertPool
if setting . AcmeCARoot != "" {
var err error
certPool , err = getCARoot ( setting . AcmeCARoot )
if err != nil {
log . Warn ( "Failed to parse CA Root certificate, using default CA trust: %v" , err )
}
}
2022-05-10 13:32:42 +03:00
myACME := certmagic . NewACMEIssuer ( magic , certmagic . ACMEIssuer {
2022-02-08 08:45:35 +03:00
CA : setting . AcmeURL ,
TrustedRoots : certPool ,
Email : setting . AcmeEmail ,
Agreed : setting . AcmeTOS ,
2021-01-25 02:37:35 +03:00
DisableHTTPChallenge : ! enableHTTPChallenge ,
DisableTLSALPNChallenge : ! enableTLSALPNChallenge ,
2021-05-14 17:39:10 +03:00
ListenHost : setting . HTTPAddr ,
AltTLSALPNPort : altTLSALPNPort ,
2021-05-12 22:58:55 +03:00
AltHTTPPort : altHTTPPort ,
2021-01-25 02:37:35 +03:00
} )
2021-04-22 23:42:33 +03:00
magic . Issuers = [ ] certmagic . Issuer { myACME }
2021-01-25 02:37:35 +03:00
// this obtains certificates or renews them if necessary
2022-02-08 08:45:35 +03:00
err := magic . ManageSync ( graceful . GetManager ( ) . HammerContext ( ) , [ ] string { setting . Domain } )
2021-01-25 02:37:35 +03:00
if err != nil {
return err
}
tlsConfig := magic . TLSConfig ( )
2021-07-13 20:17:46 +03:00
tlsConfig . NextProtos = append ( tlsConfig . NextProtos , "h2" )
2021-01-25 02:37:35 +03:00
2021-11-20 09:12:43 +03:00
if version := toTLSVersion ( setting . SSLMinimumVersion ) ; version != 0 {
tlsConfig . MinVersion = version
}
if version := toTLSVersion ( setting . SSLMaximumVersion ) ; version != 0 {
tlsConfig . MaxVersion = version
}
// Set curve preferences
if curves := toCurvePreferences ( setting . SSLCurvePreferences ) ; len ( curves ) > 0 {
tlsConfig . CurvePreferences = curves
}
// Set cipher suites
if ciphers := toTLSCiphers ( setting . SSLCipherSuites ) ; len ( ciphers ) > 0 {
tlsConfig . CipherSuites = ciphers
}
2021-01-25 02:37:35 +03:00
if enableHTTPChallenge {
go func ( ) {
2022-03-31 20:01:43 +03:00
_ , _ , finished := process . GetManager ( ) . AddTypedContext ( graceful . GetManager ( ) . HammerContext ( ) , "Web: ACME HTTP challenge server" , process . SystemProcessType , true )
defer finished ( )
2021-01-25 02:37:35 +03:00
log . Info ( "Running Let's Encrypt handler on %s" , setting . HTTPAddr + ":" + setting . PortToRedirect )
// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
2022-08-21 21:20:43 +03:00
err := runHTTP ( "tcp" , setting . HTTPAddr + ":" + setting . PortToRedirect , "Let's Encrypt HTTP Challenge" , myACME . HTTPChallengeHandler ( http . HandlerFunc ( runLetsEncryptFallbackHandler ) ) , setting . RedirectorUseProxyProtocol )
2021-01-25 02:37:35 +03:00
if err != nil {
log . Fatal ( "Failed to start the Let's Encrypt handler on port %s: %v" , setting . PortToRedirect , err )
}
} ( )
}
2022-08-21 21:20:43 +03:00
return runHTTPSWithTLSConfig ( "tcp" , listenAddr , "Web" , tlsConfig , m , setting . UseProxyProtocol , setting . ProxyProtocolTLSBridging )
2021-01-25 02:37:35 +03:00
}
func runLetsEncryptFallbackHandler ( w http . ResponseWriter , r * http . Request ) {
if r . Method != "GET" && r . Method != "HEAD" {
http . Error ( w , "Use HTTPS" , http . StatusBadRequest )
return
}
// Remove the trailing slash at the end of setting.AppURL, the request
// URI always contains a leading slash, which would result in a double
// slash
target := strings . TrimSuffix ( setting . AppURL , "/" ) + r . URL . RequestURI ( )
2022-03-23 07:54:07 +03:00
http . Redirect ( w , r , target , http . StatusTemporaryRedirect )
2021-01-25 02:37:35 +03:00
}