152 lines
5.6 KiB
TypeScript
152 lines
5.6 KiB
TypeScript
import { expect, test } from "../../fixtures/ssh-test-base";
|
|
import { execInContainer, readRemoteFile } from "../../helpers/ssh";
|
|
import { waitForLatestSessionDone } from "../../helpers/session";
|
|
import {
|
|
assertExactFixtureTransport,
|
|
credentialBrokerPublicURL,
|
|
fixtureGitHost,
|
|
fixtureGitProvider,
|
|
fixtureGitSecret,
|
|
fixtureGitUser,
|
|
fixtureGitURL,
|
|
installFixturePlugin,
|
|
revokeFixtureConnection,
|
|
runRemoteGit,
|
|
startPluginGitFixture,
|
|
uninstallFixturePlugin,
|
|
} from "../../helpers/plugin-git-credentials";
|
|
|
|
test.skip(
|
|
!process.env.KANDEV_E2E_CREDENTIAL_BROKER_PUBLIC_BASE_URL?.trim(),
|
|
"requires KANDEV_E2E_CREDENTIAL_BROKER_PUBLIC_BASE_URL reachable over HTTPS from SSH",
|
|
);
|
|
|
|
test.describe("Bitbucket plugin contract — SSH executor credential leases", () => {
|
|
test("clones and pushes through HTTPS, then denies a connection-generation change", async ({
|
|
apiClient,
|
|
backend,
|
|
seedData,
|
|
}) => {
|
|
test.setTimeout(240_000);
|
|
const publicBrokerURL = credentialBrokerPublicURL(backend.port);
|
|
if (!publicBrokerURL) throw new Error("credential broker public URL is required");
|
|
|
|
const releaseBrokerEnv = await backend.useEnv({
|
|
KANDEV_GITHUB_CREDENTIAL_BROKER_PUBLIC_BASE_URL: publicBrokerURL,
|
|
});
|
|
const fixture = await startPluginGitFixture(backend.tmpDir);
|
|
try {
|
|
// SSH intentionally forwards only broker-owned Git configuration to the
|
|
// remote agentctl. Put test-only TLS/proxy transport config in the
|
|
// target's system Git config; the credential lease still scopes the
|
|
// original HTTPS identity rather than this local transport fixture.
|
|
execInContainer(seedData.sshTarget, [
|
|
"git",
|
|
"config",
|
|
"--system",
|
|
"http.proxy",
|
|
fixture.proxyURL,
|
|
]);
|
|
execInContainer(seedData.sshTarget, ["git", "config", "--system", "http.sslVerify", "false"]);
|
|
|
|
await installFixturePlugin(backend.baseUrl);
|
|
const repository = await apiClient.createRepository(
|
|
seedData.workspaceId,
|
|
fixture.checkoutPath,
|
|
"main",
|
|
{
|
|
name: "TEAM/fixture",
|
|
provider: fixtureGitProvider,
|
|
provider_host: `https://${fixtureGitHost}`,
|
|
provider_owner: "TEAM",
|
|
provider_name: "fixture",
|
|
},
|
|
);
|
|
const task = await apiClient.createTaskWithAgent(
|
|
seedData.workspaceId,
|
|
"SSH fixture credential lease",
|
|
seedData.agentProfileId,
|
|
{
|
|
description: "/e2e:simple-message",
|
|
workflow_id: seedData.workflowId,
|
|
workflow_step_id: seedData.startStepId,
|
|
repository_ids: [repository.id],
|
|
executor_profile_id: seedData.sshExecutorProfileId,
|
|
},
|
|
);
|
|
if (!task.session_id) throw new Error("credentialed SSH task did not create a session");
|
|
await waitForLatestSessionDone(apiClient, task.id, 1, "waiting for credentialed SSH clone");
|
|
const rows = await apiClient.listSSHSessions(seedData.sshExecutorId);
|
|
const row = rows.find((candidate) => candidate.task_id === task.id);
|
|
if (!row?.remote_task_dir) throw new Error("credentialed SSH task has no remote directory");
|
|
const agentctlPID = Number(
|
|
readRemoteFile(
|
|
seedData.sshTarget,
|
|
`${row.remote_task_dir}/.kandev/sessions/${task.session_id}/agentctl.pid`,
|
|
).trim(),
|
|
);
|
|
expect(agentctlPID).toBeGreaterThan(0);
|
|
const remoteURL = runRemoteGit(
|
|
agentctlPID,
|
|
`cd '${row.remote_task_dir}' && git remote get-url origin`,
|
|
seedData.sshTarget.containerName,
|
|
);
|
|
expect(remoteURL.status).toBe(0);
|
|
expect(remoteURL.output.trim()).toBe(fixtureGitURL);
|
|
|
|
const push = runRemoteGit(
|
|
agentctlPID,
|
|
`cd '${row.remote_task_dir}' && printf 'pushed\\n' >> README.md && git add README.md && git -c user.name=E2E -c user.email=e2e@test.local commit -m 'credential fixture push' && git push origin HEAD:main`,
|
|
seedData.sshTarget.containerName,
|
|
);
|
|
expect(push.status).toBe(0);
|
|
expect(fixture.pushed()).toBe(true);
|
|
assertExactFixtureTransport(fixture);
|
|
|
|
const agentctlLog = readRemoteFile(
|
|
seedData.sshTarget,
|
|
`${row.remote_task_dir}/.kandev/sessions/${task.session_id}/agentctl.log`,
|
|
);
|
|
expect(agentctlLog).not.toContain(fixtureGitSecret);
|
|
expect(agentctlLog).not.toContain(`${fixtureGitUser}:${fixtureGitSecret}`);
|
|
const agentctlEnv = execInContainer(seedData.sshTarget, [
|
|
"sh",
|
|
"-c",
|
|
`tr '\\0' '\\n' < /proc/${agentctlPID}/environ`,
|
|
]);
|
|
expect(agentctlEnv).not.toContain(fixtureGitSecret);
|
|
expect(agentctlEnv).not.toContain(`${fixtureGitUser}:${fixtureGitSecret}`);
|
|
|
|
const revoke = await revokeFixtureConnection(backend.baseUrl, seedData.workspaceId);
|
|
expect(revoke.status).toBe(200);
|
|
expect(await revoke.text()).not.toContain(fixtureGitSecret);
|
|
const afterRevocation = runRemoteGit(
|
|
agentctlPID,
|
|
`cd '${row.remote_task_dir}' && git fetch origin`,
|
|
seedData.sshTarget.containerName,
|
|
);
|
|
expect(afterRevocation.status).not.toBe(0);
|
|
expect(afterRevocation.output).not.toContain(fixtureGitSecret);
|
|
expect(afterRevocation.output).not.toContain(`${fixtureGitUser}:${fixtureGitSecret}`);
|
|
} finally {
|
|
execInContainer(seedData.sshTarget, [
|
|
"git",
|
|
"config",
|
|
"--system",
|
|
"--unset-all",
|
|
"http.proxy",
|
|
]);
|
|
execInContainer(seedData.sshTarget, [
|
|
"git",
|
|
"config",
|
|
"--system",
|
|
"--unset-all",
|
|
"http.sslVerify",
|
|
]);
|
|
await uninstallFixturePlugin(backend.baseUrl);
|
|
await fixture.close();
|
|
await releaseBrokerEnv();
|
|
}
|
|
});
|
|
});
|