mirror of
https://gitea.com/gitea/tea.git
synced 2026-09-10 07:26:33 -04:00
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>
169 lines
4.7 KiB
Go
169 lines
4.7 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package git
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
// ErrRepositoryNotExists indicates the requested path is not inside a git repository.
|
|
ErrRepositoryNotExists = errors.New("repository does not exist")
|
|
// ErrBranchExists indicates the requested branch already exists locally.
|
|
ErrBranchExists = errors.New("branch already exists")
|
|
)
|
|
|
|
// AuthMethod carries backend-agnostic authentication information for git operations.
|
|
type AuthMethod struct {
|
|
Scheme string
|
|
Username string
|
|
Password string
|
|
KeyFile string
|
|
KeyPassphrase string
|
|
}
|
|
|
|
// CloneOptions describes repository clone behavior.
|
|
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.
|
|
type RepositoryBackend interface {
|
|
WorkTree() string
|
|
Config() (*Config, error)
|
|
Head() (*Reference, error)
|
|
AddRemote(name, remoteURL string) error
|
|
SetBranchUpstream(branchName, remoteName, remoteBranch string) error
|
|
Fetch(remoteName string, refspecs []string, auth *AuthMethod) error
|
|
CreateTrackingBranch(localBranchName, remoteBranchName, remoteName string) error
|
|
Checkout(ref ReferenceName) error
|
|
DeleteLocalBranch(branchName string) error
|
|
DeleteRemoteBranch(remoteName, remoteBranch string, auth *AuthMethod) error
|
|
ListReferences(prefixes ...string) ([]*Reference, error)
|
|
PushToAgitFlowPR(remoteName, head, base, topic string, pushOptions map[string]string, auth *AuthMethod) error
|
|
}
|
|
|
|
// Backend opens and clones repositories using a concrete git implementation.
|
|
type Backend interface {
|
|
Name() string
|
|
Open(path string) (RepositoryBackend, error)
|
|
Clone(path, remoteURL string, auth *AuthMethod, opts CloneOptions) (RepositoryBackend, error)
|
|
}
|
|
|
|
// Config mirrors the repository config fields tea needs.
|
|
type Config struct {
|
|
Remotes map[string]*RemoteConfig
|
|
Branches map[string]*Branch
|
|
}
|
|
|
|
// RemoteConfig stores remote configuration.
|
|
type RemoteConfig struct {
|
|
Name string
|
|
URLs []string
|
|
}
|
|
|
|
// Branch stores branch configuration.
|
|
type Branch struct {
|
|
Name string
|
|
Remote string
|
|
Merge ReferenceName
|
|
}
|
|
|
|
// Validate checks whether the branch contains the fields tea needs.
|
|
func (b *Branch) Validate() error {
|
|
if b == nil || b.Name == "" {
|
|
return errors.New("branch name is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Remote wraps a configured git remote.
|
|
type Remote struct {
|
|
repo *TeaRepo
|
|
config *RemoteConfig
|
|
}
|
|
|
|
// Config returns the remote configuration.
|
|
func (r *Remote) Config() *RemoteConfig {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
return r.config
|
|
}
|
|
|
|
// ReferenceName identifies a git reference.
|
|
type ReferenceName string
|
|
|
|
func (r ReferenceName) String() string { return string(r) }
|
|
|
|
// Short returns the short display name for the reference.
|
|
func (r ReferenceName) Short() string {
|
|
s := string(r)
|
|
switch {
|
|
case strings.HasPrefix(s, "refs/heads/"):
|
|
return strings.TrimPrefix(s, "refs/heads/")
|
|
case strings.HasPrefix(s, "refs/remotes/"):
|
|
return strings.TrimPrefix(s, "refs/remotes/")
|
|
case strings.HasPrefix(s, "refs/tags/"):
|
|
return strings.TrimPrefix(s, "refs/tags/")
|
|
default:
|
|
return s
|
|
}
|
|
}
|
|
|
|
// IsBranch reports whether the reference points to a local branch.
|
|
func (r ReferenceName) IsBranch() bool {
|
|
return strings.HasPrefix(string(r), "refs/heads/")
|
|
}
|
|
|
|
// IsRemote reports whether the reference points to a remote-tracking branch.
|
|
func (r ReferenceName) IsRemote() bool {
|
|
return strings.HasPrefix(string(r), "refs/remotes/")
|
|
}
|
|
|
|
// IsTag reports whether the reference points to a tag.
|
|
func (r ReferenceName) IsTag() bool {
|
|
return strings.HasPrefix(string(r), "refs/tags/")
|
|
}
|
|
|
|
// Hash wraps a git object id.
|
|
type Hash string
|
|
|
|
func (h Hash) String() string { return string(h) }
|
|
|
|
// Reference stores a resolved git ref and its hash.
|
|
type Reference struct {
|
|
name ReferenceName
|
|
hash Hash
|
|
}
|
|
|
|
// Name returns the reference name.
|
|
func (r *Reference) Name() ReferenceName { return r.name }
|
|
|
|
// Hash returns the reference hash.
|
|
func (r *Reference) Hash() Hash { return r.hash }
|
|
|
|
// NewBranchReferenceName constructs a local branch ref name.
|
|
func NewBranchReferenceName(name string) ReferenceName {
|
|
if strings.HasPrefix(name, "refs/") {
|
|
return ReferenceName(name)
|
|
}
|
|
return ReferenceName("refs/heads/" + name)
|
|
}
|
|
|
|
// NewRemoteReferenceName constructs a remote-tracking ref name.
|
|
func NewRemoteReferenceName(remote, name string) ReferenceName {
|
|
if strings.HasPrefix(name, "refs/") {
|
|
return ReferenceName(name)
|
|
}
|
|
return ReferenceName("refs/remotes/" + remote + "/" + name)
|
|
}
|