mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-14 03:43:42 +00:00
enhance: trust a private CA with runner.ca_file and add container.volumes (#1366)
Adds `container.volumes`, mounted into every Docker job, service and action container. Sources are paths on the Docker daemon's host and need no `valid_volumes` entry, and workflow volumes may use them only with the same access. Adds `runner.ca_file`, a private CA trusted for the Gitea instance besides the system's by the runner's connections and its artifact proxy. Runner-side git and `builtin:checkout` use it as their CA bundle for the instance, persisted with the checkout's credentials. Jobs get it at `/var/run/act/ca.crt` with `NODE_EXTRA_CA_CERTS` pointing there unless the job or its image sets it. Git in jobs, as run by `actions/checkout`, needs the CA in the image's bundle, for example through `container.volumes`. Fixes: https://gitea.com/gitea/runner/issues/568 Fixes: https://gitea.com/gitea/runner/issues/671 Co-authored-by: silverwind <[email protected]> Co-authored-by: silverwind <[email protected]> Reviewed-on: https://gitea.com/gitea/runner/pulls/1366 Reviewed-by: silverwind <[email protected]> Co-authored-by: bircni <[email protected]>
This commit is contained in:
1 parent
7a9fccb842
commit
aa56f46c89
22 files changed
+309
-75
No files matched your search
@@ -22,5 +22,6 @@ type Context struct {
|
||||
InsecureSkipTLS bool
|
||||
ClientCertFile string
|
||||
ClientKeyFile string
|
||||
CAFile string // the runner CA as seen inside Container
|
||||
Env map[string]string
|
||||
}
|
||||
@@ -124,7 +124,7 @@ func Main(ctx context.Context, c *action.Context) (err error) {
|
||||
credentials := fmt.Sprintf("git-credentials-%x.config", digest[:8])
|
||||
co.credentials = container.JoinPath(runnerTemp, credentials)
|
||||
persist := boolInput(c.Inputs, "persist-credentials", true)
|
||||
config, remoteConfig := serverConfig(ghc.ServerURL, cmp.Or(c.Inputs["token"], ghc.Token), c.InsecureSkipTLS, co.submodules != "" && sshKey == "")
|
||||
config, remoteConfig := serverConfig(ghc.ServerURL, append(git.RemoteConfig(cmp.Or(c.Inputs["token"], ghc.Token), c.InsecureSkipTLS), git.SSLConfig("http.", "", "", c.CAFile, executionOS)...), co.submodules != "" && sshKey == "")
|
||||
if boolInput(c.Inputs, "set-safe-directory", true) {
|
||||
remoteConfig = append(remoteConfig, "safe.directory=*") // a bound workdir belongs to the host user
|
||||
}
|
||||
@@ -145,7 +145,7 @@ func Main(ctx context.Context, c *action.Context) (err error) {
|
||||
if err := c.Container.Copy(runnerTemp, &container.FileEntry{Name: cert, Mode: 0o600, Body: string(certPEM)}, &container.FileEntry{Name: key, Mode: 0o600, Body: string(keyPEM)})(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
remoteConfig = append(remoteConfig, git.ClientCertConfig("http."+strings.TrimSuffix(ghc.ServerURL, "/")+"/.", container.JoinPath(runnerTemp, cert), container.JoinPath(runnerTemp, key), executionOS)...)
|
||||
remoteConfig = append(remoteConfig, git.SSLConfig("http."+strings.TrimSuffix(ghc.ServerURL, "/")+"/.", container.JoinPath(runnerTemp, cert), container.JoinPath(runnerTemp, key), "", executionOS)...)
|
||||
}
|
||||
env := gitEnv(c, remoteConfig)
|
||||
env["GIT_LFS_SKIP_SMUDGE"] = strconv.FormatBool(!co.lfs || len(co.sparse) == 0) // lfs pull fetches the objects of a full checkout in one go
|
||||
@@ -265,13 +265,13 @@ func lsRemote(ctx context.Context, c *action.Context, env map[string]string, url
|
||||
}
|
||||
|
||||
// serverConfig scopes the token to the server, so no other host like a repository's LFS endpoint gets it.
|
||||
func serverConfig(serverURL, token string, insecureSkipTLS, rewriteSSH bool) (file string, env []string) {
|
||||
func serverConfig(serverURL string, settings []string, rewriteSSH bool) (file string, env []string) {
|
||||
scope := strings.TrimSuffix(serverURL, "/") + "/"
|
||||
file = fmt.Sprintf("[http %q]\n", scope)
|
||||
for _, entry := range git.RemoteConfig(token, insecureSkipTLS) {
|
||||
for _, entry := range settings {
|
||||
setting := strings.TrimPrefix(entry, "http.")
|
||||
key, value, _ := strings.Cut(setting, "=")
|
||||
file += "\t" + key + " = " + value + "\n"
|
||||
file += "\t" + key + " = " + strconv.Quote(value) + "\n"
|
||||
env = append(env, "http."+scope+"."+setting)
|
||||
}
|
||||
if server, err := url.Parse(serverURL); rewriteSSH && err == nil && server.Hostname() != "" { // submodules on the server fetch over HTTP with the token
|
||||
|
||||
@@ -215,6 +215,7 @@ exec "$LFS" "$@"
|
||||
Workspace: workspace,
|
||||
BindWorkdir: tc.bind,
|
||||
NoSkipCheckout: tc.noSkip,
|
||||
CAFile: "ca.crt",
|
||||
Env: map[string]string{
|
||||
"PATH": tools + string(os.PathListSeparator) + os.Getenv("PATH"), "GIT_CONFIG_GLOBAL": globalConfig, "GIT_CONFIG_NOSYSTEM": "1",
|
||||
"GIT_DIR": host, "GITHUB_OUTPUT": output, "SSH_SERVER": filepath.ToSlash(server), "SSH_ARGS": filepath.Join(runnerTemp, "ssh-args"),
|
||||
@@ -283,6 +284,7 @@ exec "$LFS" "$@"
|
||||
assert.Len(t, strings.Fields(gitOutput(repo, "config", "--local", "--get-all", credentialsInclude)), 1, repo)
|
||||
headers, _ := exec.Command("git", "-C", repo, "config", "--get-all", "http."+serverURL+"/.extraheader").Output()
|
||||
assert.Equal(t, persisted, string(headers) == "\nAuthorization: Basic dG9rZW46dG9r\n", repo)
|
||||
assert.Equal(t, persisted, gitOutput(repo, "config", "http."+serverURL+"/.sslcainfo") != "", repo)
|
||||
}
|
||||
if tc.inputs["lfs"] == "true" && runtime.GOOS != "windows" {
|
||||
headers, _ := os.ReadFile(filepath.Join(runnerTemp, "lfs-headers"))
|
||||
@@ -306,7 +308,7 @@ exec "$LFS" "$@"
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckoutOffersClientCertOnlyDuringTheStep(t *testing.T) {
|
||||
func TestCheckoutTrustsTheRunnerCAAndOffersClientCertOnlyDuringTheStep(t *testing.T) {
|
||||
var gotCert bool
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotCert = len(r.TLS.PeerCertificates) > 0
|
||||
@@ -324,14 +326,14 @@ func TestCheckoutOffersClientCertOnlyDuringTheStep(t *testing.T) {
|
||||
require.NoError(t, os.WriteFile(keyFile, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), 0o600))
|
||||
|
||||
require.ErrorContains(t, Main(t.Context(), &action.Context{
|
||||
Container: &container.HostEnvironment{Path: workspace, TmpDir: runnerTemp, StdOut: io.Discard},
|
||||
Github: &model.GithubContext{Repository: "org/repo", Ref: "refs/heads/main", ServerURL: server.URL},
|
||||
Inputs: map[string]string{"repository": "other/repo"},
|
||||
Workspace: workspace,
|
||||
InsecureSkipTLS: true,
|
||||
ClientCertFile: certFile,
|
||||
ClientKeyFile: keyFile,
|
||||
Env: map[string]string{"PATH": os.Getenv("PATH"), "GIT_CONFIG_GLOBAL": os.DevNull, "GIT_CONFIG_NOSYSTEM": "1"},
|
||||
Container: &container.HostEnvironment{Path: workspace, TmpDir: runnerTemp, StdOut: io.Discard},
|
||||
Github: &model.GithubContext{Repository: "org/repo", Ref: "refs/heads/main", ServerURL: server.URL},
|
||||
Inputs: map[string]string{"repository": "other/repo"},
|
||||
Workspace: workspace,
|
||||
ClientCertFile: certFile,
|
||||
ClientKeyFile: keyFile,
|
||||
CAFile: certFile,
|
||||
Env: map[string]string{"PATH": os.Getenv("PATH"), "GIT_CONFIG_GLOBAL": os.DevNull, "GIT_CONFIG_NOSYSTEM": "1"},
|
||||
}), "checkout: other/repo@")
|
||||
require.True(t, gotCert)
|
||||
keys, _ := filepath.Glob(filepath.Join(runnerTemp, "git-client-key-*"))
|
||||
|
||||
Reference in new issue
Block a user