Minor cleans for consistency

This commit is contained in:
Tuan Dang
2025-04-10 12:19:37 -07:00
parent 81331ec4d1
commit 8522420e7f
10 changed files with 93 additions and 75 deletions

View File

@@ -269,7 +269,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
description: "Issue SSH certificate for user",
params: z.object({

View File

@@ -11,7 +11,7 @@ export type TSshHostDALFactory = ReturnType<typeof sshHostDALFactory>;
export const sshHostDALFactory = (db: TDbClient) => {
const sshHostOrm = ormify(db, TableName.SshHost);
const findSshHostsWithPrincipalsAcrossProjects = async (projectIds: string[], userId: string, tx?: Knex) => {
const findUserAccessibleSshHosts = async (projectIds: string[], userId: string, tx?: Knex) => {
try {
const user = await (tx || db.replicaNode())(TableName.Users).where({ id: userId }).select("username").first();
@@ -26,6 +26,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
`${TableName.SshHostLoginUser}.id`,
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
)
.leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SshHostLoginUserMapping}.userId`)
.whereIn(`${TableName.SshHost}.projectId`, projectIds)
.andWhere(`${TableName.SshHostLoginUserMapping}.userId`, userId)
.select(
@@ -186,7 +187,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
return {
...sshHostOrm,
findSshHostsWithLoginMappings,
findSshHostsWithPrincipalsAcrossProjects,
findUserAccessibleSshHosts,
findSshHostByIdWithLoginMappings
};
};

View File

@@ -13,8 +13,8 @@ export const sanitizedSshHost = SshHostsSchema.pick({
});
export const loginMappingSchema = z.object({
loginUser: z.string(),
loginUser: z.string().trim(),
allowedPrincipals: z.object({
usernames: z.array(z.string())
usernames: z.array(z.string().trim()).transform((usernames) => Array.from(new Set(usernames)))
})
});

View File

@@ -40,7 +40,7 @@ type TSshHostServiceFactoryDep = {
userDAL: Pick<TUserDALFactory, "findById" | "find">;
projectDAL: Pick<TProjectDALFactory, "find">;
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">;
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findById">;
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findOne">;
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "findOne">;
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">;
sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">;
@@ -53,7 +53,7 @@ type TSshHostServiceFactoryDep = {
| "deleteById"
| "findOne"
| "findSshHostByIdWithLoginMappings"
| "findSshHostsWithPrincipalsAcrossProjects"
| "findUserAccessibleSshHosts"
>;
sshHostLoginUserDAL: TSshHostLoginUserDALFactory;
sshHostLoginUserMappingDAL: TSshHostLoginUserMappingDALFactory;
@@ -105,7 +105,7 @@ export const sshHostServiceFactory = ({
actionProjectType: ActionProjectType.SSH
});
const projectHosts = await sshHostDAL.findSshHostsWithPrincipalsAcrossProjects([project.id], actorId); // TODO: consider fn rename
const projectHosts = await sshHostDAL.findUserAccessibleSshHosts([project.id], actorId);
allowedHosts.push(...projectHosts);
} catch {
@@ -159,9 +159,15 @@ export const sshHostServiceFactory = ({
throw new BadRequestError({ message: `Missing ${label.toLowerCase()} SSH CA` });
}
const ca = await sshCertificateAuthorityDAL.findById(finalId);
const ca = await sshCertificateAuthorityDAL.findOne({
id: finalId,
projectId
});
if (!ca) {
throw new BadRequestError({ message: `${label} SSH CA with ID '${finalId}' not found` });
throw new BadRequestError({
message: `${label} SSH CA with ID '${finalId}' not found in project '${projectId}'`
});
}
return ca.id;
@@ -216,6 +222,7 @@ export const sshHostServiceFactory = ({
tx
);
// (dangtony98): room to optimize
for await (const { loginUser, allowedPrincipals } of loginMappings) {
const sshHostLoginUser = await sshHostLoginUserDAL.create(
{
@@ -225,32 +232,45 @@ export const sshHostServiceFactory = ({
tx
);
const users = await userDAL.find(
{
$in: {
username: allowedPrincipals.usernames
if (allowedPrincipals.usernames.length > 0) {
const users = await userDAL.find(
{
$in: {
username: allowedPrincipals.usernames
}
},
{ tx }
);
const foundUsernames = new Set(users.map((u) => u.username));
for (const uname of allowedPrincipals.usernames) {
if (!foundUsernames.has(uname)) {
throw new BadRequestError({
message: `Invalid username: ${uname}`
});
}
},
{ tx }
);
}
for await (const user of users) {
await permissionService.getUserProjectPermission({
userId: user.id,
projectId,
authMethod: actorAuthMethod,
userOrgId: actorOrgId,
actionProjectType: ActionProjectType.SSH
});
for await (const user of users) {
// check that each user has access to the SSH project
await permissionService.getUserProjectPermission({
userId: user.id,
projectId,
authMethod: actorAuthMethod,
userOrgId: actorOrgId,
actionProjectType: ActionProjectType.SSH
});
}
await sshHostLoginUserMappingDAL.insertMany(
users.map((user) => ({
sshHostLoginUserId: sshHostLoginUser.id,
userId: user.id
})),
tx
);
}
await sshHostLoginUserMappingDAL.insertMany(
users.map((user) => ({
sshHostLoginUserId: sshHostLoginUser.id,
userId: user.id
})),
tx
);
}
const newSshHostWithLoginMappings = await sshHostDAL.findSshHostByIdWithLoginMappings(host.id, tx);
@@ -317,46 +337,44 @@ export const sshHostServiceFactory = ({
tx
);
if (allowedPrincipals.usernames.length === 0) {
continue; // or maybe insert no mappings and just skip validation
}
if (allowedPrincipals.usernames.length > 0) {
const users = await userDAL.find(
{
$in: {
username: allowedPrincipals.usernames
}
},
{ tx }
);
const users = await userDAL.find(
{
$in: {
username: allowedPrincipals.usernames
const foundUsernames = new Set(users.map((u) => u.username));
for (const uname of allowedPrincipals.usernames) {
if (!foundUsernames.has(uname)) {
throw new BadRequestError({
message: `Invalid username: ${uname}`
});
}
},
{ tx }
);
}
const foundUsernames = new Set(users.map((u) => u.username));
for (const uname of allowedPrincipals.usernames) {
if (!foundUsernames.has(uname)) {
throw new BadRequestError({
message: `Invalid username: ${uname}`
for await (const user of users) {
await permissionService.getUserProjectPermission({
userId: user.id,
projectId: host.projectId,
authMethod: actorAuthMethod,
userOrgId: actorOrgId,
actionProjectType: ActionProjectType.SSH
});
}
}
for await (const user of users) {
await permissionService.getUserProjectPermission({
userId: user.id,
projectId: host.projectId,
authMethod: actorAuthMethod,
userOrgId: actorOrgId,
actionProjectType: ActionProjectType.SSH
});
await sshHostLoginUserMappingDAL.insertMany(
users.map((user) => ({
sshHostLoginUserId: sshHostLoginUser.id,
userId: user.id
})),
tx
);
}
await sshHostLoginUserMappingDAL.insertMany(
users.map((user) => ({
sshHostLoginUserId: sshHostLoginUser.id,
userId: user.id
})),
tx
);
}
}
}

View File

@@ -1328,7 +1328,7 @@ export const SSH_HOSTS = {
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
allowedPrincipals: "A list of allowed principals that can log in as the login user.",
loginMappings:
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals.",
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users in the Infisical SSH project.",
userSshCaId:
"The ID of the SSH CA to use for user certificates. If not specified, the default user SSH CA will be used if it exists.",
hostSshCaId:
@@ -1342,7 +1342,7 @@ export const SSH_HOSTS = {
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
allowedPrincipals: "A list of allowed principals that can log in as the login user.",
loginMappings:
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals."
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users in the Infisical SSH project."
},
DELETE: {
sshHostId: "The ID of the SSH host to delete."