enhance: support client certificates for mutual TLS (#1273)

Add `runner.client_cert_file` and `runner.client_key_file` for Gitea instances that require mutual TLS. The runner presents the certificate on its RPCs, job summary uploads, and the action, reusable workflow and `builtin:checkout` clones from the instance. The files are read on use, so renewed certificates apply without a restart. Job steps do not get them. An incomplete or mismatched pair fails startup.

Clones now decide the token, client certificate and `runner.insecure` with one host check, which also gives reusable workflow clones the TLS settings they lacked. `runner.insecure` still only covers the registered address.

Fixes: https://gitea.com/gitea/runner/issues/321
Co-authored-by: silverwind <[email protected]>
Co-authored-by: silverwind <[email protected]>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1273
Reviewed-by: silverwind <[email protected]>
Co-authored-by: bircni <[email protected]>
This commit is contained in:
authored and silverwind committed 2026-10-08 17:48:31 +00:00
1 parent e5aa9add02
commit f963e9cfe8
21 files changed
+298 -202

No files matched your search

+11 -8
View File
@@ -19,20 +19,23 @@ import (
"gitea.dev/actionslib/runner/v1/runnerv1connect"
)
func getHTTPClient(endpoint string, insecure bool, timeout time.Duration, extraHeaders map[string]string) *http.Client {
func NewTransport(endpoint string, tlsConfig *tls.Config) *http.Transport {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
MaxIdleConns: 10,
MaxIdleConnsPerHost: 10, // All requests go to one host; default is 2 which causes frequent reconnects.
IdleConnTimeout: 90 * time.Second,
ForceAttemptHTTP2: true,
}
if strings.HasPrefix(endpoint, "https://") && insecure {
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
if strings.HasPrefix(endpoint, "https://") {
transport.TLSClientConfig = tlsConfig
}
return transport
}
func getHTTPClient(endpoint string, tlsConfig *tls.Config, timeout time.Duration, extraHeaders map[string]string) *http.Client {
return &http.Client{
Transport: transport,
Transport: NewTransport(endpoint, tlsConfig),
Timeout: timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= 10 { // a custom CheckRedirect replaces net/http's own limit
@@ -54,7 +57,7 @@ func getHTTPClient(endpoint string, insecure bool, timeout time.Duration, extraH
// New returns a new runner client. timeout bounds every RPC: without it a
// stalled connection parks the reporter for the whole job context, so logs and
// heartbeats stop together and the task is reaped as a zombie.
func New(endpoint string, insecure bool, uuid, token string, timeout time.Duration, extraHeaders map[string]string, opts ...connect.ClientOption) *HTTPClient {
func New(endpoint string, tlsConfig *tls.Config, uuid, token string, timeout time.Duration, extraHeaders map[string]string, opts ...connect.ClientOption) *HTTPClient {
baseURL := strings.TrimRight(endpoint, "/") + "/api/actions"
opts = append(opts, connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc {
@@ -75,7 +78,7 @@ func New(endpoint string, insecure bool, uuid, token string, timeout time.Durati
}
})))
httpClient := getHTTPClient(endpoint, insecure, timeout, extraHeaders)
httpClient := getHTTPClient(endpoint, tlsConfig, timeout, extraHeaders)
return &HTTPClient{
PingServiceClient: pingv1connect.NewPingServiceClient(
httpClient,