Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

client certificate validation #21221

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
23 changes: 22 additions & 1 deletion cmd/web_https.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cmd

import (
"crypto/tls"
"crypto/x509"
"net/http"
"os"
"strings"
Expand Down Expand Up @@ -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"}
Expand Down Expand Up @@ -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)
}

Expand Down
1 change: 1 addition & 0 deletions docs/content/administration/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`: **<empty>**: 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".
Expand Down
4 changes: 4 additions & 0 deletions docs/content/administration/https-support.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
5 changes: 5 additions & 0 deletions modules/setting/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ var (
OfflineMode bool
CertFile string
KeyFile string
CAFile string
StaticRootPath string
StaticCacheTime time.Duration
EnableGzip bool
Expand Down Expand Up @@ -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("")
Expand Down