summaryrefslogtreecommitdiffstats
path: root/cmd/web_acme.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/web_acme.go')
-rw-r--r--cmd/web_acme.go135
1 files changed, 135 insertions, 0 deletions
diff --git a/cmd/web_acme.go b/cmd/web_acme.go
new file mode 100644
index 00000000..90e4a027
--- /dev/null
+++ b/cmd/web_acme.go
@@ -0,0 +1,135 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package cmd
+
+import (
+ "crypto/x509"
+ "encoding/pem"
+ "fmt"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/modules/graceful"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/process"
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/caddyserver/certmagic"
+)
+
+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 {
+ // If HTTP Challenge enabled, needs to be serving on port 80. For TLSALPN needs 443.
+ // Due to docker port mapping this can't be checked programmatically
+ // TODO: these are placeholders until we add options for each in settings with appropriate warning
+ enableHTTPChallenge := true
+ enableTLSALPNChallenge := true
+ altHTTPPort := 0
+ altTLSALPNPort := 0
+
+ if p, err := strconv.Atoi(setting.PortToRedirect); err == nil {
+ altHTTPPort = p
+ }
+ if p, err := strconv.Atoi(setting.HTTPPort); err == nil {
+ altTLSALPNPort = p
+ }
+
+ magic := certmagic.NewDefault()
+ 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)
+ }
+ }
+ myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
+ CA: setting.AcmeURL,
+ TrustedRoots: certPool,
+ Email: setting.AcmeEmail,
+ Agreed: setting.AcmeTOS,
+ DisableHTTPChallenge: !enableHTTPChallenge,
+ DisableTLSALPNChallenge: !enableTLSALPNChallenge,
+ ListenHost: setting.HTTPAddr,
+ AltTLSALPNPort: altTLSALPNPort,
+ AltHTTPPort: altHTTPPort,
+ })
+
+ magic.Issuers = []certmagic.Issuer{myACME}
+
+ // this obtains certificates or renews them if necessary
+ err := magic.ManageSync(graceful.GetManager().HammerContext(), []string{setting.Domain})
+ if err != nil {
+ return err
+ }
+
+ tlsConfig := magic.TLSConfig()
+ tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
+
+ 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
+ }
+
+ if enableHTTPChallenge {
+ go func() {
+ _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: ACME HTTP challenge server", process.SystemProcessType, true)
+ defer finished()
+
+ 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)
+ err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectorUseProxyProtocol)
+ if err != nil {
+ log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
+ }
+ }()
+ }
+
+ return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging)
+}
+
+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()
+ http.Redirect(w, r, target, http.StatusTemporaryRedirect)
+}