mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
Add ssh host endpoint for issuing ssh host cert
This commit is contained in:
@@ -47,6 +47,14 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
await createOnUpdateTrigger(knex, TableName.ProjectSshConfig);
|
await createOnUpdateTrigger(knex, TableName.ProjectSshConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasColumn = await knex.schema.hasColumn(TableName.SshCertificate, "sshHostId");
|
||||||
|
if (!hasColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SshCertificate, (t) => {
|
||||||
|
t.uuid("sshHostId").nullable();
|
||||||
|
t.foreign("sshHostId").references("id").inTable(TableName.SshHost).onDelete("SET NULL");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
@@ -58,4 +66,11 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
|
|
||||||
await knex.schema.dropTableIfExists(TableName.SshHost);
|
await knex.schema.dropTableIfExists(TableName.SshHost);
|
||||||
await dropOnUpdateTrigger(knex, TableName.SshHost);
|
await dropOnUpdateTrigger(knex, TableName.SshHost);
|
||||||
|
|
||||||
|
const hasColumn = await knex.schema.hasColumn(TableName.SshCertificate, "sshHostId");
|
||||||
|
if (hasColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SshCertificate, (t) => {
|
||||||
|
t.dropColumn("sshHostId");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export const SshCertificatesSchema = z.object({
|
|||||||
principals: z.string().array(),
|
principals: z.string().array(),
|
||||||
keyId: z.string(),
|
keyId: z.string(),
|
||||||
notBefore: z.date(),
|
notBefore: z.date(),
|
||||||
notAfter: z.date()
|
notAfter: z.date(),
|
||||||
|
sshHostId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSshCertificates = z.infer<typeof SshCertificatesSchema>;
|
export type TSshCertificates = z.infer<typeof SshCertificatesSchema>;
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
|
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
|
||||||
import { sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema";
|
import { sanitizedSshHost } from "@app/ee/services/ssh-host/ssh-host-schema";
|
||||||
import { isValidHostname } from "@app/ee/services/ssh-host/ssh-host-validators";
|
import { isValidHostname } from "@app/ee/services/ssh-host/ssh-host-validators";
|
||||||
import { SSH_HOSTS } from "@app/lib/api-docs";
|
import { SSH_HOSTS } from "@app/lib/api-docs";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { publicSshCaLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -38,18 +41,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
// TODO: consider adding audit log
|
||||||
// TODO: audit log
|
|
||||||
// await server.services.auditLog.createAuditLog({
|
|
||||||
// ...req.auditLogInfo,
|
|
||||||
// projectId: certificateTemplate.projectId,
|
|
||||||
// event: {
|
|
||||||
// type: EventType.GET_SSH_CERTIFICATE_TEMPLATE,
|
|
||||||
// metadata: {
|
|
||||||
// certificateTemplateId: certificateTemplate.id
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
return hosts;
|
return hosts;
|
||||||
}
|
}
|
||||||
@@ -86,17 +78,17 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: audit log
|
await server.services.auditLog.createAuditLog({
|
||||||
// await server.services.auditLog.createAuditLog({
|
...req.auditLogInfo,
|
||||||
// ...req.auditLogInfo,
|
projectId: host.projectId,
|
||||||
// projectId: certificateTemplate.projectId,
|
event: {
|
||||||
// event: {
|
type: EventType.GET_SSH_HOST,
|
||||||
// type: EventType.GET_SSH_CERTIFICATE_TEMPLATE,
|
metadata: {
|
||||||
// metadata: {
|
sshHostId: host.id,
|
||||||
// certificateTemplateId: certificateTemplate.id
|
hostname: host.hostname
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
@@ -161,26 +153,22 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: audit log
|
await server.services.auditLog.createAuditLog({
|
||||||
// await server.services.auditLog.createAuditLog({
|
...req.auditLogInfo,
|
||||||
// ...req.auditLogInfo,
|
projectId: host.projectId,
|
||||||
// projectId: ca.projectId,
|
event: {
|
||||||
// event: {
|
type: EventType.CREATE_SSH_HOST,
|
||||||
// type: EventType.CREATE_SSH_CERTIFICATE_TEMPLATE,
|
metadata: {
|
||||||
// metadata: {
|
sshHostId: host.id,
|
||||||
// certificateTemplateId: certificateTemplate.id,
|
hostname: host.hostname,
|
||||||
// sshCaId: ca.id,
|
userCertTtl: host.userCertTtl,
|
||||||
// name: certificateTemplate.name,
|
hostCertTtl: host.hostCertTtl,
|
||||||
// ttl: certificateTemplate.ttl,
|
loginMappings: host.loginMappings,
|
||||||
// maxTTL: certificateTemplate.maxTTL,
|
userSshCaId: host.userSshCaId,
|
||||||
// allowedUsers: certificateTemplate.allowedUsers,
|
hostSshCaId: host.hostSshCaId
|
||||||
// allowedHosts: certificateTemplate.allowedHosts,
|
}
|
||||||
// allowUserCertificates: certificateTemplate.allowUserCertificates,
|
}
|
||||||
// allowHostCertificates: certificateTemplate.allowHostCertificates,
|
});
|
||||||
// allowCustomKeyIds: certificateTemplate.allowCustomKeyIds
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
@@ -205,16 +193,17 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((v) => isValidHostname(v), {
|
.refine((v) => isValidHostname(v), {
|
||||||
message: "Hostname must be a valid hostname"
|
message: "Hostname must be a valid hostname"
|
||||||
})
|
})
|
||||||
|
.optional()
|
||||||
.describe(SSH_HOSTS.UPDATE.hostname),
|
.describe(SSH_HOSTS.UPDATE.hostname),
|
||||||
userCertTtl: z
|
userCertTtl: z
|
||||||
.string()
|
.string()
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.default("1h")
|
.optional()
|
||||||
.describe(SSH_HOSTS.UPDATE.userCertTtl),
|
.describe(SSH_HOSTS.UPDATE.userCertTtl),
|
||||||
hostCertTtl: z
|
hostCertTtl: z
|
||||||
.string()
|
.string()
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.default("1y")
|
.optional()
|
||||||
.describe(SSH_HOSTS.UPDATE.hostCertTtl),
|
.describe(SSH_HOSTS.UPDATE.hostCertTtl),
|
||||||
loginMappings: z
|
loginMappings: z
|
||||||
.object({
|
.object({
|
||||||
@@ -246,19 +235,22 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: audit log
|
await server.services.auditLog.createAuditLog({
|
||||||
// await server.services.auditLog.createAuditLog({
|
...req.auditLogInfo,
|
||||||
// ...req.auditLogInfo,
|
projectId: host.projectId,
|
||||||
// projectId: ca.projectId,
|
event: {
|
||||||
// event: {
|
type: EventType.UPDATE_SSH_HOST,
|
||||||
// type: EventType.UPDATE_SSH_CA,
|
metadata: {
|
||||||
// metadata: {
|
sshHostId: host.id,
|
||||||
// sshCaId: ca.id,
|
hostname: host.hostname,
|
||||||
// friendlyName: ca.friendlyName,
|
userCertTtl: host.userCertTtl,
|
||||||
// status: ca.status as SshCaStatus
|
hostCertTtl: host.hostCertTtl,
|
||||||
// }
|
loginMappings: host.loginMappings,
|
||||||
// }
|
userSshCaId: host.userSshCaId,
|
||||||
// });
|
hostSshCaId: host.hostSshCaId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
@@ -295,17 +287,17 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: audit log
|
await server.services.auditLog.createAuditLog({
|
||||||
// await server.services.auditLog.createAuditLog({
|
...req.auditLogInfo,
|
||||||
// ...req.auditLogInfo,
|
projectId: host.projectId,
|
||||||
// projectId: certificateTemplate.projectId,
|
event: {
|
||||||
// event: {
|
type: EventType.DELETE_SSH_HOST,
|
||||||
// type: EventType.DELETE_SSH_CERTIFICATE_TEMPLATE,
|
metadata: {
|
||||||
// metadata: {
|
sshHostId: host.id,
|
||||||
// certificateTemplateId: certificateTemplate.id
|
hostname: host.hostname
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
@@ -313,7 +305,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:sshHostId/issue",
|
url: "/:sshHostId/issue-user-cert",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
@@ -321,7 +313,10 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Issue SSH credentials (certificate + key)",
|
description: "Issue SSH credentials (certificate + key)",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sshHostId: z.string().describe(SSH_HOSTS.DELETE.sshHostId)
|
sshHostId: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.sshHostId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
loginUser: z.string().describe(SSH_HOSTS.ISSUE_SSH_CREDENTIALS.loginUser)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -334,41 +329,41 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { serialNumber, signedPublicKey, privateKey, publicKey, keyAlgorithm } =
|
const { serialNumber, signedPublicKey, privateKey, publicKey, keyAlgorithm, host, principals } =
|
||||||
await server.services.sshHost.issueSshCredsFromHost({
|
await server.services.sshHost.issueSshHostUserCert({
|
||||||
sshHostId: req.params.sshHostId,
|
sshHostId: req.params.sshHostId,
|
||||||
|
loginUser: req.body.loginUser,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: add audit log
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ISSUE_SSH_HOST_USER_CERT,
|
||||||
|
metadata: {
|
||||||
|
sshHostId: req.params.sshHostId,
|
||||||
|
hostname: host.hostname,
|
||||||
|
loginUser: req.body.loginUser,
|
||||||
|
principals,
|
||||||
|
ttl: host.userCertTtl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// await server.services.auditLog.createAuditLog({
|
await server.services.telemetry.sendPostHogEvents({
|
||||||
// ...req.auditLogInfo,
|
event: PostHogEventTypes.IssueSshHostUserCert,
|
||||||
// orgId: req.permission.orgId,
|
distinctId: getTelemetryDistinctId(req),
|
||||||
// event: {
|
properties: {
|
||||||
// type: EventType.ISSUE_SSH_CREDS,
|
sshHostId: req.params.sshHostId,
|
||||||
// metadata: {
|
hostname: host.hostname,
|
||||||
// certificateTemplateId: certificateTemplate.id,
|
principals,
|
||||||
// keyAlgorithm: req.body.keyAlgorithm,
|
...req.auditLogInfo
|
||||||
// certType: req.body.certType,
|
}
|
||||||
// principals: req.body.principals,
|
});
|
||||||
// ttl: String(ttl),
|
|
||||||
// keyId
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// await server.services.telemetry.sendPostHogEvents({
|
|
||||||
// event: PostHogEventTypes.IssueSshCreds,
|
|
||||||
// distinctId: getTelemetryDistinctId(req),
|
|
||||||
// properties: {
|
|
||||||
// certificateTemplateId: req.body.certificateTemplateId,
|
|
||||||
// principals: req.body.principals,
|
|
||||||
// ...req.auditLogInfo
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
serialNumber,
|
serialNumber,
|
||||||
@@ -379,4 +374,90 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:sshHostId/issue-host-cert",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Issue SSH certificate for host",
|
||||||
|
params: z.object({
|
||||||
|
sshHostId: z.string().describe(SSH_HOSTS.DELETE.sshHostId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
publicKey: z.string().describe(SSH_HOSTS.ISSUE_HOST_CERT.publicKey)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
serialNumber: z.string().describe(SSH_HOSTS.ISSUE_HOST_CERT.serialNumber),
|
||||||
|
signedKey: z.string().describe(SSH_HOSTS.ISSUE_HOST_CERT.signedKey)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { host, principals, serialNumber, signedPublicKey } = await server.services.sshHost.issueSshHostHostCert({
|
||||||
|
sshHostId: req.params.sshHostId,
|
||||||
|
publicKey: req.body.publicKey,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ISSUE_SSH_HOST_HOST_CERT,
|
||||||
|
metadata: {
|
||||||
|
sshHostId: req.params.sshHostId,
|
||||||
|
hostname: host.hostname,
|
||||||
|
principals,
|
||||||
|
serialNumber,
|
||||||
|
ttl: host.hostCertTtl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.telemetry.sendPostHogEvents({
|
||||||
|
event: PostHogEventTypes.IssueSshHostHostCert,
|
||||||
|
distinctId: getTelemetryDistinctId(req),
|
||||||
|
properties: {
|
||||||
|
sshHostId: req.params.sshHostId,
|
||||||
|
hostname: host.hostname,
|
||||||
|
principals,
|
||||||
|
...req.auditLogInfo
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
serialNumber,
|
||||||
|
signedKey: signedPublicKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:sshHostId/user-ca-public-key",
|
||||||
|
config: {
|
||||||
|
rateLimit: publicSshCaLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get public key of the user SSH CA linked to the host",
|
||||||
|
params: z.object({
|
||||||
|
sshHostId: z.string().trim().describe(SSH_HOSTS.GET_USER_CA_PUBLIC_KEY.sshHostId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const publicKey = await server.services.sshHost.getSshHostUserCaPk(req.params.sshHostId);
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -181,6 +181,12 @@ export enum EventType {
|
|||||||
UPDATE_SSH_CERTIFICATE_TEMPLATE = "update-ssh-certificate-template",
|
UPDATE_SSH_CERTIFICATE_TEMPLATE = "update-ssh-certificate-template",
|
||||||
DELETE_SSH_CERTIFICATE_TEMPLATE = "delete-ssh-certificate-template",
|
DELETE_SSH_CERTIFICATE_TEMPLATE = "delete-ssh-certificate-template",
|
||||||
GET_SSH_CERTIFICATE_TEMPLATE = "get-ssh-certificate-template",
|
GET_SSH_CERTIFICATE_TEMPLATE = "get-ssh-certificate-template",
|
||||||
|
CREATE_SSH_HOST = "create-ssh-host",
|
||||||
|
UPDATE_SSH_HOST = "update-ssh-host",
|
||||||
|
DELETE_SSH_HOST = "delete-ssh-host",
|
||||||
|
GET_SSH_HOST = "get-ssh-host",
|
||||||
|
ISSUE_SSH_HOST_USER_CERT = "issue-ssh-host-user-cert",
|
||||||
|
ISSUE_SSH_HOST_HOST_CERT = "issue-ssh-host-host-cert",
|
||||||
CREATE_CA = "create-certificate-authority",
|
CREATE_CA = "create-certificate-authority",
|
||||||
GET_CA = "get-certificate-authority",
|
GET_CA = "get-certificate-authority",
|
||||||
UPDATE_CA = "update-certificate-authority",
|
UPDATE_CA = "update-certificate-authority",
|
||||||
@@ -1455,6 +1461,76 @@ interface DeleteSshCertificateTemplate {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateSshHost {
|
||||||
|
type: EventType.CREATE_SSH_HOST;
|
||||||
|
metadata: {
|
||||||
|
sshHostId: string;
|
||||||
|
hostname: string;
|
||||||
|
userCertTtl: string;
|
||||||
|
hostCertTtl: string;
|
||||||
|
loginMappings: {
|
||||||
|
loginUser: string;
|
||||||
|
allowedPrincipals: string[];
|
||||||
|
}[];
|
||||||
|
userSshCaId: string;
|
||||||
|
hostSshCaId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSshHost {
|
||||||
|
type: EventType.UPDATE_SSH_HOST;
|
||||||
|
metadata: {
|
||||||
|
sshHostId: string;
|
||||||
|
hostname?: string;
|
||||||
|
userCertTtl?: string;
|
||||||
|
hostCertTtl?: string;
|
||||||
|
loginMappings?: {
|
||||||
|
loginUser: string;
|
||||||
|
allowedPrincipals: string[];
|
||||||
|
}[];
|
||||||
|
userSshCaId?: string;
|
||||||
|
hostSshCaId?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSshHost {
|
||||||
|
type: EventType.DELETE_SSH_HOST;
|
||||||
|
metadata: {
|
||||||
|
sshHostId: string;
|
||||||
|
hostname: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSshHost {
|
||||||
|
type: EventType.GET_SSH_HOST;
|
||||||
|
metadata: {
|
||||||
|
sshHostId: string;
|
||||||
|
hostname: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IssueSshHostUserCert {
|
||||||
|
type: EventType.ISSUE_SSH_HOST_USER_CERT;
|
||||||
|
metadata: {
|
||||||
|
sshHostId: string;
|
||||||
|
hostname: string;
|
||||||
|
loginUser: string;
|
||||||
|
principals: string[];
|
||||||
|
ttl: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IssueSshHostHostCert {
|
||||||
|
type: EventType.ISSUE_SSH_HOST_HOST_CERT;
|
||||||
|
metadata: {
|
||||||
|
sshHostId: string;
|
||||||
|
hostname: string;
|
||||||
|
serialNumber: string;
|
||||||
|
principals: string[];
|
||||||
|
ttl: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateCa {
|
interface CreateCa {
|
||||||
type: EventType.CREATE_CA;
|
type: EventType.CREATE_CA;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -2409,6 +2485,12 @@ export type Event =
|
|||||||
| UpdateSshCertificateTemplate
|
| UpdateSshCertificateTemplate
|
||||||
| GetSshCertificateTemplate
|
| GetSshCertificateTemplate
|
||||||
| DeleteSshCertificateTemplate
|
| DeleteSshCertificateTemplate
|
||||||
|
| CreateSshHost
|
||||||
|
| UpdateSshHost
|
||||||
|
| DeleteSshHost
|
||||||
|
| GetSshHost
|
||||||
|
| IssueSshHostUserCert
|
||||||
|
| IssueSshHostHostCert
|
||||||
| CreateCa
|
| CreateCa
|
||||||
| GetCa
|
| GetCa
|
||||||
| UpdateCa
|
| UpdateCa
|
||||||
|
|||||||
@@ -5,5 +5,7 @@ export const sanitizedSshHost = SshHostsSchema.pick({
|
|||||||
projectId: true,
|
projectId: true,
|
||||||
hostname: true,
|
hostname: true,
|
||||||
userCertTtl: true,
|
userCertTtl: true,
|
||||||
hostCertTtl: true
|
hostCertTtl: true,
|
||||||
|
userSshCaId: true,
|
||||||
|
hostSshCaId: true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,23 +5,31 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||||
import { TSshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal";
|
import { TSshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal";
|
||||||
|
import { TSshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal";
|
||||||
|
import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal";
|
||||||
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
|
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
|
||||||
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
|
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
|
||||||
import { TSshHostLoginMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-mapping-dal";
|
import { TSshHostLoginMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-mapping-dal";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal";
|
import { TProjectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
import { convertActorToPrincipals, createSshCert, createSshKeyPair } from "../ssh/ssh-certificate-authority-fns";
|
import {
|
||||||
|
convertActorToPrincipals,
|
||||||
|
createSshCert,
|
||||||
|
createSshKeyPair,
|
||||||
|
getSshPublicKey
|
||||||
|
} from "../ssh/ssh-certificate-authority-fns";
|
||||||
import { SshCertType } from "../ssh/ssh-certificate-authority-types";
|
import { SshCertType } from "../ssh/ssh-certificate-authority-types";
|
||||||
import {
|
import {
|
||||||
TCreateSshHostDTO,
|
TCreateSshHostDTO,
|
||||||
TDeleteSshHostDTO,
|
TDeleteSshHostDTO,
|
||||||
TGetSshHostDTO,
|
TGetSshHostDTO,
|
||||||
TIssueSshCredsFromHostDTO,
|
TIssueSshHostHostCertDTO,
|
||||||
|
TIssueSshHostUserCertDTO,
|
||||||
TListSshHostsDTO,
|
TListSshHostsDTO,
|
||||||
TUpdateSshHostDTO
|
TUpdateSshHostDTO
|
||||||
} from "./ssh-host-types";
|
} from "./ssh-host-types";
|
||||||
@@ -32,6 +40,8 @@ type TSshCertificateAuthorityServiceFactoryDep = {
|
|||||||
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">;
|
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">;
|
||||||
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findById">;
|
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findById">;
|
||||||
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "findOne">;
|
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "findOne">;
|
||||||
|
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">;
|
||||||
|
sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">;
|
||||||
sshHostDAL: Pick<
|
sshHostDAL: Pick<
|
||||||
TSshHostDALFactory,
|
TSshHostDALFactory,
|
||||||
| "transaction"
|
| "transaction"
|
||||||
@@ -64,6 +74,8 @@ export const sshHostServiceFactory = ({
|
|||||||
projectSshConfigDAL,
|
projectSshConfigDAL,
|
||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
sshCertificateAuthoritySecretDAL,
|
sshCertificateAuthoritySecretDAL,
|
||||||
|
sshCertificateDAL,
|
||||||
|
sshCertificateBodyDAL,
|
||||||
sshHostDAL,
|
sshHostDAL,
|
||||||
sshHostLoginMappingDAL,
|
sshHostLoginMappingDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -104,15 +116,13 @@ export const sshHostServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const principals = await convertActorToPrincipals({
|
const principals = await convertActorToPrincipals({
|
||||||
// actor,
|
actor,
|
||||||
// actorId,
|
actorId,
|
||||||
// userDAL
|
userDAL
|
||||||
// });
|
});
|
||||||
|
|
||||||
const hosts = await sshHostDAL.findSshHostsWithPrincipalsAcrossProjects(projectIdsWithAccess, [
|
const hosts = await sshHostDAL.findSshHostsWithPrincipalsAcrossProjects(projectIdsWithAccess, principals);
|
||||||
"[email protected]" // hardcode for now
|
|
||||||
]);
|
|
||||||
|
|
||||||
return hosts;
|
return hosts;
|
||||||
};
|
};
|
||||||
@@ -336,13 +346,14 @@ export const sshHostServiceFactory = ({
|
|||||||
*
|
*
|
||||||
* Note: Used for issuing SSH credentials as part of request against a specific SSH Host.
|
* Note: Used for issuing SSH credentials as part of request against a specific SSH Host.
|
||||||
*/
|
*/
|
||||||
const issueSshCredsFromHost = async ({
|
const issueSshHostUserCert = async ({
|
||||||
sshHostId,
|
sshHostId,
|
||||||
|
loginUser,
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TIssueSshCredsFromHostDTO) => {
|
}: TIssueSshHostUserCertDTO) => {
|
||||||
const host = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId);
|
const host = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId);
|
||||||
if (!host) {
|
if (!host) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -376,16 +387,29 @@ export const sshHostServiceFactory = ({
|
|||||||
cipherTextBlob: sshCaSecret.encryptedPrivateKey
|
cipherTextBlob: sshCaSecret.encryptedPrivateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
// create user key pair
|
// (dangtony98): will support more algorithms in the future
|
||||||
const keyAlgorithm = SshCertKeyAlgorithm.ED25519; // (dangtony98): will support more algorithms in the future
|
const keyAlgorithm = SshCertKeyAlgorithm.ED25519;
|
||||||
const { publicKey, privateKey } = await createSshKeyPair(keyAlgorithm);
|
const { publicKey, privateKey } = await createSshKeyPair(keyAlgorithm);
|
||||||
|
|
||||||
const principals = await convertActorToPrincipals({
|
const internalPrincipals = await convertActorToPrincipals({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
userDAL
|
userDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mapping = host.loginMappings.find(
|
||||||
|
(m) => m.loginUser === loginUser && m.allowedPrincipals.some((allowed) => internalPrincipals.includes(allowed))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!mapping) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: `You are not allowed to login as ${loginUser} on this host`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// (dangtony98): include the loginUser as a principal on the issued certificate
|
||||||
|
const principals = [...internalPrincipals, loginUser];
|
||||||
|
|
||||||
const { serialNumber, signedPublicKey, ttl } = await createSshCert({
|
const { serialNumber, signedPublicKey, ttl } = await createSshCert({
|
||||||
caPrivateKey: decryptedCaPrivateKey.toString("utf8"),
|
caPrivateKey: decryptedCaPrivateKey.toString("utf8"),
|
||||||
clientPublicKey: publicKey,
|
clientPublicKey: publicKey,
|
||||||
@@ -395,23 +419,169 @@ export const sshHostServiceFactory = ({
|
|||||||
certType: SshCertType.USER
|
certType: SshCertType.USER
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: host.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
const encryptedCertificate = secretManagerEncryptor({
|
||||||
|
plainText: Buffer.from(signedPublicKey, "utf8")
|
||||||
|
}).cipherTextBlob;
|
||||||
|
|
||||||
|
await sshCertificateDAL.transaction(async (tx) => {
|
||||||
|
const cert = await sshCertificateDAL.create(
|
||||||
|
{
|
||||||
|
sshCaId: host.hostSshCaId,
|
||||||
|
sshHostId: host.id,
|
||||||
|
serialNumber,
|
||||||
|
certType: SshCertType.USER,
|
||||||
|
principals,
|
||||||
|
keyId,
|
||||||
|
notBefore: new Date(),
|
||||||
|
notAfter: new Date(Date.now() + ttl * 1000)
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await sshCertificateBodyDAL.create(
|
||||||
|
{
|
||||||
|
sshCertId: cert.id,
|
||||||
|
encryptedCertificate
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
host,
|
||||||
|
principals,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
signedPublicKey,
|
signedPublicKey,
|
||||||
privateKey,
|
privateKey,
|
||||||
publicKey,
|
publicKey,
|
||||||
ttl,
|
ttl,
|
||||||
keyId,
|
|
||||||
keyAlgorithm
|
keyAlgorithm
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const issueSshHostHostCert = async ({
|
||||||
|
sshHostId,
|
||||||
|
publicKey,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TIssueSshHostHostCertDTO) => {
|
||||||
|
const host = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId);
|
||||||
|
if (!host) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `SSH host with ID ${sshHostId} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: host.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHosts);
|
||||||
|
// TODO: update permissions
|
||||||
|
|
||||||
|
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: host.hostSshCaId });
|
||||||
|
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: host.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedCaPrivateKey = secretManagerDecryptor({
|
||||||
|
cipherTextBlob: sshCaSecret.encryptedPrivateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const principals = [host.hostname];
|
||||||
|
const keyId = `host:${host.hostname}`;
|
||||||
|
|
||||||
|
const { serialNumber, signedPublicKey, ttl } = await createSshCert({
|
||||||
|
caPrivateKey: decryptedCaPrivateKey.toString("utf8"),
|
||||||
|
clientPublicKey: publicKey,
|
||||||
|
keyId,
|
||||||
|
principals,
|
||||||
|
requestedTtl: host.hostCertTtl,
|
||||||
|
certType: SshCertType.HOST
|
||||||
|
});
|
||||||
|
|
||||||
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: host.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
const encryptedCertificate = secretManagerEncryptor({
|
||||||
|
plainText: Buffer.from(signedPublicKey, "utf8")
|
||||||
|
}).cipherTextBlob;
|
||||||
|
|
||||||
|
await sshCertificateDAL.transaction(async (tx) => {
|
||||||
|
const cert = await sshCertificateDAL.create(
|
||||||
|
{
|
||||||
|
sshCaId: host.hostSshCaId,
|
||||||
|
sshHostId: host.id,
|
||||||
|
serialNumber,
|
||||||
|
certType: SshCertType.HOST,
|
||||||
|
principals,
|
||||||
|
keyId,
|
||||||
|
notBefore: new Date(),
|
||||||
|
notAfter: new Date(Date.now() + ttl * 1000)
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await sshCertificateBodyDAL.create(
|
||||||
|
{
|
||||||
|
sshCertId: cert.id,
|
||||||
|
encryptedCertificate
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { host, principals, serialNumber, signedPublicKey };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSshHostUserCaPk = async (sshHostId: string) => {
|
||||||
|
const host = await sshHostDAL.findById(sshHostId);
|
||||||
|
if (!host) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `SSH host with ID ${sshHostId} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: host.userSshCaId });
|
||||||
|
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId: host.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedCaPrivateKey = secretManagerDecryptor({
|
||||||
|
cipherTextBlob: sshCaSecret.encryptedPrivateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const publicKey = await getSshPublicKey(decryptedCaPrivateKey.toString("utf-8"));
|
||||||
|
|
||||||
|
return publicKey;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listSshHosts,
|
listSshHosts,
|
||||||
createSshHost,
|
createSshHost,
|
||||||
updateSshHost,
|
updateSshHost,
|
||||||
deleteSshHost,
|
deleteSshHost,
|
||||||
getSshHost,
|
getSshHost,
|
||||||
issueSshCredsFromHost
|
issueSshHostUserCert,
|
||||||
|
issueSshHostHostCert,
|
||||||
|
getSshHostUserCaPk
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ export type TDeleteSshHostDTO = {
|
|||||||
sshHostId: string;
|
sshHostId: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TIssueSshCredsFromHostDTO = {
|
export type TIssueSshHostUserCertDTO = {
|
||||||
sshHostId: string;
|
sshHostId: string;
|
||||||
|
loginUser: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TIssueSshHostHostCertDTO = {
|
||||||
|
sshHostId: string;
|
||||||
|
publicKey: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -300,7 +300,12 @@ export const validateSshCertificateTtl = (template: TSshCertificateTemplates, tt
|
|||||||
* that it only contains alphanumeric characters with no spaces.
|
* that it only contains alphanumeric characters with no spaces.
|
||||||
*/
|
*/
|
||||||
export const validateSshCertificateKeyId = (keyId: string) => {
|
export const validateSshCertificateKeyId = (keyId: string) => {
|
||||||
const regex = characterValidator([CharacterType.AlphaNumeric, CharacterType.Hyphen]);
|
const regex = characterValidator([
|
||||||
|
CharacterType.AlphaNumeric,
|
||||||
|
CharacterType.Hyphen,
|
||||||
|
CharacterType.Colon,
|
||||||
|
CharacterType.Period
|
||||||
|
]);
|
||||||
if (!regex(keyId)) {
|
if (!regex(keyId)) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message:
|
message:
|
||||||
|
|||||||
@@ -1348,11 +1348,21 @@ export const SSH_HOSTS = {
|
|||||||
sshHostId: "The ID of the SSH host to delete."
|
sshHostId: "The ID of the SSH host to delete."
|
||||||
},
|
},
|
||||||
ISSUE_SSH_CREDENTIALS: {
|
ISSUE_SSH_CREDENTIALS: {
|
||||||
|
sshHostId: "The ID of the SSH host to issue the SSH credentials for.",
|
||||||
|
loginUser: "The login user to issue the SSH credentials for.",
|
||||||
keyAlgorithm: "The type of public key algorithm and size, in bits, of the key pair for the SSH host.",
|
keyAlgorithm: "The type of public key algorithm and size, in bits, of the key pair for the SSH host.",
|
||||||
serialNumber: "The serial number of the issued SSH certificate.",
|
serialNumber: "The serial number of the issued SSH certificate.",
|
||||||
signedKey: "The SSH certificate or signed SSH public key.",
|
signedKey: "The SSH certificate or signed SSH public key.",
|
||||||
privateKey: "The private key corresponding to the issued SSH certificate.",
|
privateKey: "The private key corresponding to the issued SSH certificate.",
|
||||||
publicKey: "The public key of the issued SSH certificate."
|
publicKey: "The public key of the issued SSH certificate."
|
||||||
|
},
|
||||||
|
ISSUE_HOST_CERT: {
|
||||||
|
publicKey: "The SSH public key to issue the SSH certificate for.",
|
||||||
|
serialNumber: "The serial number of the issued SSH certificate.",
|
||||||
|
signedKey: "The SSH certificate or signed SSH public key."
|
||||||
|
},
|
||||||
|
GET_USER_CA_PUBLIC_KEY: {
|
||||||
|
sshHostId: "The ID of the SSH host to get the user SSH CA public key for."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -93,3 +93,10 @@ export const userEngagementLimit: RateLimitOptions = {
|
|||||||
max: 5,
|
max: 5,
|
||||||
keyGenerator: (req) => req.realIp
|
keyGenerator: (req) => req.realIp
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const publicSshCaLimit: RateLimitOptions = {
|
||||||
|
timeWindow: 60 * 1000,
|
||||||
|
hook: "preValidation",
|
||||||
|
max: 30, // conservative default
|
||||||
|
keyGenerator: (req) => req.realIp
|
||||||
|
};
|
||||||
|
|||||||
@@ -803,6 +803,8 @@ export const registerRoutes = async (
|
|||||||
projectSshConfigDAL,
|
projectSshConfigDAL,
|
||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
sshCertificateAuthoritySecretDAL,
|
sshCertificateAuthoritySecretDAL,
|
||||||
|
sshCertificateDAL,
|
||||||
|
sshCertificateBodyDAL,
|
||||||
sshHostDAL,
|
sshHostDAL,
|
||||||
sshHostLoginMappingDAL,
|
sshHostLoginMappingDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export enum PostHogEventTypes {
|
|||||||
SecretRequestDeleted = "Secret Request Deleted",
|
SecretRequestDeleted = "Secret Request Deleted",
|
||||||
SignSshKey = "Sign SSH Key",
|
SignSshKey = "Sign SSH Key",
|
||||||
IssueSshCreds = "Issue SSH Credentials",
|
IssueSshCreds = "Issue SSH Credentials",
|
||||||
|
IssueSshHostUserCert = "Issue SSH Host User Certificate",
|
||||||
|
IssueSshHostHostCert = "Issue SSH Host Host Certificate",
|
||||||
SignCert = "Sign PKI Certificate",
|
SignCert = "Sign PKI Certificate",
|
||||||
IssueCert = "Issue PKI Certificate"
|
IssueCert = "Issue PKI Certificate"
|
||||||
}
|
}
|
||||||
@@ -161,6 +163,26 @@ export type TIssueSshCredsEvent = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TIssueSshHostUserCertEvent = {
|
||||||
|
event: PostHogEventTypes.IssueSshHostUserCert;
|
||||||
|
properties: {
|
||||||
|
sshHostId: string;
|
||||||
|
hostname: string;
|
||||||
|
principals: string[];
|
||||||
|
userAgent?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueSshHostHostCertEvent = {
|
||||||
|
event: PostHogEventTypes.IssueSshHostHostCert;
|
||||||
|
properties: {
|
||||||
|
sshHostId: string;
|
||||||
|
hostname: string;
|
||||||
|
principals: string[];
|
||||||
|
userAgent?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type TSignCertificateEvent = {
|
export type TSignCertificateEvent = {
|
||||||
event: PostHogEventTypes.SignCert;
|
event: PostHogEventTypes.SignCert;
|
||||||
properties: {
|
properties: {
|
||||||
@@ -195,6 +217,8 @@ export type TPostHogEvent = { distinctId: string } & (
|
|||||||
| TSecretRequestDeletedEvent
|
| TSecretRequestDeletedEvent
|
||||||
| TSignSshKeyEvent
|
| TSignSshKeyEvent
|
||||||
| TIssueSshCredsEvent
|
| TIssueSshCredsEvent
|
||||||
|
| TIssueSshHostUserCertEvent
|
||||||
|
| TIssueSshHostHostCertEvent
|
||||||
| TSignCertificateEvent
|
| TSignCertificateEvent
|
||||||
| TIssueCertificateEvent
|
| TIssueCertificateEvent
|
||||||
);
|
);
|
||||||
|
|||||||
+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.3
|
github.com/infisical/go-sdk v0.5.4
|
||||||
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.3 h1:6qQCkxR1NqeoYu/6gL9BvQARr/apupX7+Ei5adCRTCU=
|
github.com/infisical/go-sdk v0.5.4 h1:/Jbl9DLYLmYA3A9W8YB7Kqhm8vymL1WeoITvjXBCq8w=
|
||||||
github.com/infisical/go-sdk v0.5.3/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
|
github.com/infisical/go-sdk v0.5.4/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=
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -128,20 +128,36 @@ export const ProjectLayout = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ isActive }) => (
|
{({ isActive }) => (
|
||||||
<MenuItem isSelected={isActive} icon="lock-closed">
|
<MenuItem isSelected={isActive} icon="server">
|
||||||
Overview
|
Hosts
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to={`/${ProjectType.SSH}/$projectId/hosts` as const}
|
to={`/${ProjectType.SSH}/$projectId/certificates` as const}
|
||||||
params={{
|
params={{
|
||||||
projectId: currentWorkspace.id
|
projectId: currentWorkspace.id
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ isActive }) => (
|
{({ isActive }) => (
|
||||||
<MenuItem isSelected={isActive} icon="server" iconMode="reverse">
|
<MenuItem isSelected={isActive} icon="certificate" iconMode="reverse">
|
||||||
Hosts
|
Certificates
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to={`/${ProjectType.SSH}/$projectId/cas` as const}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem
|
||||||
|
isSelected={isActive}
|
||||||
|
icon="certificate-authority"
|
||||||
|
iconMode="reverse"
|
||||||
|
>
|
||||||
|
Certificate Authorities
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
+1
-1
@@ -65,7 +65,7 @@ const formatDescription = (type: ProjectType) => {
|
|||||||
return "Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.";
|
return "Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.";
|
||||||
if (type === ProjectType.KMS)
|
if (type === ProjectType.KMS)
|
||||||
return "Centralize the management of keys for cryptographic operations, such as encryption and decryption.";
|
return "Centralize the management of keys for cryptographic operations, such as encryption and decryption.";
|
||||||
return "Generate SSH credentials to provide secure and centralized SSH access control for your infrastructure.";
|
return "Infisical SSH lets you issue SSH credentials to users for short-lived, secure SSH access to infrastructure.";
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
import { Helmet } from "react-helmet";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
|
||||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
|
||||||
|
|
||||||
import { SshCaSection, SshCertificatesSection } from "./components";
|
|
||||||
|
|
||||||
enum TabSections {
|
|
||||||
SshCa = "ssh-certificate-authorities",
|
|
||||||
SshCertificates = "ssh-certificates"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OverviewPage = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Helmet>
|
|
||||||
<title>{t("common.head-title", { title: "Certificates" })}</title>
|
|
||||||
</Helmet>
|
|
||||||
<div className="h-full bg-bunker-800">
|
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
|
||||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
|
||||||
<PageHeader
|
|
||||||
title="Overview"
|
|
||||||
description="Infisical SSH lets you issue SSH credentials to clients to provide short-lived, secure SSH access to infrastructure."
|
|
||||||
/>
|
|
||||||
<Tabs defaultValue={TabSections.SshCertificates}>
|
|
||||||
<TabList>
|
|
||||||
<Tab value={TabSections.SshCertificates}>SSH Certificates</Tab>
|
|
||||||
<Tab value={TabSections.SshCa}>Certificate Authorities</Tab>
|
|
||||||
</TabList>
|
|
||||||
<TabPanel value={TabSections.SshCertificates}>
|
|
||||||
<motion.div
|
|
||||||
key="panel-ssh-certificate-s"
|
|
||||||
transition={{ duration: 0.15 }}
|
|
||||||
initial={{ opacity: 0, translateX: 30 }}
|
|
||||||
animate={{ opacity: 1, translateX: 0 }}
|
|
||||||
exit={{ opacity: 0, translateX: 30 }}
|
|
||||||
>
|
|
||||||
<ProjectPermissionCan
|
|
||||||
I={ProjectPermissionActions.Read}
|
|
||||||
a={ProjectPermissionSub.SshCertificates}
|
|
||||||
renderGuardBanner
|
|
||||||
passThrough={false}
|
|
||||||
>
|
|
||||||
<SshCertificatesSection />
|
|
||||||
</ProjectPermissionCan>
|
|
||||||
</motion.div>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel value={TabSections.SshCa}>
|
|
||||||
<motion.div
|
|
||||||
key="panel-ssh-certificate-authorities"
|
|
||||||
transition={{ duration: 0.15 }}
|
|
||||||
initial={{ opacity: 0, translateX: 30 }}
|
|
||||||
animate={{ opacity: 1, translateX: 0 }}
|
|
||||||
exit={{ opacity: 0, translateX: 30 }}
|
|
||||||
>
|
|
||||||
<ProjectPermissionCan
|
|
||||||
I={ProjectPermissionActions.Read}
|
|
||||||
a={ProjectPermissionSub.SshCertificateAuthorities}
|
|
||||||
renderGuardBanner
|
|
||||||
passThrough={false}
|
|
||||||
>
|
|
||||||
<SshCaSection />
|
|
||||||
</ProjectPermissionCan>
|
|
||||||
</motion.div>
|
|
||||||
</TabPanel>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -20,7 +20,7 @@ import { useDeleteSshCa, useGetSshCaById } from "@app/hooks/api";
|
|||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { SshCaModal } from "../OverviewPage/components/SshCaModal";
|
import { SshCaModal } from "../SshCasPage/components/SshCaModal";
|
||||||
import { SshCaDetailsSection, SshCertificateTemplatesSection } from "./components";
|
import { SshCaDetailsSection, SshCertificateTemplatesSection } from "./components";
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { SshCaSection } from "./components";
|
||||||
|
|
||||||
|
export const SshCasPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: "Certificates" })}</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className="h-full bg-bunker-800">
|
||||||
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
|
<PageHeader
|
||||||
|
title="SSH Certificate Authorities"
|
||||||
|
description="Manage the SSH certificate authorities used to sign user and host certificates, including custom and default CAs."
|
||||||
|
/>
|
||||||
|
<SshCaSection />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { SshCaSection } from "./SshCaSection";
|
||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { OverviewPage } from "./OverviewPage";
|
import { SshCasPage } from "./SshCasPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview"
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas"
|
||||||
)({
|
)({
|
||||||
component: OverviewPage
|
component: SshCasPage
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { SshCertificatesSection } from "./components";
|
||||||
|
|
||||||
|
export const SshCertsPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: "Certificates" })}</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className="h-full bg-bunker-800">
|
||||||
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
|
<PageHeader
|
||||||
|
title="SSH Certificates"
|
||||||
|
description="View and audit all issued SSH certificates, including validity and associated access metadata."
|
||||||
|
/>
|
||||||
|
<SshCertificatesSection />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+3
-9
@@ -1,21 +1,15 @@
|
|||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
|
||||||
import { Button } from "@app/components/v2";
|
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { SshCertificateModal } from "../../SshCaByIDPage/components/SshCertificateModal";
|
import { SshCertificateModal } from "../../SshCaByIDPage/components/SshCertificateModal";
|
||||||
import { SshCertificatesTable } from "./SshCertificatesTable";
|
import { SshCertificatesTable } from "./SshCertificatesTable";
|
||||||
|
|
||||||
export const SshCertificatesSection = () => {
|
export const SshCertificatesSection = () => {
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["sshCertificate"] as const);
|
const { popUp, handlePopUpToggle } = usePopUp(["sshCertificate"] as const);
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="mb-4 flex justify-between">
|
<div className="mb-4 flex justify-between">
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">Certificates</p>
|
<p className="text-xl font-semibold text-mineshaft-100">Certificates</p>
|
||||||
<ProjectPermissionCan
|
{/* <ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Create}
|
I={ProjectPermissionActions.Create}
|
||||||
a={ProjectPermissionSub.SshCertificates}
|
a={ProjectPermissionSub.SshCertificates}
|
||||||
>
|
>
|
||||||
@@ -30,7 +24,7 @@ export const SshCertificatesSection = () => {
|
|||||||
Request
|
Request
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan> */}
|
||||||
</div>
|
</div>
|
||||||
<SshCertificatesTable />
|
<SshCertificatesTable />
|
||||||
<SshCertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
<SshCertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||||
-1
@@ -1,2 +1 @@
|
|||||||
export { SshCaSection } from "./SshCaSection";
|
|
||||||
export { SshCertificatesSection } from "./SshCertificatesSection";
|
export { SshCertificatesSection } from "./SshCertificatesSection";
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { SshCertsPage } from "./SshCertsPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/certificates"
|
||||||
|
)({
|
||||||
|
component: SshCertsPage
|
||||||
|
});
|
||||||
@@ -17,7 +17,7 @@ export const SshHostsPage = () => {
|
|||||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Hosts"
|
title="Hosts"
|
||||||
description="Infisical SSH lets you issue SSH credentials to clients to provide short-lived, secure SSH access to infrastructure."
|
description="Manage your SSH hosts, configure access policies, and define login behavior for secure connections."
|
||||||
/>
|
/>
|
||||||
<SshHostsSection />
|
<SshHostsSection />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -217,11 +217,7 @@ export const SshHostModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
{i === 0 && (
|
{i === 0 && (
|
||||||
<FormLabel
|
<FormLabel label="Allowed Principals" className="text-xs text-mineshaft-400" />
|
||||||
label="Allowed Principals"
|
|
||||||
className="text-xs text-mineshaft-400"
|
|
||||||
isOptional
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ export const SshHostsSection = () => {
|
|||||||
|
|
||||||
const onRemoveSshHostSubmit = async (sshHostId: string) => {
|
const onRemoveSshHostSubmit = async (sshHostId: string) => {
|
||||||
try {
|
try {
|
||||||
console.log("pre onRemoveSshHostSubmit");
|
|
||||||
const host = await deleteSshHost({ sshHostId });
|
const host = await deleteSshHost({ sshHostId });
|
||||||
console.log("post onRemoveSshHostSubmit deleted host: ", host);
|
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Successfully deleted SSH host: ${host.hostname}`,
|
text: `Successfully deleted SSH host: ${host.hostname}`,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
|||||||
import { SshHostsPage } from "./SshHostsPage";
|
import { SshHostsPage } from "./SshHostsPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts"
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview"
|
||||||
)({
|
)({
|
||||||
component: SshHostsPage
|
component: SshHostsPage
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -78,8 +78,9 @@ import { Route as secretManagerIntegrationsRouteAzureKeyVaultOauthRedirectImport
|
|||||||
import { Route as secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectImport } from './pages/secret-manager/integrations/route-azure-app-configurations-oauth-redirect'
|
import { Route as secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectImport } from './pages/secret-manager/integrations/route-azure-app-configurations-oauth-redirect'
|
||||||
import { Route as projectAccessControlPageRouteCertManagerImport } from './pages/project/AccessControlPage/route-cert-manager'
|
import { Route as projectAccessControlPageRouteCertManagerImport } from './pages/project/AccessControlPage/route-cert-manager'
|
||||||
import { Route as sshSettingsPageRouteImport } from './pages/ssh/SettingsPage/route'
|
import { Route as sshSettingsPageRouteImport } from './pages/ssh/SettingsPage/route'
|
||||||
import { Route as sshOverviewPageRouteImport } from './pages/ssh/OverviewPage/route'
|
|
||||||
import { Route as sshSshHostsPageRouteImport } from './pages/ssh/SshHostsPage/route'
|
import { Route as sshSshHostsPageRouteImport } from './pages/ssh/SshHostsPage/route'
|
||||||
|
import { Route as sshSshCertsPageRouteImport } from './pages/ssh/SshCertsPage/route'
|
||||||
|
import { Route as sshSshCasPageRouteImport } from './pages/ssh/SshCasPage/route'
|
||||||
import { Route as secretManagerSettingsPageRouteImport } from './pages/secret-manager/SettingsPage/route'
|
import { Route as secretManagerSettingsPageRouteImport } from './pages/secret-manager/SettingsPage/route'
|
||||||
import { Route as secretManagerSecretRotationPageRouteImport } from './pages/secret-manager/SecretRotationPage/route'
|
import { Route as secretManagerSecretRotationPageRouteImport } from './pages/secret-manager/SecretRotationPage/route'
|
||||||
import { Route as secretManagerOverviewPageRouteImport } from './pages/secret-manager/OverviewPage/route'
|
import { Route as secretManagerOverviewPageRouteImport } from './pages/secret-manager/OverviewPage/route'
|
||||||
@@ -801,15 +802,21 @@ const sshSettingsPageRouteRoute = sshSettingsPageRouteImport.update({
|
|||||||
getParentRoute: () => sshLayoutRoute,
|
getParentRoute: () => sshLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const sshOverviewPageRouteRoute = sshOverviewPageRouteImport.update({
|
const sshSshHostsPageRouteRoute = sshSshHostsPageRouteImport.update({
|
||||||
id: '/overview',
|
id: '/overview',
|
||||||
path: '/overview',
|
path: '/overview',
|
||||||
getParentRoute: () => sshLayoutRoute,
|
getParentRoute: () => sshLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const sshSshHostsPageRouteRoute = sshSshHostsPageRouteImport.update({
|
const sshSshCertsPageRouteRoute = sshSshCertsPageRouteImport.update({
|
||||||
id: '/hosts',
|
id: '/certificates',
|
||||||
path: '/hosts',
|
path: '/certificates',
|
||||||
|
getParentRoute: () => sshLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const sshSshCasPageRouteRoute = sshSshCasPageRouteImport.update({
|
||||||
|
id: '/cas',
|
||||||
|
path: '/cas',
|
||||||
getParentRoute: () => sshLayoutRoute,
|
getParentRoute: () => sshLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
@@ -2153,18 +2160,25 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof secretManagerSettingsPageRouteImport
|
preLoaderRoute: typeof secretManagerSettingsPageRouteImport
|
||||||
parentRoute: typeof secretManagerLayoutImport
|
parentRoute: typeof secretManagerLayoutImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts': {
|
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas': {
|
||||||
id: '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts'
|
id: '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas'
|
||||||
path: '/hosts'
|
path: '/cas'
|
||||||
fullPath: '/ssh/$projectId/hosts'
|
fullPath: '/ssh/$projectId/cas'
|
||||||
preLoaderRoute: typeof sshSshHostsPageRouteImport
|
preLoaderRoute: typeof sshSshCasPageRouteImport
|
||||||
|
parentRoute: typeof sshLayoutImport
|
||||||
|
}
|
||||||
|
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/certificates': {
|
||||||
|
id: '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/certificates'
|
||||||
|
path: '/certificates'
|
||||||
|
fullPath: '/ssh/$projectId/certificates'
|
||||||
|
preLoaderRoute: typeof sshSshCertsPageRouteImport
|
||||||
parentRoute: typeof sshLayoutImport
|
parentRoute: typeof sshLayoutImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview': {
|
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview': {
|
||||||
id: '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview'
|
id: '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview'
|
||||||
path: '/overview'
|
path: '/overview'
|
||||||
fullPath: '/ssh/$projectId/overview'
|
fullPath: '/ssh/$projectId/overview'
|
||||||
preLoaderRoute: typeof sshOverviewPageRouteImport
|
preLoaderRoute: typeof sshSshHostsPageRouteImport
|
||||||
parentRoute: typeof sshLayoutImport
|
parentRoute: typeof sshLayoutImport
|
||||||
}
|
}
|
||||||
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings': {
|
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings': {
|
||||||
@@ -3477,8 +3491,9 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildr
|
|||||||
)
|
)
|
||||||
|
|
||||||
interface sshLayoutRouteChildren {
|
interface sshLayoutRouteChildren {
|
||||||
|
sshSshCasPageRouteRoute: typeof sshSshCasPageRouteRoute
|
||||||
|
sshSshCertsPageRouteRoute: typeof sshSshCertsPageRouteRoute
|
||||||
sshSshHostsPageRouteRoute: typeof sshSshHostsPageRouteRoute
|
sshSshHostsPageRouteRoute: typeof sshSshHostsPageRouteRoute
|
||||||
sshOverviewPageRouteRoute: typeof sshOverviewPageRouteRoute
|
|
||||||
sshSettingsPageRouteRoute: typeof sshSettingsPageRouteRoute
|
sshSettingsPageRouteRoute: typeof sshSettingsPageRouteRoute
|
||||||
projectAccessControlPageRouteSshRoute: typeof projectAccessControlPageRouteSshRoute
|
projectAccessControlPageRouteSshRoute: typeof projectAccessControlPageRouteSshRoute
|
||||||
sshSshCaByIDPageRouteRoute: typeof sshSshCaByIDPageRouteRoute
|
sshSshCaByIDPageRouteRoute: typeof sshSshCaByIDPageRouteRoute
|
||||||
@@ -3488,8 +3503,9 @@ interface sshLayoutRouteChildren {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sshLayoutRouteChildren: sshLayoutRouteChildren = {
|
const sshLayoutRouteChildren: sshLayoutRouteChildren = {
|
||||||
|
sshSshCasPageRouteRoute: sshSshCasPageRouteRoute,
|
||||||
|
sshSshCertsPageRouteRoute: sshSshCertsPageRouteRoute,
|
||||||
sshSshHostsPageRouteRoute: sshSshHostsPageRouteRoute,
|
sshSshHostsPageRouteRoute: sshSshHostsPageRouteRoute,
|
||||||
sshOverviewPageRouteRoute: sshOverviewPageRouteRoute,
|
|
||||||
sshSettingsPageRouteRoute: sshSettingsPageRouteRoute,
|
sshSettingsPageRouteRoute: sshSettingsPageRouteRoute,
|
||||||
projectAccessControlPageRouteSshRoute: projectAccessControlPageRouteSshRoute,
|
projectAccessControlPageRouteSshRoute: projectAccessControlPageRouteSshRoute,
|
||||||
sshSshCaByIDPageRouteRoute: sshSshCaByIDPageRouteRoute,
|
sshSshCaByIDPageRouteRoute: sshSshCaByIDPageRouteRoute,
|
||||||
@@ -3771,8 +3787,9 @@ export interface FileRoutesByFullPath {
|
|||||||
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
|
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
|
||||||
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
||||||
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
|
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
|
||||||
'/ssh/$projectId/hosts': typeof sshSshHostsPageRouteRoute
|
'/ssh/$projectId/cas': typeof sshSshCasPageRouteRoute
|
||||||
'/ssh/$projectId/overview': typeof sshOverviewPageRouteRoute
|
'/ssh/$projectId/certificates': typeof sshSshCertsPageRouteRoute
|
||||||
|
'/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute
|
||||||
'/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
|
'/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
|
||||||
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
||||||
'/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute
|
'/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute
|
||||||
@@ -3944,8 +3961,9 @@ export interface FileRoutesByTo {
|
|||||||
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
|
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
|
||||||
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
||||||
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
|
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
|
||||||
'/ssh/$projectId/hosts': typeof sshSshHostsPageRouteRoute
|
'/ssh/$projectId/cas': typeof sshSshCasPageRouteRoute
|
||||||
'/ssh/$projectId/overview': typeof sshOverviewPageRouteRoute
|
'/ssh/$projectId/certificates': typeof sshSshCertsPageRouteRoute
|
||||||
|
'/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute
|
||||||
'/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
|
'/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
|
||||||
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
||||||
'/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute
|
'/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute
|
||||||
@@ -4133,8 +4151,9 @@ export interface FileRoutesById {
|
|||||||
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/overview': typeof secretManagerOverviewPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/overview': typeof secretManagerOverviewPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/settings': typeof secretManagerSettingsPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/settings': typeof secretManagerSettingsPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts': typeof sshSshHostsPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas': typeof sshSshCasPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview': typeof sshOverviewPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/certificates': typeof sshSshCertsPageRouteRoute
|
||||||
|
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview': typeof sshSshHostsPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings': typeof sshSettingsPageRouteRoute
|
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings': typeof sshSettingsPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute
|
'/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute
|
||||||
@@ -4315,7 +4334,8 @@ export interface FileRouteTypes {
|
|||||||
| '/secret-manager/$projectId/overview'
|
| '/secret-manager/$projectId/overview'
|
||||||
| '/secret-manager/$projectId/secret-rotation'
|
| '/secret-manager/$projectId/secret-rotation'
|
||||||
| '/secret-manager/$projectId/settings'
|
| '/secret-manager/$projectId/settings'
|
||||||
| '/ssh/$projectId/hosts'
|
| '/ssh/$projectId/cas'
|
||||||
|
| '/ssh/$projectId/certificates'
|
||||||
| '/ssh/$projectId/overview'
|
| '/ssh/$projectId/overview'
|
||||||
| '/ssh/$projectId/settings'
|
| '/ssh/$projectId/settings'
|
||||||
| '/cert-manager/$projectId/access-management'
|
| '/cert-manager/$projectId/access-management'
|
||||||
@@ -4487,7 +4507,8 @@ export interface FileRouteTypes {
|
|||||||
| '/secret-manager/$projectId/overview'
|
| '/secret-manager/$projectId/overview'
|
||||||
| '/secret-manager/$projectId/secret-rotation'
|
| '/secret-manager/$projectId/secret-rotation'
|
||||||
| '/secret-manager/$projectId/settings'
|
| '/secret-manager/$projectId/settings'
|
||||||
| '/ssh/$projectId/hosts'
|
| '/ssh/$projectId/cas'
|
||||||
|
| '/ssh/$projectId/certificates'
|
||||||
| '/ssh/$projectId/overview'
|
| '/ssh/$projectId/overview'
|
||||||
| '/ssh/$projectId/settings'
|
| '/ssh/$projectId/settings'
|
||||||
| '/cert-manager/$projectId/access-management'
|
| '/cert-manager/$projectId/access-management'
|
||||||
@@ -4674,7 +4695,8 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/overview'
|
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/overview'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secret-rotation'
|
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/secret-rotation'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/settings'
|
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/settings'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts'
|
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas'
|
||||||
|
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/certificates'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview'
|
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings'
|
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management'
|
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management'
|
||||||
@@ -5217,7 +5239,8 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "ssh/layout.tsx",
|
"filePath": "ssh/layout.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId",
|
"parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId",
|
||||||
"children": [
|
"children": [
|
||||||
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts",
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas",
|
||||||
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/certificates",
|
||||||
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview",
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview",
|
||||||
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings",
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings",
|
||||||
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/access-management",
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/access-management",
|
||||||
@@ -5267,12 +5290,16 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "secret-manager/SettingsPage/route.tsx",
|
"filePath": "secret-manager/SettingsPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout"
|
"parent": "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout"
|
||||||
},
|
},
|
||||||
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts": {
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas": {
|
||||||
"filePath": "ssh/SshHostsPage/route.tsx",
|
"filePath": "ssh/SshCasPage/route.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout"
|
||||||
|
},
|
||||||
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/certificates": {
|
||||||
|
"filePath": "ssh/SshCertsPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout"
|
"parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout"
|
||||||
},
|
},
|
||||||
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview": {
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview": {
|
||||||
"filePath": "ssh/OverviewPage/route.tsx",
|
"filePath": "ssh/SshHostsPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout"
|
"parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout"
|
||||||
},
|
},
|
||||||
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings": {
|
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings": {
|
||||||
|
|||||||
@@ -306,8 +306,9 @@ const kmsRoutes = route("/kms/$projectId", [
|
|||||||
|
|
||||||
const sshRoutes = route("/ssh/$projectId", [
|
const sshRoutes = route("/ssh/$projectId", [
|
||||||
layout("ssh-layout", "ssh/layout.tsx", [
|
layout("ssh-layout", "ssh/layout.tsx", [
|
||||||
route("/overview", "ssh/OverviewPage/route.tsx"),
|
route("/overview", "ssh/SshHostsPage/route.tsx"),
|
||||||
route("/hosts", "ssh/SshHostsPage/route.tsx"),
|
route("/certificates", "ssh/SshCertsPage/route.tsx"),
|
||||||
|
route("/cas", "ssh/SshCasPage/route.tsx"),
|
||||||
route("/ca/$caId", "ssh/SshCaByIDPage/route.tsx"),
|
route("/ca/$caId", "ssh/SshCaByIDPage/route.tsx"),
|
||||||
route("/settings", "ssh/SettingsPage/route.tsx"),
|
route("/settings", "ssh/SettingsPage/route.tsx"),
|
||||||
route("/access-management", "project/AccessControlPage/route-ssh.tsx"),
|
route("/access-management", "project/AccessControlPage/route-ssh.tsx"),
|
||||||
|
|||||||
Reference in New Issue
Block a user