code.gitea.io/gitea@v1.22.3/cmd/web_acme.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package cmd 5 6 import ( 7 "crypto/x509" 8 "encoding/pem" 9 "fmt" 10 "net/http" 11 "os" 12 "strconv" 13 "strings" 14 15 "code.gitea.io/gitea/modules/graceful" 16 "code.gitea.io/gitea/modules/log" 17 "code.gitea.io/gitea/modules/process" 18 "code.gitea.io/gitea/modules/setting" 19 20 "github.com/caddyserver/certmagic" 21 ) 22 23 func getCARoot(path string) (*x509.CertPool, error) { 24 r, err := os.ReadFile(path) 25 if err != nil { 26 return nil, err 27 } 28 block, _ := pem.Decode(r) 29 if block == nil { 30 return nil, fmt.Errorf("no PEM found in the file %s", path) 31 } 32 caRoot, err := x509.ParseCertificate(block.Bytes) 33 if err != nil { 34 return nil, err 35 } 36 certPool := x509.NewCertPool() 37 certPool.AddCert(caRoot) 38 return certPool, nil 39 } 40 41 func runACME(listenAddr string, m http.Handler) error { 42 // If HTTP Challenge enabled, needs to be serving on port 80. For TLSALPN needs 443. 43 // Due to docker port mapping this can't be checked programmatically 44 // TODO: these are placeholders until we add options for each in settings with appropriate warning 45 enableHTTPChallenge := true 46 enableTLSALPNChallenge := true 47 altHTTPPort := 0 48 altTLSALPNPort := 0 49 50 if p, err := strconv.Atoi(setting.PortToRedirect); err == nil { 51 altHTTPPort = p 52 } 53 if p, err := strconv.Atoi(setting.HTTPPort); err == nil { 54 altTLSALPNPort = p 55 } 56 57 magic := certmagic.NewDefault() 58 magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory} 59 // Try to use private CA root if provided, otherwise defaults to system's trust 60 var certPool *x509.CertPool 61 if setting.AcmeCARoot != "" { 62 var err error 63 certPool, err = getCARoot(setting.AcmeCARoot) 64 if err != nil { 65 log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err) 66 } 67 } 68 myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{ 69 CA: setting.AcmeURL, 70 TrustedRoots: certPool, 71 Email: setting.AcmeEmail, 72 Agreed: setting.AcmeTOS, 73 DisableHTTPChallenge: !enableHTTPChallenge, 74 DisableTLSALPNChallenge: !enableTLSALPNChallenge, 75 ListenHost: setting.HTTPAddr, 76 AltTLSALPNPort: altTLSALPNPort, 77 AltHTTPPort: altHTTPPort, 78 }) 79 80 magic.Issuers = []certmagic.Issuer{myACME} 81 82 // this obtains certificates or renews them if necessary 83 err := magic.ManageSync(graceful.GetManager().HammerContext(), []string{setting.Domain}) 84 if err != nil { 85 return err 86 } 87 88 tlsConfig := magic.TLSConfig() 89 tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") 90 91 if version := toTLSVersion(setting.SSLMinimumVersion); version != 0 { 92 tlsConfig.MinVersion = version 93 } 94 if version := toTLSVersion(setting.SSLMaximumVersion); version != 0 { 95 tlsConfig.MaxVersion = version 96 } 97 98 // Set curve preferences 99 if curves := toCurvePreferences(setting.SSLCurvePreferences); len(curves) > 0 { 100 tlsConfig.CurvePreferences = curves 101 } 102 103 // Set cipher suites 104 if ciphers := toTLSCiphers(setting.SSLCipherSuites); len(ciphers) > 0 { 105 tlsConfig.CipherSuites = ciphers 106 } 107 108 if enableHTTPChallenge { 109 go func() { 110 _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: ACME HTTP challenge server", process.SystemProcessType, true) 111 defer finished() 112 113 log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect) 114 // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here) 115 err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectorUseProxyProtocol) 116 if err != nil { 117 log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err) 118 } 119 }() 120 } 121 122 return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging) 123 } 124 125 func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { 126 if r.Method != "GET" && r.Method != "HEAD" { 127 http.Error(w, "Use HTTPS", http.StatusBadRequest) 128 return 129 } 130 // Remove the trailing slash at the end of setting.AppURL, the request 131 // URI always contains a leading slash, which would result in a double 132 // slash 133 target := strings.TrimSuffix(setting.AppURL, "/") + r.URL.RequestURI() 134 http.Redirect(w, r, target, http.StatusTemporaryRedirect) 135 }