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>
This commit is contained in:
Suhaib 2026-09-04 21:21:44 -07:00
parent 58931b5d17
commit 8240b385b1
17 changed files with 376 additions and 37 deletions

View file

@ -120,7 +120,10 @@ func runApi(requestCtx stdctx.Context, cmd *cli.Command) error {
}
// Create API client and make request
client := api.NewClient(ctx.Login)
client, err := api.NewClient(ctx.Login)
if err != nil {
return err
}
resp, err := client.Do(request.Method, request.Endpoint, body, request.Headers)
if err != nil {
return fmt.Errorf("request failed: %w", err)

View file

@ -83,6 +83,16 @@ token. Equivalent to running 'tea login helper setup' afterwards.`,
Aliases: []string{"i"},
Usage: "Disable TLS verification",
},
&cli.StringFlag{
Name: "client-cert",
Sources: cli.EnvVars("GITEA_SERVER_CLIENT_CERT"),
Usage: "Path to a PEM encoded TLS client certificate, for servers requiring mutual TLS",
},
&cli.StringFlag{
Name: "client-key",
Sources: cli.EnvVars("GITEA_SERVER_CLIENT_KEY"),
Usage: "Path to the PEM encoded private key for --client-cert",
},
&cli.StringFlag{
Name: "ssh-agent-principal",
Aliases: []string{"c"},
@ -127,9 +137,11 @@ func runLoginAdd(requestCtx context.Context, cmd *cli.Command) error {
// if OAuth flag is provided, use OAuth2 PKCE flow
if cmd.Bool("oauth") {
opts := auth.OAuthOptions{
Name: cmd.String("name"),
URL: cmd.String("url"),
Insecure: cmd.Bool("insecure"),
Name: cmd.String("name"),
URL: cmd.String("url"),
Insecure: cmd.Bool("insecure"),
ClientCert: cmd.String("client-cert"),
ClientKey: cmd.String("client-key"),
}
// Only set clientID if provided
@ -164,6 +176,8 @@ func runLoginAdd(requestCtx context.Context, cmd *cli.Command) error {
cmd.String("ssh-agent-principal"),
cmd.String("ssh-agent-key"),
cmd.Bool("insecure"),
cmd.String("client-cert"),
cmd.String("client-key"),
sshAgent,
!cmd.Bool("no-version-check"),
cmd.Bool("git-credentials"),

View file

@ -39,8 +39,12 @@ List Gitea logins
Add a Gitea login
**--client-cert**="": Path to a PEM encoded TLS client certificate, for servers requiring mutual TLS
**--client-id**="": OAuth client ID (for use with --oauth)
**--client-key**="": Path to the PEM encoded private key for --client-cert
**--git-credentials, --helper, -j**: Register tea as a git credential helper for this login's URL, so 'git push' and 'git clone' over HTTPS authenticate silently using the stored token
**--insecure, -i**: Disable TLS verification

View file

@ -4,7 +4,6 @@
package api
import (
"crypto/tls"
"fmt"
"io"
"log"
@ -24,21 +23,28 @@ type Client struct {
}
// NewClient creates a new API client from a Login config
func NewClient(login *config.Login) *Client {
func NewClient(login *config.Login) (*Client, error) {
// Refresh OAuth token if expired or near expiry
if err := login.RefreshOAuthTokenIfNeeded(); err != nil {
log.Printf("Warning: failed to refresh OAuth token: %v", err)
}
// A misconfigured client certificate is fatal rather than a fallback to a
// plain connection: the request would otherwise be rejected by the server
// with the same opaque error the certificate is meant to avoid.
tlsConfig, err := login.TLSConfig()
if err != nil {
return nil, err
}
httpClient := &http.Client{
Transport: httputil.WrapTransport(&tls.Config{InsecureSkipVerify: login.Insecure}),
Transport: httputil.WrapTransport(tlsConfig),
}
return &Client{
baseURL: strings.TrimSuffix(login.URL, "/"),
token: login.GetAccessToken(),
httpClient: httpClient,
}
}, nil
}
// Do executes an HTTP request with authentication headers

View file

@ -7,7 +7,6 @@ import (
"context"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"encoding/base64"
"fmt"
"net"
@ -47,6 +46,8 @@ type OAuthOptions struct {
Name string
URL string
Insecure bool
ClientCert string
ClientKey string
ClientID string
RedirectURL string
Port int
@ -77,7 +78,7 @@ func OAuthLoginWithFullOptions(ctx context.Context, opts OAuthOptions) error {
return err
}
return createLoginFromToken(ctx, opts.Name, serverURL, token, opts.Insecure)
return createLoginFromToken(ctx, opts, serverURL, token)
}
// performBrowserOAuthFlow performs the browser-based OAuth2 PKCE flow and returns the token.
@ -127,7 +128,11 @@ func performBrowserOAuthFlow(ctx context.Context, opts OAuthOptions) (serverURL
codeChallenge := generateCodeChallenge(codeVerifier)
// Set up the OAuth2 config
ctx = context.WithValue(ctx, oauth2.HTTPClient, createHTTPClient(opts.Insecure))
httpClient, err := createHTTPClient(opts)
if err != nil {
return "", nil, err
}
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
// Configure the OAuth2 endpoints
authURL := fmt.Sprintf("%s/login/oauth/authorize", normalizedURL)
@ -198,11 +203,17 @@ func performBrowserOAuthFlow(ctx context.Context, opts OAuthOptions) (serverURL
return serverURL, token, nil
}
// createHTTPClient creates an HTTP client with optional insecure setting
func createHTTPClient(insecure bool) *http.Client {
return &http.Client{
Transport: httputil.WrapTransport(&tls.Config{InsecureSkipVerify: insecure}),
// createHTTPClient creates an HTTP client honoring the login's TLS settings,
// so the OAuth flow reaches servers behind mutual-TLS proxies too.
func createHTTPClient(opts OAuthOptions) (*http.Client, error) {
l := config.Login{Insecure: opts.Insecure, ClientCert: opts.ClientCert, ClientKey: opts.ClientKey}
tlsConfig, err := l.TLSConfig()
if err != nil {
return nil, err
}
return &http.Client{
Transport: httputil.WrapTransport(tlsConfig),
}, nil
}
// generateCodeVerifier creates a cryptographically random string for PKCE
@ -372,7 +383,8 @@ var openBrowser = func(url string) error {
}
// createLoginFromToken creates a login entry using the obtained access token
func createLoginFromToken(ctx context.Context, name, serverURL string, token *oauth2.Token, insecure bool) error {
func createLoginFromToken(ctx context.Context, opts OAuthOptions, serverURL string, token *oauth2.Token) error {
name := opts.Name
if name == "" {
var err error
name, err = task.GenerateLoginName(serverURL, "")
@ -387,7 +399,9 @@ func createLoginFromToken(ctx context.Context, name, serverURL string, token *oa
URL: serverURL,
Token: token.AccessToken, // temporarily set for Client() validation
AuthMethod: config.AuthMethodOAuth,
Insecure: insecure,
Insecure: opts.Insecure,
ClientCert: opts.ClientCert,
ClientKey: opts.ClientKey,
VersionCheck: true,
Created: time.Now().Unix(),
}
@ -435,6 +449,8 @@ func ReauthenticateLogin(ctx context.Context, login *config.Login) error {
Name: login.Name,
URL: login.URL,
Insecure: login.Insecure,
ClientCert: login.ClientCert,
ClientKey: login.ClientKey,
ClientID: config.DefaultClientID,
RedirectURL: fmt.Sprintf("http://%s:%d", redirectHost, redirectPort),
Port: redirectPort,

View file

@ -44,8 +44,14 @@ type Login struct {
Default bool `yaml:"default"`
SSHHost string `yaml:"ssh_host"`
// optional path to the private key
SSHKey string `yaml:"ssh_key"`
Insecure bool `yaml:"insecure"`
SSHKey string `yaml:"ssh_key"`
Insecure bool `yaml:"insecure"`
// ClientCert is an optional path to a PEM encoded TLS client certificate,
// presented to servers that require mutual TLS (e.g. a reverse proxy
// configured with nginx' ssl_verify_client). ClientKey is its private key;
// both must be set together.
ClientCert string `yaml:"client_cert,omitempty"`
ClientKey string `yaml:"client_key,omitempty"`
SSHCertPrincipal string `yaml:"ssh_certificate_principal"`
SSHAgent bool `yaml:"ssh_agent"`
SSHKeyFingerprint string `yaml:"ssh_key_agent_pub"`
@ -63,6 +69,52 @@ type Login struct {
TokenExpiry int64 `yaml:"token_expiry,omitempty"`
}
// TLSConfig returns the TLS configuration to use for HTTPS requests to this
// login's server. It returns nil when the login needs neither relaxed
// verification nor a client certificate, so callers keep Go's default TLS
// behaviour.
func (l *Login) TLSConfig() (*tls.Config, error) {
cert, err := l.clientCertificate()
if err != nil {
return nil, err
}
if cert == nil && !l.Insecure {
return nil, nil
}
cfg := &tls.Config{InsecureSkipVerify: l.Insecure}
if cert != nil {
cfg.Certificates = []tls.Certificate{*cert}
}
return cfg, nil
}
// clientCertificate loads the login's PEM client certificate/key pair, and
// returns nil when the login does not configure one.
func (l *Login) clientCertificate() (*tls.Certificate, error) {
if l.ClientCert == "" && l.ClientKey == "" {
return nil, nil
}
if l.ClientCert == "" || l.ClientKey == "" {
return nil, errors.New("client_cert and client_key must be set together to use a TLS client certificate")
}
certFile, err := utils.AbsPathWithExpansion(l.ClientCert)
if err != nil {
return nil, fmt.Errorf("invalid client certificate path %q: %w", l.ClientCert, err)
}
keyFile, err := utils.AbsPathWithExpansion(l.ClientKey)
if err != nil {
return nil, fmt.Errorf("invalid client key path %q: %w", l.ClientKey, err)
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("failed to load TLS client certificate: %w", err)
}
return &cert, nil
}
// IsOAuth returns true if this login uses OAuth with secure credential storage.
func (l *Login) IsOAuth() bool {
return l.AuthMethod == AuthMethodOAuth
@ -417,8 +469,12 @@ func doOAuthRefresh(ctx context.Context, l *Login) (*oauth2.Token, error) {
Expiry: expiry,
}
tlsConfig, err := l.TLSConfig()
if err != nil {
return nil, err
}
httpClient := &http.Client{
Transport: httputil.WrapTransport(&tls.Config{InsecureSkipVerify: l.Insecure}),
Transport: httputil.WrapTransport(tlsConfig),
}
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
@ -458,16 +514,16 @@ func (l *Login) ClientWithoutRefresh(options ...gitea.ClientOption) *gitea.Clien
// fails fast instead of hanging forever. These bound connection setup and
// time-to-first-response-byte only, so slow-but-progressing transfers (e.g.
// large attachment uploads) are unaffected.
tlsConfig, err := l.TLSConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to configure TLS for login %s: %s\n", l.Name, err)
os.Exit(1)
}
httpClient := &http.Client{
Transport: httputil.WrapTransport(nil),
Transport: httputil.WrapTransport(tlsConfig),
}
if l.Insecure {
cookieJar, _ := cookiejar.New(nil) // New with nil options never returns an error
httpClient = &http.Client{
Jar: cookieJar,
Transport: httputil.WrapTransport(&tls.Config{InsecureSkipVerify: true}),
}
httpClient.Jar, _ = cookiejar.New(nil) // New with nil options never returns an error
}
// versioncheck must be prepended in options to make sure we don't hit any version checks in the sdk

View file

@ -0,0 +1,170 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package config
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
"gitea.dev/tea/modules/httputil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTLSConfigWithoutSettingsIsNil(t *testing.T) {
t.Parallel()
cfg, err := (&Login{}).TLSConfig()
require.NoError(t, err)
assert.Nil(t, cfg, "a login with default TLS settings must not override the transport's TLS config")
}
func TestTLSConfigInsecureOnly(t *testing.T) {
t.Parallel()
cfg, err := (&Login{Insecure: true}).TLSConfig()
require.NoError(t, err)
require.NotNil(t, cfg)
assert.True(t, cfg.InsecureSkipVerify)
assert.Empty(t, cfg.Certificates)
}
func TestTLSConfigRequiresBothCertAndKey(t *testing.T) {
t.Parallel()
for name, login := range map[string]*Login{
"cert without key": {ClientCert: "/tmp/client.crt"},
"key without cert": {ClientKey: "/tmp/client.key"},
} {
t.Run(name, func(t *testing.T) {
t.Parallel()
_, err := login.TLSConfig()
require.Error(t, err)
assert.Contains(t, err.Error(), "must be set together")
})
}
}
func TestTLSConfigReportsUnreadableCertificate(t *testing.T) {
t.Parallel()
dir := t.TempDir()
login := &Login{
ClientCert: filepath.Join(dir, "missing.crt"),
ClientKey: filepath.Join(dir, "missing.key"),
}
_, err := login.TLSConfig()
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to load TLS client certificate")
}
func TestTLSConfigLoadsClientCertificate(t *testing.T) {
t.Parallel()
_, certPath, keyPath := writeTestClientCert(t)
cfg, err := (&Login{ClientCert: certPath, ClientKey: keyPath}).TLSConfig()
require.NoError(t, err)
require.NotNil(t, cfg)
assert.False(t, cfg.InsecureSkipVerify)
require.Len(t, cfg.Certificates, 1)
}
// TestClientCertAuthenticatesAgainstMutualTLSServer is the end-to-end case from
// issue #451: a server that rejects any client not presenting a certificate,
// standing in for a reverse proxy configured with nginx' ssl_verify_client.
func TestClientCertAuthenticatesAgainstMutualTLSServer(t *testing.T) {
t.Parallel()
clientCert, certPath, keyPath := writeTestClientCert(t)
clientCAs := x509.NewCertPool()
clientCAs.AddCert(clientCert)
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
server.TLS = &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientCAs,
}
server.StartTLS()
defer server.Close()
// The server's own certificate is self-signed, so skip verification of it;
// the point under test is the certificate tea presents, not the one it gets.
withoutCert := &Login{URL: server.URL, Insecure: true}
withCert := &Login{URL: server.URL, Insecure: true, ClientCert: certPath, ClientKey: keyPath}
_, err := doGet(t, withoutCert, server.URL)
require.Error(t, err, "server must reject a client that presents no certificate")
resp, err := doGet(t, withCert, server.URL)
require.NoError(t, err, "client certificate should satisfy the server's mutual TLS requirement")
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
func doGet(t *testing.T, login *Login, url string) (*http.Response, error) {
t.Helper()
tlsConfig, err := login.TLSConfig()
require.NoError(t, err)
client := &http.Client{Transport: httputil.WrapTransport(tlsConfig)}
return client.Get(url)
}
// writeTestClientCert generates a self-signed certificate usable for client
// authentication and writes the PEM pair into a temp dir.
func writeTestClientCert(t *testing.T) (cert *x509.Certificate, certPath, keyPath string) {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "tea-test-client"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
IsCA: true,
}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
require.NoError(t, err)
cert, err = x509.ParseCertificate(der)
require.NoError(t, err)
keyDER, err := x509.MarshalECPrivateKey(key)
require.NoError(t, err)
dir := t.TempDir()
certPath = filepath.Join(dir, "client.crt")
keyPath = filepath.Join(dir, "client.key")
require.NoError(t, os.WriteFile(certPath, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0o600))
require.NoError(t, os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}), 0o600))
return cert, certPath, keyPath
}

View file

@ -41,6 +41,8 @@ func GetLoginByEnvVar() *config.Login {
Token: token,
SSHHost: giteaInstanceSSHHost,
Insecure: insecure,
ClientCert: os.Getenv("GITEA_INSTANCE_CLIENT_CERT"),
ClientKey: os.Getenv("GITEA_INSTANCE_CLIENT_KEY"),
SSHKey: "",
SSHCertPrincipal: "",
SSHKeyFingerprint: "",

View file

@ -64,7 +64,7 @@ func TestCanSwitchBackends(t *testing.T) {
require.Equal(t, "open:demo", repo.WorkTree())
require.Equal(t, "fake", CurrentBackendName())
cloned, err := Clone("target", "https://example.com/repo.git", nil, 1, false)
cloned, err := Clone("target", "https://example.com/repo.git", nil, CloneOptions{Depth: 1})
require.NoError(t, err)
require.Equal(t, "clone:target:https://example.com/repo.git", cloned.WorkTree())
}

View file

@ -38,11 +38,25 @@ func (b cliBackend) Open(path string) (RepositoryBackend, error) {
return &cliRepository{workTree: strings.TrimSpace(out)}, nil
}
func (b cliBackend) Clone(path, remoteURL string, auth *AuthMethod, opts CloneOptions) (RepositoryBackend, error) {
extraConfigs := make([]string, 0, 1)
// cloneTLSConfigs translates the TLS related clone options into "git -c"
// settings, so a clone against a server with a private CA or one demanding a
// client certificate uses the same material as tea's own API requests.
func cloneTLSConfigs(opts CloneOptions) []string {
configs := make([]string, 0, 3)
if opts.Insecure {
extraConfigs = append(extraConfigs, "http.sslVerify=false")
configs = append(configs, "http.sslVerify=false")
}
if opts.ClientCert != "" {
configs = append(configs, "http.sslCert="+opts.ClientCert)
}
if opts.ClientKey != "" {
configs = append(configs, "http.sslKey="+opts.ClientKey)
}
return configs
}
func (b cliBackend) Clone(path, remoteURL string, auth *AuthMethod, opts CloneOptions) (RepositoryBackend, error) {
extraConfigs := cloneTLSConfigs(opts)
args := []string{"clone"}
if opts.Depth > 0 {

View file

@ -0,0 +1,42 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCloneTLSConfigs(t *testing.T) {
t.Parallel()
for name, tc := range map[string]struct {
opts CloneOptions
want []string
}{
"defaults add nothing": {
opts: CloneOptions{},
want: []string{},
},
"insecure disables verification": {
opts: CloneOptions{Insecure: true},
want: []string{"http.sslVerify=false"},
},
"client certificate is passed to git": {
opts: CloneOptions{ClientCert: "/certs/client.crt", ClientKey: "/certs/client.key"},
want: []string{"http.sslCert=/certs/client.crt", "http.sslKey=/certs/client.key"},
},
"client certificate combines with insecure": {
opts: CloneOptions{Insecure: true, ClientCert: "/certs/client.crt", ClientKey: "/certs/client.key"},
want: []string{"http.sslVerify=false", "http.sslCert=/certs/client.crt", "http.sslKey=/certs/client.key"},
},
} {
t.Run(name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, cloneTLSConfigs(tc.opts))
})
}
}

View file

@ -4,8 +4,8 @@
package git
// Clone clones a repository using the active backend.
func Clone(path, remoteURL string, auth *AuthMethod, depth int, insecure bool) (*TeaRepo, error) {
backend, err := currentBackend().Clone(path, remoteURL, auth, CloneOptions{Depth: depth, Insecure: insecure})
func Clone(path, remoteURL string, auth *AuthMethod, opts CloneOptions) (*TeaRepo, error) {
backend, err := currentBackend().Clone(path, remoteURL, auth, opts)
if err != nil {
return nil, err
}

View file

@ -28,6 +28,11 @@ type AuthMethod struct {
type CloneOptions struct {
Depth int
Insecure bool
// ClientCert and ClientKey are paths to a PEM encoded TLS client
// certificate and its key, passed to git as http.sslCert/http.sslKey so
// clones from mutual-TLS protected servers succeed.
ClientCert string
ClientKey string
}
// RepositoryBackend is the backend abstraction used by TeaRepo.

View file

@ -255,7 +255,7 @@ func CreateLogin(ctx context.Context) error {
printTitleAndContent("Check version of Gitea instance:", strconv.FormatBool(versionCheck))
}
return task.CreateLogin(ctx, name, token, user, passwd, otp, scopes, sshKey, giteaURL, sshCertPrincipal, sshKeyFingerprint, insecure, sshAgent, versionCheck, helper)
return task.CreateLogin(ctx, name, token, user, passwd, otp, scopes, sshKey, giteaURL, sshCertPrincipal, sshKeyFingerprint, insecure, "", "", sshAgent, versionCheck, helper)
}
func parseSSHPubkeySelection(display string) (sshKey, sshCertPrincipal, sshKeyFingerprint string, sshAgent bool, err error) {

View file

@ -72,7 +72,7 @@ func HasGitCredentialHelper(login config.Login) bool {
}
// CreateLogin create a login to be stored in config
func CreateLogin(ctx stdctx.Context, name, token, user, passwd, otp, scopes, sshKey, giteaURL, sshCertPrincipal, sshKeyFingerprint string, insecure, sshAgent, versionCheck, addHelper bool) error {
func CreateLogin(ctx stdctx.Context, name, token, user, passwd, otp, scopes, sshKey, giteaURL, sshCertPrincipal, sshKeyFingerprint string, insecure bool, clientCert, clientKey string, sshAgent, versionCheck, addHelper bool) error {
// checks ...
// ... if we have a url
if len(giteaURL) == 0 {
@ -120,6 +120,8 @@ func CreateLogin(ctx stdctx.Context, name, token, user, passwd, otp, scopes, ssh
URL: serverURL.String(),
Token: token,
Insecure: insecure,
ClientCert: clientCert,
ClientKey: clientKey,
SSHKey: sshKey,
SSHCertPrincipal: sshCertPrincipal,
SSHKeyFingerprint: sshKeyFingerprint,

View file

@ -43,7 +43,12 @@ func RepoClone(
path = repoName
}
repo, err := local_git.Clone(path, originURL.String(), auth, depth, login.Insecure)
repo, err := local_git.Clone(path, originURL.String(), auth, local_git.CloneOptions{
Depth: depth,
Insecure: login.Insecure,
ClientCert: login.ClientCert,
ClientKey: login.ClientKey,
})
if err != nil {
return nil, err
}

View file

@ -96,7 +96,7 @@ func createIntegrationLogin(t *testing.T) *config.Login {
require.NotEmpty(t, integrationToken, "integration token setup failed")
require.NoError(t, task.CreateLogin(t.Context(), "integration", integrationToken, "", "", "", "", "", integrationGiteaURL, "", "", true, false, false, false))
require.NoError(t, task.CreateLogin(t.Context(), "integration", integrationToken, "", "", "", "", "", integrationGiteaURL, "", "", true, "", "", false, false, false))
login, err := config.GetLoginByName("integration")
require.NoError(t, err)