mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 12:27:28 +00:00
Minor cleans for consistency
This commit is contained in:
@@ -269,7 +269,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Issue SSH certificate for user",
|
description: "Issue SSH certificate for user",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export type TSshHostDALFactory = ReturnType<typeof sshHostDALFactory>;
|
|||||||
export const sshHostDALFactory = (db: TDbClient) => {
|
export const sshHostDALFactory = (db: TDbClient) => {
|
||||||
const sshHostOrm = ormify(db, TableName.SshHost);
|
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 {
|
try {
|
||||||
const user = await (tx || db.replicaNode())(TableName.Users).where({ id: userId }).select("username").first();
|
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.SshHostLoginUser}.id`,
|
||||||
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SshHostLoginUserMapping}.userId`)
|
||||||
.whereIn(`${TableName.SshHost}.projectId`, projectIds)
|
.whereIn(`${TableName.SshHost}.projectId`, projectIds)
|
||||||
.andWhere(`${TableName.SshHostLoginUserMapping}.userId`, userId)
|
.andWhere(`${TableName.SshHostLoginUserMapping}.userId`, userId)
|
||||||
.select(
|
.select(
|
||||||
@@ -186,7 +187,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
return {
|
return {
|
||||||
...sshHostOrm,
|
...sshHostOrm,
|
||||||
findSshHostsWithLoginMappings,
|
findSshHostsWithLoginMappings,
|
||||||
findSshHostsWithPrincipalsAcrossProjects,
|
findUserAccessibleSshHosts,
|
||||||
findSshHostByIdWithLoginMappings
|
findSshHostByIdWithLoginMappings
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ export const sanitizedSshHost = SshHostsSchema.pick({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const loginMappingSchema = z.object({
|
export const loginMappingSchema = z.object({
|
||||||
loginUser: z.string(),
|
loginUser: z.string().trim(),
|
||||||
allowedPrincipals: z.object({
|
allowedPrincipals: z.object({
|
||||||
usernames: z.array(z.string())
|
usernames: z.array(z.string().trim()).transform((usernames) => Array.from(new Set(usernames)))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type TSshHostServiceFactoryDep = {
|
|||||||
userDAL: Pick<TUserDALFactory, "findById" | "find">;
|
userDAL: Pick<TUserDALFactory, "findById" | "find">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "find">;
|
projectDAL: Pick<TProjectDALFactory, "find">;
|
||||||
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">;
|
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">;
|
||||||
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findById">;
|
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findOne">;
|
||||||
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "findOne">;
|
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "findOne">;
|
||||||
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">;
|
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">;
|
||||||
sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">;
|
sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">;
|
||||||
@@ -53,7 +53,7 @@ type TSshHostServiceFactoryDep = {
|
|||||||
| "deleteById"
|
| "deleteById"
|
||||||
| "findOne"
|
| "findOne"
|
||||||
| "findSshHostByIdWithLoginMappings"
|
| "findSshHostByIdWithLoginMappings"
|
||||||
| "findSshHostsWithPrincipalsAcrossProjects"
|
| "findUserAccessibleSshHosts"
|
||||||
>;
|
>;
|
||||||
sshHostLoginUserDAL: TSshHostLoginUserDALFactory;
|
sshHostLoginUserDAL: TSshHostLoginUserDALFactory;
|
||||||
sshHostLoginUserMappingDAL: TSshHostLoginUserMappingDALFactory;
|
sshHostLoginUserMappingDAL: TSshHostLoginUserMappingDALFactory;
|
||||||
@@ -105,7 +105,7 @@ export const sshHostServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SSH
|
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);
|
allowedHosts.push(...projectHosts);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -159,9 +159,15 @@ export const sshHostServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: `Missing ${label.toLowerCase()} SSH CA` });
|
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) {
|
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;
|
return ca.id;
|
||||||
@@ -216,6 +222,7 @@ export const sshHostServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// (dangtony98): room to optimize
|
||||||
for await (const { loginUser, allowedPrincipals } of loginMappings) {
|
for await (const { loginUser, allowedPrincipals } of loginMappings) {
|
||||||
const sshHostLoginUser = await sshHostLoginUserDAL.create(
|
const sshHostLoginUser = await sshHostLoginUserDAL.create(
|
||||||
{
|
{
|
||||||
@@ -225,6 +232,7 @@ export const sshHostServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (allowedPrincipals.usernames.length > 0) {
|
||||||
const users = await userDAL.find(
|
const users = await userDAL.find(
|
||||||
{
|
{
|
||||||
$in: {
|
$in: {
|
||||||
@@ -234,7 +242,18 @@ export const sshHostServiceFactory = ({
|
|||||||
{ tx }
|
{ 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) {
|
for await (const user of users) {
|
||||||
|
// check that each user has access to the SSH project
|
||||||
await permissionService.getUserProjectPermission({
|
await permissionService.getUserProjectPermission({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -252,6 +271,7 @@ export const sshHostServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newSshHostWithLoginMappings = await sshHostDAL.findSshHostByIdWithLoginMappings(host.id, tx);
|
const newSshHostWithLoginMappings = await sshHostDAL.findSshHostByIdWithLoginMappings(host.id, tx);
|
||||||
if (!newSshHostWithLoginMappings) {
|
if (!newSshHostWithLoginMappings) {
|
||||||
@@ -317,10 +337,7 @@ export const sshHostServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
if (allowedPrincipals.usernames.length === 0) {
|
if (allowedPrincipals.usernames.length > 0) {
|
||||||
continue; // or maybe insert no mappings and just skip validation
|
|
||||||
}
|
|
||||||
|
|
||||||
const users = await userDAL.find(
|
const users = await userDAL.find(
|
||||||
{
|
{
|
||||||
$in: {
|
$in: {
|
||||||
@@ -360,6 +377,7 @@ export const sshHostServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatedHostWithLoginMappings = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId, tx);
|
const updatedHostWithLoginMappings = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId, tx);
|
||||||
if (!updatedHostWithLoginMappings) {
|
if (!updatedHostWithLoginMappings) {
|
||||||
|
|||||||
@@ -1328,7 +1328,7 @@ export const SSH_HOSTS = {
|
|||||||
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
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.",
|
allowedPrincipals: "A list of allowed principals that can log in as the login user.",
|
||||||
loginMappings:
|
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:
|
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.",
|
"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:
|
hostSshCaId:
|
||||||
@@ -1342,7 +1342,7 @@ export const SSH_HOSTS = {
|
|||||||
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
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.",
|
allowedPrincipals: "A list of allowed principals that can log in as the login user.",
|
||||||
loginMappings:
|
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: {
|
DELETE: {
|
||||||
sshHostId: "The ID of the SSH host to delete."
|
sshHostId: "The ID of the SSH host to delete."
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ require (
|
|||||||
github.com/fatih/semgroup v1.2.0
|
github.com/fatih/semgroup v1.2.0
|
||||||
github.com/gitleaks/go-gitdiff v0.8.0
|
github.com/gitleaks/go-gitdiff v0.8.0
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/infisical/go-sdk v0.5.5
|
github.com/infisical/go-sdk v0.5.7
|
||||||
github.com/infisical/infisical-kmip v0.3.5
|
github.com/infisical/infisical-kmip v0.3.5
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
||||||
|
|||||||
+2
-2
@@ -277,8 +277,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
|||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/infisical/go-sdk v0.5.5 h1:A0KfqZvRWScjVj19dbh2uHH4wSsElj5cTAgcT1Adezs=
|
github.com/infisical/go-sdk v0.5.7 h1:q/gQGmbTvpCJYlhE3pyyWqifdQeM6yJyiYRIlXK4nXw=
|
||||||
github.com/infisical/go-sdk v0.5.5/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
|
github.com/infisical/go-sdk v0.5.7/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
|
||||||
github.com/infisical/infisical-kmip v0.3.5 h1:QM3s0e18B+mYv3a9HQNjNAlbwZJBzXq5BAJM2scIeiE=
|
github.com/infisical/infisical-kmip v0.3.5 h1:QM3s0e18B+mYv3a9HQNjNAlbwZJBzXq5BAJM2scIeiE=
|
||||||
github.com/infisical/infisical-kmip v0.3.5/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs=
|
github.com/infisical/infisical-kmip v0.3.5/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs=
|
||||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||||
|
|||||||
@@ -103,10 +103,9 @@ we will register a remote host with Infisical through a [machine identity](/docu
|
|||||||
4.2. On the registered host in the **Hosts** tab, click **Edit SSH Host** and add a login mapping for the user(s) you added in step 4.1.
|
4.2. On the registered host in the **Hosts** tab, click **Edit SSH Host** and add a login mapping for the user(s) you added in step 4.1.
|
||||||
|
|
||||||
The login mapping dictates what user(s) will be allowed access to the remote host and under a specific login user; in the allowed principals,
|
The login mapping dictates what user(s) will be allowed access to the remote host and under a specific login user; in the allowed principals,
|
||||||
you should input a comma-separated list of usernames of users part of the Infisical SSH project that will be allowed to login to the remote host as the login user.
|
you should select user(s) part of the Infisical SSH project that will be allowed to login to the remote host as the login user.
|
||||||
|
|
||||||
For instance, if you add a mapping with the login user `ec2-user` with allowed principals of `[email protected]` and `[email protected]`
|
For instance, if you add a mapping with the login user `ec2-user` to some users John and Alice in Infisical, then they will be allowed to login to the remote host as `ec2-user` which is a system user that
|
||||||
then both users with the username `[email protected]` and `[email protected]` will be allowed to login to the remote host as `ec2-user` which is a system user that
|
|
||||||
exists on the remote host.
|
exists on the remote host.
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 620 KiB After Width: | Height: | Size: 643 KiB |
@@ -10,7 +10,7 @@ export const SshHostsPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: "Certificates" })}</title>
|
<title>{t("common.head-title", { title: "SSH" })}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="h-full bg-bunker-800">
|
<div className="h-full bg-bunker-800">
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
|||||||
Reference in New Issue
Block a user