#157: isolate worktree credential helpers
This commit is contained in:
@@ -69,7 +69,7 @@ public final class GitWorktrees implements Worktrees {
|
||||
|
||||
/** A credential helper command which reads only an environment variable at Git call time. */
|
||||
private static final String ENVIRONMENT_CREDENTIAL_HELPER = "!f() { if [ \"$1\" = get ]; then "
|
||||
+ "printf 'username=%s\\n\\n' \"${GITEA_TOKEN:-$WORKER_GITEA_TOKEN}\"; fi; }; f";
|
||||
+ "printf 'username=%s\\npassword=%s\\n\\n' git \"$WORKER_GITEA_TOKEN\"; fi; }; f";
|
||||
|
||||
/**
|
||||
* A tracked project config that is hostile in a provisioned worktree, and what to replace it
|
||||
@@ -179,7 +179,10 @@ public final class GitWorktrees implements Worktrees {
|
||||
/** Configure a per-worktree helper that supplies a token from the member environment at call time. */
|
||||
private void configureEnvironmentCredentialHelper(String repoRoot, String worktreePath) {
|
||||
exec("git", "-C", repoRoot, "config", "extensions.worktreeConfig", "true");
|
||||
exec("git", "-C", worktreePath, "config", "--worktree", "credential.helper",
|
||||
// An empty helper resets values inherited from the system or global config. Without it Git
|
||||
// asks the next helper after this one, which can expose an operator-level credential.
|
||||
exec("git", "-C", worktreePath, "config", "--worktree", "--replace-all", "credential.helper", "");
|
||||
exec("git", "-C", worktreePath, "config", "--worktree", "--add", "credential.helper",
|
||||
ENVIRONMENT_CREDENTIAL_HELPER);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,11 @@ class GitWorktreesTest {
|
||||
private static String gitOutput(Path cwd, String... args) throws Exception {
|
||||
List<String> cmd = new java.util.ArrayList<>(List.of("git"));
|
||||
cmd.addAll(List.of(args));
|
||||
Process p = new ProcessBuilder(cmd).directory(cwd.toFile()).redirectErrorStream(true).start();
|
||||
ProcessBuilder pb = new ProcessBuilder(cmd).directory(cwd.toFile()).redirectErrorStream(true);
|
||||
pb.environment().put("GIT_CONFIG_GLOBAL", "/dev/null");
|
||||
pb.environment().put("GIT_CONFIG_SYSTEM", "/dev/null");
|
||||
pb.environment().put("GIT_TERMINAL_PROMPT", "0");
|
||||
Process p = pb.start();
|
||||
String out = new String(p.getInputStream().readAllBytes());
|
||||
assertTrue(p.waitFor(30, TimeUnit.SECONDS), "git timed out: " + String.join(" ", cmd));
|
||||
assertEquals(0, p.exitValue(), "git " + String.join(" ", args) + " failed:\n" + out);
|
||||
@@ -231,10 +235,40 @@ class GitWorktreesTest {
|
||||
assertFalse(gitOutput(worktree, "config", "--list").contains("synthetic-test-token"),
|
||||
"git config --list exposed user info");
|
||||
String helper = gitOutput(worktree, "config", "--worktree", "--get", "credential.helper");
|
||||
assertTrue(helper.contains("GITEA_TOKEN"), "credential helper does not read the member environment");
|
||||
assertTrue(helper.contains("WORKER_GITEA_TOKEN"), "credential helper does not read the member environment");
|
||||
assertFalse(helper.contains("synthetic-test-token"), "credential helper stored user info");
|
||||
}
|
||||
|
||||
@Test
|
||||
void worktreeCredentialHelperCompletesWithoutUsingAnInheritedHelper(@TempDir Path tmp) throws Exception {
|
||||
Path repo = initRepo(tmp.resolve("repo"));
|
||||
git(repo, "remote", "add", "origin", "https://git.ltms.dev/akb/kb.git");
|
||||
String wt = new GitWorktrees(tmp.resolve("wts").toString())
|
||||
.add(repo.toString(), "fleetd-157-helper", "HEAD");
|
||||
Path globalConfig = tmp.resolve("global.gitconfig");
|
||||
Files.writeString(globalConfig, """
|
||||
[credential]
|
||||
helper = !f() { printf 'username=%s\\npassword=%s\\n\\n' operator operator-secret; }; f
|
||||
""");
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder("git", "credential", "fill")
|
||||
.directory(Path.of(wt).toFile()).redirectErrorStream(true);
|
||||
pb.environment().put("GIT_CONFIG_GLOBAL", globalConfig.toString());
|
||||
pb.environment().put("GIT_CONFIG_SYSTEM", "/dev/null");
|
||||
pb.environment().put("GIT_TERMINAL_PROMPT", "0");
|
||||
pb.environment().put("WORKER_GITEA_TOKEN", "synthetic-worker-value");
|
||||
Process p = pb.start();
|
||||
p.getOutputStream().write("protocol=https\nhost=git.ltms.dev\n\n".getBytes(StandardCharsets.UTF_8));
|
||||
p.getOutputStream().close();
|
||||
String credential = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||
assertTrue(p.waitFor(30, TimeUnit.SECONDS), "git credential fill timed out");
|
||||
assertEquals(0, p.exitValue(), "git credential fill failed");
|
||||
assertTrue(credential.contains("username=git"), "helper did not return its fixed username");
|
||||
assertTrue(credential.contains("password=synthetic-worker-value"),
|
||||
"helper did not return the worker token as the password");
|
||||
assertFalse(credential.contains("operator-secret"), "Git used the inherited global helper");
|
||||
}
|
||||
|
||||
@Test
|
||||
void provisioningRefusesAWorktreeWhoseOriginStillHasHttpsUserInfo(@TempDir Path tmp) throws Exception {
|
||||
Path repo = initRepo(tmp.resolve("repo"));
|
||||
|
||||
Reference in New Issue
Block a user