vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Answer whoami --role from the repository's rules
eec0e60344bf5686fc278ba5a8415dc0777ba9e2 · Keenan Nemetz · 6d ago · parent f422c24f
Answer whoami --role from the repository's rules A role is per repository, so whoami cannot print one truth without knowing which repository is meant. Keep the default answer name-only, for the scripts that call it, and make the role an explicit query on the remote's repository. The server's vole-whoami verb now accepts a repository path and returns the effective role after the same private-repository rule as a fetch. A run credential may ask only about its own repository. Update the CLI help and the contributor and account docs to point at the new query.
server/integration_test.go +64−1
| @@ -452,11 +452,18 @@ func (f *fixture) get(path string) (int, string) { | |||
| 452 | 452 | // cli runs the built binary in dir as the owner. A null global git config means | |
| 453 | 453 | // a developer's own identity cannot change what a test sees. | |
| 454 | 454 | func (f *fixture) cli(dir string, args ...string) (string, string, error) { | |
| 455 | f.t.Helper() | ||
| 456 | return f.cliWithKey(f.keyPath, dir, args...) | ||
| 457 | } | ||
| 458 | |||
| 459 | // cliWithKey is cli for another account. The key is what the instance resolves, | ||
| 460 | // so a test can ask a different role the same question. | ||
| 461 | func (f *fixture) cliWithKey(key, dir string, args ...string) (string, string, error) { | ||
| 455 | 462 | f.t.Helper() | |
| 456 | 463 | cmd := exec.Command(binPath, args...) | |
| 457 | 464 | cmd.Dir = dir | |
| 458 | 465 | cmd.Env = append(os.Environ(), | |
| 459 | "GIT_SSH_COMMAND=ssh -i "+f.keyPath+" -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR", | ||
| 466 | "GIT_SSH_COMMAND=ssh -i "+key+" -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR", | ||
| 460 | 467 | "GIT_CONFIG_GLOBAL=/dev/null", "GIT_CONFIG_SYSTEM=/dev/null", | |
| 461 | 468 | "USER=keenan") | |
| 462 | 469 | var out, errb bytes.Buffer | |
| @@ -1131,6 +1138,62 @@ func TestWhoAmIAnswersFromTheInstance(t *testing.T) { | |||
| 1131 | 1138 | } | |
| 1132 | 1139 | } | |
| 1133 | 1140 | ||
| 1141 | // TestWhoAmIRoleAnswersFromTheInstance checks the explicit role query. The role | ||
| 1142 | // is per repository, so the answer comes from the ACL behind the remote, not from | ||
| 1143 | // the account's instance role alone. A private repository the key cannot read | ||
| 1144 | // gives the same answer as a clone: not found. | ||
| 1145 | func TestWhoAmIRoleAnswersFromTheInstance(t *testing.T) { | ||
| 1146 | t.Parallel() | ||
| 1147 | f := newFixture(t) | ||
| 1148 | work := workRepo(t, map[string]string{"readme.md": "hi\n"}) | ||
| 1149 | if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil { | ||
| 1150 | t.Fatalf("push main: %s", errs) | ||
| 1151 | } | ||
| 1152 | clone := filepath.Join(t.TempDir(), "clone") | ||
| 1153 | if _, errs, err := f.git("", "clone", "-q", f.sshURL(f.repo), clone); err != nil { | ||
| 1154 | t.Fatalf("clone: %s", errs) | ||
| 1155 | } | ||
| 1156 | |||
| 1157 | out, errs, err := f.cli(clone, "whoami", "--role") | ||
| 1158 | if err != nil { | ||
| 1159 | t.Fatalf("whoami --role: %v\n%s", err, errs) | ||
| 1160 | } | ||
| 1161 | if got := strings.TrimSpace(out); got != "admin" { | ||
| 1162 | t.Fatalf("whoami --role = %q, want admin for the repository owner", got) | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | writerKey := f.addWriter(t) | ||
| 1166 | out, errs, err = f.cliWithKey(writerKey, clone, "whoami", "--role") | ||
| 1167 | if err != nil { | ||
| 1168 | t.Fatalf("writer whoami --role: %v\n%s", err, errs) | ||
| 1169 | } | ||
| 1170 | if got := strings.TrimSpace(out); got != "maintain" { | ||
| 1171 | t.Fatalf("writer whoami --role = %q, want maintain", got) | ||
| 1172 | } | ||
| 1173 | |||
| 1174 | // A stranger on a private repository learns nothing about it, just like a | ||
| 1175 | // fetch that the read rule refuses. | ||
| 1176 | pub, priv := keypair(t, filepath.Join(f.cfg.DataDir, "id_stranger")) | ||
| 1177 | if _, err := f.srv.Identity.Create("stranger", pub, "test"); err != nil { | ||
| 1178 | t.Fatal(err) | ||
| 1179 | } | ||
| 1180 | if _, errs, err := f.cliWithKey(priv, clone, "whoami", "--role"); err == nil { | ||
| 1181 | t.Fatalf("a stranger read a private repository's role: %s", errs) | ||
| 1182 | } else if !strings.Contains(errs, "not found") { | ||
| 1183 | t.Fatalf("the refusal does not hide the private repository: %s", errs) | ||
| 1184 | } | ||
| 1185 | |||
| 1186 | // Published, the repository's default role is the account's role. | ||
| 1187 | f.publish(f.repo) | ||
| 1188 | out, errs, err = f.cliWithKey(priv, clone, "whoami", "--role") | ||
| 1189 | if err != nil { | ||
| 1190 | t.Fatalf("stranger whoami --role on a public repository: %v\n%s", err, errs) | ||
| 1191 | } | ||
| 1192 | if got := strings.TrimSpace(out); got != "read" { | ||
| 1193 | t.Fatalf("stranger whoami --role = %q, want read after publishing", got) | ||
| 1194 | } | ||
| 1195 | } | ||
| 1196 | |||
| 1134 | 1197 | // TestReadCommandsNeedNoIdentity: `issue list` and `issue show` read the branch, | |
| 1135 | 1198 | // so they must keep working where the instance cannot be reached at all. | |
| 1136 | 1199 | func TestReadCommandsNeedNoIdentity(t *testing.T) { | |