Files
doktainer-upstream/tests/backend/ssh-access-security.test.ts
T

169 lines
4.2 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
getSshConfigurationRevision,
parseEffectiveSshPolicy,
validateSshAccessPolicy,
} from "../../src/server/services/ssh-services/internal/ssh-access-security";
import {
SystemAccountConflictError,
SystemAccountInputError,
} from "../../src/server/services/ssh-services/internal/system-account-security";
const basePolicy = {
serverUsername: "deploy",
serverAuthType: "SSH_KEY" as const,
currentRevision: "a".repeat(64),
expectedRevision: "a".repeat(64),
currentPasswordAuthentication: false,
desired: {
pubkeyAuthentication: true,
passwordAuthentication: false,
permitRootLogin: "no" as const,
permitEmptyPasswords: false as const,
},
temporaryMinutes: null,
};
test("effective SSH policy parsing normalizes root key-only aliases", () => {
assert.deepEqual(
parseEffectiveSshPolicy([
"pubkeyauthentication yes",
"passwordauthentication no",
"permitrootlogin without-password",
"permitemptypasswords no",
].join("\n")),
{
pubkeyAuthentication: true,
passwordAuthentication: false,
permitRootLogin: "prohibit-password",
permitEmptyPasswords: false,
},
);
assert.equal(
parseEffectiveSshPolicy([
"pubkeyauthentication yes",
"passwordauthentication yes",
"permitrootlogin yes",
"permitemptypasswords yes",
].join("\n"))?.permitEmptyPasswords,
true,
);
});
test("SSH policy revision is stable and content-sensitive", () => {
assert.equal(getSshConfigurationRevision("same"), getSshConfigurationRevision("same"));
assert.notEqual(getSshConfigurationRevision("same"), getSshConfigurationRevision("changed"));
});
test("SSH policy keeps the active Doktainer authentication method", () => {
assert.doesNotThrow(() => validateSshAccessPolicy(basePolicy));
assert.throws(
() =>
validateSshAccessPolicy({
...basePolicy,
desired: {
...basePolicy.desired,
pubkeyAuthentication: false,
passwordAuthentication: true,
},
}),
/uses an SSH key/i,
);
assert.throws(
() =>
validateSshAccessPolicy({
...basePolicy,
serverAuthType: "PASSWORD",
desired: {
...basePolicy.desired,
passwordAuthentication: false,
},
}),
/uses a password/i,
);
});
test("SSH policy rejects stale revisions, empty passwords, and disabling all methods", () => {
assert.throws(
() =>
validateSshAccessPolicy({
...basePolicy,
expectedRevision: "b".repeat(64),
}),
SystemAccountConflictError,
);
assert.throws(
() =>
validateSshAccessPolicy({
...basePolicy,
desired: {
...basePolicy.desired,
permitEmptyPasswords: true,
} as never,
}),
/must remain disabled/i,
);
assert.throws(
() =>
validateSshAccessPolicy({
...basePolicy,
desired: {
...basePolicy.desired,
pubkeyAuthentication: false,
passwordAuthentication: false,
},
}),
/at least one/i,
);
});
test("root sessions and temporary password access have lockout guards", () => {
assert.throws(
() =>
validateSshAccessPolicy({
...basePolicy,
serverUsername: "root",
}),
/connects as root/i,
);
assert.throws(
() =>
validateSshAccessPolicy({
...basePolicy,
serverUsername: "root",
serverAuthType: "PASSWORD",
desired: {
...basePolicy.desired,
passwordAuthentication: true,
permitRootLogin: "prohibit-password",
},
}),
SystemAccountInputError,
);
assert.doesNotThrow(() =>
validateSshAccessPolicy({
...basePolicy,
desired: {
...basePolicy.desired,
passwordAuthentication: true,
},
temporaryMinutes: 15,
}),
);
assert.throws(
() =>
validateSshAccessPolicy({
...basePolicy,
currentPasswordAuthentication: true,
desired: {
...basePolicy.desired,
passwordAuthentication: true,
},
temporaryMinutes: 15,
}),
/already enabled/i,
);
});