package main import ( "encoding/json" "net/http" "net/http/cgi" "net/http/httptest" "os" "os/exec" "path/filepath" "strings" "testing" ) const agentToken = "0123456789abcdef0123456789abcdef01234567" // privateForge is a forge serving one private repository over git's smart http, to agent-anchor with its token // alone, as the forge does for an account a team lets read it. func privateForge(t *testing.T) (origin, bare string) { t.Helper() backend := filepath.Join(gitExecPath(t), "git-http-backend") if _, err := os.Stat(backend); err != nil { t.Skipf("git-http-backend is not installed: %v", err) } root := t.TempDir() bare = filepath.Join(root, "private.git") work := t.TempDir() runGit(t, work, nil, "git", "init", "-q", "-b", "main", ".") commit(t, work, "one") runGit(t, root, nil, "git", "clone", "-q", "--bare", work, bare) cgiHandler := &cgi.Handler{Path: backend, Env: []string{"GIT_PROJECT_ROOT=" + root, "GIT_HTTP_EXPORT_ALL=1"}} srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok || user != "agent-anchor" || pass != agentToken { w.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`) http.Error(w, "unauthorized", http.StatusUnauthorized) return } if r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "git-receive-pack") { http.Error(w, "read only", http.StatusForbidden) return } cgiHandler.ServeHTTP(w, r) })) t.Cleanup(srv.Close) return srv.URL, bare } func gitExecPath(t *testing.T) string { t.Helper() out, err := exec.Command("git", "--exec-path").Output() if err != nil { t.Skipf("git is not installed: %v", err) } return strings.TrimSpace(string(out)) } func runGit(t *testing.T, dir string, env []string, name string, args ...string) string { t.Helper() out, err := runErr(dir, env, name, args...) if err != nil { t.Fatalf("%s %v: %v\n%s", name, args, err, out) } return out } func runErr(dir string, env []string, name string, args ...string) (string, error) { cmd := exec.Command(name, args...) cmd.Dir = dir cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@t", "GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@t", "GIT_TERMINAL_PROMPT=0", "GIT_ASKPASS=", "SSH_ASKPASS=") cmd.Env = append(cmd.Env, env...) out, err := cmd.CombinedOutput() return string(out), err } func commit(t *testing.T, dir, text string) { t.Helper() if err := os.WriteFile(filepath.Join(dir, "file"), []byte(text+"\n"), 0o644); err != nil { t.Fatal(err) } runGit(t, dir, nil, "git", "add", "file") runGit(t, dir, nil, "git", "commit", "-q", "-m", text) } // sessionEnv is the environment a session starts with: the managed settings' `env`, in a home with no git // configuration of its own. func sessionEnv(t *testing.T, home, managedSettings string) []string { t.Helper() var settings struct { Env map[string]string `json:"env"` } if err := json.Unmarshal([]byte(managedSettings), &settings); err != nil { t.Fatal(err) } env := []string{"HOME=" + home, "XDG_CONFIG_HOME=" + filepath.Join(home, ".config"), "GIT_CONFIG_NOSYSTEM=1"} for k, v := range settings.Env { env = append(env, k+"="+v) } return env } func forgePaths(t *testing.T) Paths { t.Helper() return Paths{State: t.TempDir(), Home: t.TempDir(), Node: "anchor"} } func managedFor(p Paths) string { facts := Facts{Node: p.Node, Console: "http://console"} if ForgeCredentialHeld(p) { facts.ForgeCredentials = p.forgeCredentials() } return Render(facts, Settings{}, nil, "", nil, Config{})["managed-settings.json"] } // Issue 737's done-when, as far as a machine of its own proves it: an agent session clones a private repository and // fetches it again, and no credential appears in a log, a command line or the session. func TestAnAgentClonesAPrivateRepositoryAndFetchesItAgainWithTheCredentialPlaced(t *testing.T) { origin, bare := privateForge(t) p := forgePaths(t) repo := origin + "/private.git" // Before the forge issued anything, the session's git has no credential: the clone is refused, as today. before := sessionEnv(t, p.Home, managedFor(p)) if out, err := runErr(t.TempDir(), before, "git", "clone", "-q", repo, "x"); err == nil { t.Fatalf("a private repository was cloned with no credential:\n%s", out) } // The vault hands this module what the forge issued for this machine's agent. lines := strings.Replace(origin, "http://", "http://agent-anchor:"+agentToken+"@", 1) + "\n" + "https://agent-anchor:" + agentToken + "@git.example.test\n" if err := os.WriteFile(p.forgeSecret(), []byte(lines), 0o600); err != nil { t.Fatal(err) } said, err := PlaceForgeCredential(p) if err != nil { t.Fatal(err) } info, err := os.Stat(p.forgeCredentials()) if err != nil || info.Mode().Perm() != 0o600 { t.Fatalf("the credential file: %v %v, want mode 0600", info, err) } if again, err := PlaceForgeCredential(p); err != nil || again != "" { t.Fatalf("placed again with nothing changed: %q %v", again, err) } managed := managedFor(p) env := sessionEnv(t, p.Home, managed) work := t.TempDir() cloneOut := runGit(t, work, env, "git", "clone", repo, "clone") clone := filepath.Join(work, "clone") if got := runGit(t, clone, env, "git", "log", "--format=%s"); strings.TrimSpace(got) != "one" { t.Fatalf("cloned %q", got) } // The forge's repository moves on; the agent fetches it again. other := t.TempDir() runGit(t, other, nil, "git", "clone", "-q", bare, "w") commit(t, filepath.Join(other, "w"), "two") runGit(t, filepath.Join(other, "w"), nil, "git", "push", "-q", "origin", "main") fetchOut := runGit(t, clone, env, "git", "fetch", "origin") if got := runGit(t, clone, env, "git", "log", "--format=%s", "origin/main"); !strings.HasPrefix(got, "two") { t.Fatalf("fetched %q", got) } // A push is the forge's to refuse (ADR 0365 ยง5): the token reads, and nothing writes with it. commit(t, clone, "three") if out, err := runErr(clone, env, "git", "push", "origin", "main"); err == nil { t.Fatalf("a push with the agent's credential went through:\n%s", out) } // No credential in what the session saw, its command lines, its environment or the module's words. remote := runGit(t, clone, env, "git", "remote", "get-url", "origin") for what, text := range map[string]string{"the clone's output": cloneOut, "the fetch's output": fetchOut, "the remote's url": remote, "the managed settings": managed, "the session's environment": strings.Join(env, "\n"), "what the module said": said, "the clone's configuration": readOrEmpty(filepath.Join(clone, ".git", "config"))} { if strings.Contains(text, agentToken) { t.Errorf("the token appears in %s", what) } } } func readOrEmpty(path string) string { raw, _ := os.ReadFile(path) return string(raw) } func TestWhatGitChangedInTheFileIsPutBackAndAValueNotIssuedIsNeverPlaced(t *testing.T) { p := forgePaths(t) // A value the mesh made while the forge had not been asked: not credential lines, never placed. if err := os.WriteFile(p.forgeSecret(), []byte("Zm9yZ2VkLWJ5LXRoZS1tZXNo\n"), 0o600); err != nil { t.Fatal(err) } if said, err := PlaceForgeCredential(p); err != nil || said != "" { t.Fatalf("%q %v", said, err) } if _, err := os.Stat(p.forgeCredentials()); err == nil { t.Fatal("a value the forge did not issue was placed") } if ForgeCredentialHeld(p) || strings.Contains(managedFor(p), "GIT_CONFIG") { t.Fatal("the sessions' git is pointed at a credential that is not held") } issued := "https://agent-anchor:" + agentToken + "@git.example.test\n" if err := os.WriteFile(p.forgeSecret(), []byte(issued), 0o600); err != nil { t.Fatal(err) } if _, err := PlaceForgeCredential(p); err != nil { t.Fatal(err) } // git's store helper erased the line after a rejection, and a person's password for another host was stored. if err := os.WriteFile(p.forgeCredentials(), []byte("https://me:mine@example.org\n"), 0o644); err != nil { t.Fatal(err) } if said, err := PlaceForgeCredential(p); err != nil || !strings.Contains(said, "placed") { t.Fatalf("%q %v", said, err) } if got := readOrEmpty(p.forgeCredentials()); got != issued { t.Fatalf("the file holds %q", strings.ReplaceAll(got, agentToken, "")) } if info, _ := os.Stat(p.forgeCredentials()); info.Mode().Perm() != 0o600 { t.Fatalf("mode %v", info.Mode().Perm()) } // The credential withdrawn: taken back, and the sessions' git no longer pointed at it. if err := os.Remove(p.forgeSecret()); err != nil { t.Fatal(err) } if said, err := PlaceForgeCredential(p); err != nil || !strings.Contains(said, "taken back") || strings.Contains(said, agentToken) { t.Fatalf("%q %v", said, err) } if _, err := os.Stat(p.forgeCredentials()); err == nil { t.Fatal("a withdrawn credential stayed in the agent's home") } } func TestTheSessionsGitIsPointedAtTheFileUnderTheOperatorsOwnEnvironment(t *testing.T) { managed := map[string]any{"env": map[string]any{"FOO": "bar", "GIT_CONFIG_COUNT": "7"}} forgeEnv(managed, "/home/agent/.config/mesh/forge.credentials") env := managed["env"].(map[string]any) if env["FOO"] != "bar" || env["GIT_CONFIG_COUNT"] != "1" || env["GIT_CONFIG_KEY_0"] != "credential.helper" || env["GIT_CONFIG_VALUE_0"] != "store --file='/home/agent/.config/mesh/forge.credentials'" { t.Fatalf("%v", env) } none := map[string]any{} forgeEnv(none, "") if _, set := none["env"]; set { t.Fatal("an environment was set with no credential held") } } func TestForgeCredentialLinesAreReadStrictly(t *testing.T) { for _, bad := range []string{"", "\n", "token", "https://git.example.test", "https://agent-anchor@git.example.test", "https://agent-anchor:t@git.example.test/novox/hq.git", "ssh://agent-anchor:t@git.example.test", "https://agent-anchor:t@git.example.test\nnot a line"} { if _, err := ForgeCredentialLines(bad); err == nil { t.Errorf("%q was taken", bad) } else if strings.Contains(err.Error(), "t@git") { t.Errorf("the refusal said the value: %v", err) } } }