gitea.tea/modules/context/context_login.go
Suhaib 8240b385b1 feat(login): support TLS client certificates for mutual TLS servers
Gitea instances behind a reverse proxy that requires mutual TLS (nginx'
ssl_verify_client, or the equivalent in Traefik or HAProxy) reject every
tea request at the proxy, before it reaches Gitea. The user sees an opaque
403/400 from the proxy rather than an authentication error, and there was
no way to make tea present a certificate: only full verification and
--insecure were configurable.

Add an optional client certificate to a login, stored as client_cert and
client_key next to the existing insecure flag:

    tea login add --url https://git.example.com --token ... \
        --client-cert ~/.certs/client.crt \
        --client-key ~/.certs/client.key

The pair is loaded by Login.TLSConfig(), which now builds the TLS config
for every outgoing connection: SDK API calls, the OAuth authorization and
token refresh flows, and the raw 'tea api' client. Because the certificate
lives on the login, all commands using it present it automatically. Paths
support ~ expansion so they can be written into the config file by hand,
and clones pass the same material to git as http.sslCert/http.sslKey.

Both fields must be set together; setting only one, or pointing at a
certificate that cannot be loaded, is a hard error rather than a silent
fallback to a plain connection, which would fail at the proxy with the
same opaque error the certificate is meant to avoid.

Requests without either setting keep Go's default TLS behaviour: the
config is only overridden when the login asks for it.

Fixes #451

Signed-off-by: Suhaib <suhaib.abdulquddos@gmail.com>
2026-09-04 21:21:44 -07:00

54 lines
1.4 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package context
import (
"os"
"strconv"
"time"
"gitea.dev/tea/modules/config"
)
// GetLoginByEnvVar returns a login based on environment variables, or nil if no login can be created
func GetLoginByEnvVar() *config.Login {
var token string
giteaToken := os.Getenv("GITEA_TOKEN")
githubToken := os.Getenv("GH_TOKEN")
giteaInstanceURL := os.Getenv("GITEA_INSTANCE_URL")
giteaInstanceSSHHost := os.Getenv("GITEA_INSTANCE_SSH_HOST")
instanceInsecure := os.Getenv("GITEA_INSTANCE_INSECURE")
insecure := false
if len(instanceInsecure) > 0 {
insecure, _ = strconv.ParseBool(instanceInsecure)
}
// if no tokens are set, or no instance url for gitea fail fast
if len(giteaInstanceURL) == 0 || (len(giteaToken) == 0 && len(githubToken) == 0) {
return nil
}
token = giteaToken
if len(giteaToken) == 0 {
token = githubToken
}
return &config.Login{
Name: "GITEA_LOGIN_VIA_ENV",
URL: giteaInstanceURL,
Token: token,
SSHHost: giteaInstanceSSHHost,
Insecure: insecure,
ClientCert: os.Getenv("GITEA_INSTANCE_CLIENT_CERT"),
ClientKey: os.Getenv("GITEA_INSTANCE_CLIENT_KEY"),
SSHKey: "",
SSHCertPrincipal: "",
SSHKeyFingerprint: "",
SSHAgent: false,
Created: time.Now().Unix(),
VersionCheck: false,
}
}