summaryrefslogtreecommitdiffstats
path: root/cmd/web_acme.go
diff options
context:
space:
mode:
authorCristian Le <github@lecris.me>2022-02-08 14:45:35 +0900
committerGitHub <noreply@github.com>2022-02-08 13:45:35 +0800
commit60f203385e6f27fae47f3cc8c5d71309f4fd88dc (patch)
tree3be2a41b96ab7ab0419e0e63676a5fc16e20cd7d /cmd/web_acme.go
parenta60e8be8d15e90a44f2a746a4e8d81a81e03d2db (diff)
downloadgitea-60f203385e6f27fae47f3cc8c5d71309f4fd88dc.tar.gz
gitea-60f203385e6f27fae47f3cc8c5d71309f4fd88dc.zip
Support custom ACME provider (#18340)
* Added ACMECAURL option to support custom ACME provider. Closes #18306 * Refactor setting.go https settings, renamed options and variables, and documented app.example.ini * Refactored runLetsEncrypt to runACME * Improved documentation
Diffstat (limited to 'cmd/web_acme.go')
-rw-r--r--cmd/web_acme.go132
1 files changed, 132 insertions, 0 deletions
diff --git a/cmd/web_acme.go b/cmd/web_acme.go
new file mode 100644
index 0000000000..9a04274db5
--- /dev/null
+++ b/cmd/web_acme.go
@@ -0,0 +1,132 @@
+// 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 (
+ "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/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.NewACMEManager(magic, certmagic.ACMEManager{
+ 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() {
+ 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)))
+ 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)
+}
+
+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.StatusFound)
+}