From 7853c2edebca4a55e1cb18ede257296925e9f3c0 Mon Sep 17 00:00:00 2001 From: xoc Date: Fri, 20 Oct 2023 15:03:23 +0200 Subject: [PATCH] rebase to current main --- cmd/web.go | 2 +- cmd/web_https.go | 23 ++++++++++++++++++- .../config-cheat-sheet.en-us.md | 1 + .../administration/https-support.en-us.md | 4 ++++ modules/setting/server.go | 5 ++++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/cmd/web.go b/cmd/web.go index 01386251becfa..fb010fd390247 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -330,7 +330,7 @@ func listen(m http.Handler, handleRedirector bool) error { NoHTTPRedirector() } } - err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging) + err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, setting.CAFile, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging) case setting.FCGI: if handleRedirector { NoHTTPRedirector() diff --git a/cmd/web_https.go b/cmd/web_https.go index 70d35cd40d84a..8e1c52f5fd536 100644 --- a/cmd/web_https.go +++ b/cmd/web_https.go @@ -5,6 +5,7 @@ package cmd import ( "crypto/tls" + "crypto/x509" "net/http" "os" "strings" @@ -135,7 +136,11 @@ var ( // be provided. If the certificate is signed by a certificate authority, the // certFile should be the concatenation of the server's certificate followed by the // CA's certificate. -func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error { +// +// caFile can optionally point to a PEM encoded CA certificate file. It is used to +// validate certificates presented by the client against. When supplied, mutual TLS +// is enforced and no other TLS channels are established. +func runHTTPS(network, listenAddr, name, certFile, keyFile, caFile string, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error { tlsConfig := &tls.Config{} if tlsConfig.NextProtos == nil { tlsConfig.NextProtos = []string{"h2", "http/1.1"} @@ -183,6 +188,22 @@ func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handle return err } + // Adding additional client certificate validation. + if caFile != "" { + caPEMBlock, err := os.ReadFile(caFile) + if err != nil { + log.Error("Failed to load https ca file %s for %s:%s: %v", caFile, network, listenAddr, err) + return err + } + + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = x509.NewCertPool() + + if !tlsConfig.ClientCAs.AppendCertsFromPEM(caPEMBlock) { + log.Fatal("Failed to load https ca file %s into cert pool for %s:%s", caFile, network, listenAddr) + } + } + return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging) } diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 617715fbaa12e..40eaf916f3178 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -357,6 +357,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `OFFLINE_MODE`: **false**: Disables use of CDN for static files and Gravatar for profile pictures. - `CERT_FILE`: **https/cert.pem**: Cert file path used for HTTPS. When chaining, the server certificate must come first, then intermediate CA certificates (if any). This is ignored if `ENABLE_ACME=true`. Paths are relative to `CUSTOM_PATH`. - `KEY_FILE`: **https/key.pem**: Key file path used for HTTPS. This is ignored if `ENABLE_ACME=true`. Paths are relative to `CUSTOM_PATH`. +- `MUTUAL_TLS_CA_FILE`: ****: CA file path used for mutual TLS authentication. When used, enforces client certificate usage and prevents TLS tunnel otherwise. Paths are relative to `CUSTOM_PATH`. - `STATIC_ROOT_PATH`: **_`StaticRootPath`_**: Upper level of template and static files path. - `APP_DATA_PATH`: **data** (**/data/gitea** on docker): Default path for application data. Relative paths will be made absolute against _`AppWorkPath`_. - `STATIC_CACHE_TIME`: **6h**: Web browser cache time for static resources on `custom/`, `public/` and all uploaded avatars. Note that this cache is disabled when `RUN_MODE` is "dev". diff --git a/docs/content/administration/https-support.en-us.md b/docs/content/administration/https-support.en-us.md index 4e18722ddf140..c33e36e0f90f4 100644 --- a/docs/content/administration/https-support.en-us.md +++ b/docs/content/administration/https-support.en-us.md @@ -98,3 +98,7 @@ After that, enable HTTPS by following one of these guides: - [caddy](https://caddyserver.com/docs/tls) Note: Enabling HTTPS only at the proxy level is referred as [TLS Termination Proxy](https://en.wikipedia.org/wiki/TLS_termination_proxy). The proxy server accepts incoming TLS connections, decrypts the contents, and passes the now unencrypted contents to Gitea. This is normally fine as long as both the proxy and Gitea instances are either on the same machine, or on different machines within private network (with the proxy is exposed to outside network). If your Gitea instance is separated from your proxy over a public network, or if you want full end-to-end encryption, you can also [enable HTTPS support directly in Gitea using built-in server](#using-the-built-in-server) and forward the connections over HTTPS instead. + +## Using mutual TLS (client certificates) + +Gitea supports using client certificates for additional security by taking advantage of mutual TLS. When setting the `MUTUAL_TLS_CA_FILE` option in the server section, the HTTPS server started by Gitea will enforce clients supplying a certificate and validating it against one of the PEM encoded CA certificates in `MUTUAL_TLS_CA_FILE`. The CA can be different than the one providing the server's certificate. TLS connection is not established should the client certificate be expired or otherwise invalid or if not signed by a trusted CA. Currently this feature only affects the TLS channel between client and server, the certificate's details do not affect authentication within Gitea and is still required after establishing the TLS channel. diff --git a/modules/setting/server.go b/modules/setting/server.go index d053fee5e76bb..6e5ee48d1f491 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -77,6 +77,7 @@ var ( OfflineMode bool CertFile string KeyFile string + CAFile string StaticRootPath string StaticCacheTime time.Duration EnableGzip bool @@ -216,12 +217,16 @@ func loadServerFrom(rootCfg ConfigProvider) { } else { CertFile = sec.Key("CERT_FILE").String() KeyFile = sec.Key("KEY_FILE").String() + CAFile = sec.Key("MUTUAL_TLS_CA_FILE").String() if len(CertFile) > 0 && !filepath.IsAbs(CertFile) { CertFile = filepath.Join(CustomPath, CertFile) } if len(KeyFile) > 0 && !filepath.IsAbs(KeyFile) { KeyFile = filepath.Join(CustomPath, KeyFile) } + if len(CAFile) > 0 && !filepath.IsAbs(CAFile) { + CAFile = filepath.Join(CustomPath, CAFile) + } } SSLMinimumVersion = sec.Key("SSL_MIN_VERSION").MustString("") SSLMaximumVersion = sec.Key("SSL_MAX_VERSION").MustString("")