Add ssh host endpoint for issuing ssh host cert

This commit is contained in:
Tuan Dang
2025-04-07 20:47:52 -07:00
parent eb4816fd29
commit 99aa567a6f
39 changed files with 686 additions and 258 deletions
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 }) => (
<MenuItem isSelected={isActive} icon="lock-closed">
Overview
<MenuItem isSelected={isActive} icon="server">
Hosts
</MenuItem>
)}
</Link>
<Link
to={`/${ProjectType.SSH}/$projectId/hosts` as const}
to={`/${ProjectType.SSH}/$projectId/certificates` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="server" iconMode="reverse">
Hosts
<MenuItem isSelected={isActive} icon="certificate" iconMode="reverse">
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>
)}
</Link>
@@ -65,7 +65,7 @@ const formatDescription = (type: ProjectType) => {
return "Manage your PKI infrastructure and issue digital certificates for services, applications, and devices.";
if (type === ProjectType.KMS)
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 = {
@@ -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 { usePopUp } from "@app/hooks/usePopUp";
import { SshCaModal } from "../OverviewPage/components/SshCaModal";
import { SshCaModal } from "../SshCasPage/components/SshCaModal";
import { SshCaDetailsSection, SshCertificateTemplatesSection } from "./components";
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";
@@ -1,9 +1,9 @@
import { createFileRoute } from "@tanstack/react-router";
import { OverviewPage } from "./OverviewPage";
import { SshCasPage } from "./SshCasPage";
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>
</>
);
};
@@ -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 { SshCertificateModal } from "../../SshCaByIDPage/components/SshCertificateModal";
import { SshCertificatesTable } from "./SshCertificatesTable";
export const SshCertificatesSection = () => {
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["sshCertificate"] as const);
const { popUp, handlePopUpToggle } = usePopUp(["sshCertificate"] as const);
return (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Certificates</p>
<ProjectPermissionCan
{/* <ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.SshCertificates}
>
@@ -30,7 +24,7 @@ export const SshCertificatesSection = () => {
Request
</Button>
)}
</ProjectPermissionCan>
</ProjectPermissionCan> */}
</div>
<SshCertificatesTable />
<SshCertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
@@ -1,2 +1 @@
export { SshCaSection } from "./SshCaSection";
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">
<PageHeader
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 />
</div>
@@ -217,11 +217,7 @@ export const SshHostModal = ({ popUp, handlePopUpToggle }: Props) => {
</div>
<div className="flex-grow">
{i === 0 && (
<FormLabel
label="Allowed Principals"
className="text-xs text-mineshaft-400"
isOptional
/>
<FormLabel label="Allowed Principals" className="text-xs text-mineshaft-400" />
)}
<Controller
control={control}
@@ -21,9 +21,7 @@ export const SshHostsSection = () => {
const onRemoveSshHostSubmit = async (sshHostId: string) => {
try {
console.log("pre onRemoveSshHostSubmit");
const host = await deleteSshHost({ sshHostId });
console.log("post onRemoveSshHostSubmit deleted host: ", host);
createNotification({
text: `Successfully deleted SSH host: ${host.hostname}`,
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { SshHostsPage } from "./SshHostsPage";
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
});
+53 -26
View File
@@ -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 projectAccessControlPageRouteCertManagerImport } from './pages/project/AccessControlPage/route-cert-manager'
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 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 secretManagerSecretRotationPageRouteImport } from './pages/secret-manager/SecretRotationPage/route'
import { Route as secretManagerOverviewPageRouteImport } from './pages/secret-manager/OverviewPage/route'
@@ -801,15 +802,21 @@ const sshSettingsPageRouteRoute = sshSettingsPageRouteImport.update({
getParentRoute: () => sshLayoutRoute,
} as any)
const sshOverviewPageRouteRoute = sshOverviewPageRouteImport.update({
const sshSshHostsPageRouteRoute = sshSshHostsPageRouteImport.update({
id: '/overview',
path: '/overview',
getParentRoute: () => sshLayoutRoute,
} as any)
const sshSshHostsPageRouteRoute = sshSshHostsPageRouteImport.update({
id: '/hosts',
path: '/hosts',
const sshSshCertsPageRouteRoute = sshSshCertsPageRouteImport.update({
id: '/certificates',
path: '/certificates',
getParentRoute: () => sshLayoutRoute,
} as any)
const sshSshCasPageRouteRoute = sshSshCasPageRouteImport.update({
id: '/cas',
path: '/cas',
getParentRoute: () => sshLayoutRoute,
} as any)
@@ -2153,18 +2160,25 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof secretManagerSettingsPageRouteImport
parentRoute: typeof secretManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts': {
id: '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts'
path: '/hosts'
fullPath: '/ssh/$projectId/hosts'
preLoaderRoute: typeof sshSshHostsPageRouteImport
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas': {
id: '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas'
path: '/cas'
fullPath: '/ssh/$projectId/cas'
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
}
'/_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'
fullPath: '/ssh/$projectId/overview'
preLoaderRoute: typeof sshOverviewPageRouteImport
preLoaderRoute: typeof sshSshHostsPageRouteImport
parentRoute: typeof sshLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings': {
@@ -3477,8 +3491,9 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildr
)
interface sshLayoutRouteChildren {
sshSshCasPageRouteRoute: typeof sshSshCasPageRouteRoute
sshSshCertsPageRouteRoute: typeof sshSshCertsPageRouteRoute
sshSshHostsPageRouteRoute: typeof sshSshHostsPageRouteRoute
sshOverviewPageRouteRoute: typeof sshOverviewPageRouteRoute
sshSettingsPageRouteRoute: typeof sshSettingsPageRouteRoute
projectAccessControlPageRouteSshRoute: typeof projectAccessControlPageRouteSshRoute
sshSshCaByIDPageRouteRoute: typeof sshSshCaByIDPageRouteRoute
@@ -3488,8 +3503,9 @@ interface sshLayoutRouteChildren {
}
const sshLayoutRouteChildren: sshLayoutRouteChildren = {
sshSshCasPageRouteRoute: sshSshCasPageRouteRoute,
sshSshCertsPageRouteRoute: sshSshCertsPageRouteRoute,
sshSshHostsPageRouteRoute: sshSshHostsPageRouteRoute,
sshOverviewPageRouteRoute: sshOverviewPageRouteRoute,
sshSettingsPageRouteRoute: sshSettingsPageRouteRoute,
projectAccessControlPageRouteSshRoute: projectAccessControlPageRouteSshRoute,
sshSshCaByIDPageRouteRoute: sshSshCaByIDPageRouteRoute,
@@ -3771,8 +3787,9 @@ export interface FileRoutesByFullPath {
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
'/ssh/$projectId/hosts': typeof sshSshHostsPageRouteRoute
'/ssh/$projectId/overview': typeof sshOverviewPageRouteRoute
'/ssh/$projectId/cas': typeof sshSshCasPageRouteRoute
'/ssh/$projectId/certificates': typeof sshSshCertsPageRouteRoute
'/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute
'/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
'/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute
@@ -3944,8 +3961,9 @@ export interface FileRoutesByTo {
'/secret-manager/$projectId/overview': typeof secretManagerOverviewPageRouteRoute
'/secret-manager/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute
'/secret-manager/$projectId/settings': typeof secretManagerSettingsPageRouteRoute
'/ssh/$projectId/hosts': typeof sshSshHostsPageRouteRoute
'/ssh/$projectId/overview': typeof sshOverviewPageRouteRoute
'/ssh/$projectId/cas': typeof sshSshCasPageRouteRoute
'/ssh/$projectId/certificates': typeof sshSshCertsPageRouteRoute
'/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute
'/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
'/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/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/ssh/$projectId/_ssh-layout/hosts': typeof sshSshHostsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview': typeof sshOverviewPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas': typeof sshSshCasPageRouteRoute
'/_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/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute
'/_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/secret-rotation'
| '/secret-manager/$projectId/settings'
| '/ssh/$projectId/hosts'
| '/ssh/$projectId/cas'
| '/ssh/$projectId/certificates'
| '/ssh/$projectId/overview'
| '/ssh/$projectId/settings'
| '/cert-manager/$projectId/access-management'
@@ -4487,7 +4507,8 @@ export interface FileRouteTypes {
| '/secret-manager/$projectId/overview'
| '/secret-manager/$projectId/secret-rotation'
| '/secret-manager/$projectId/settings'
| '/ssh/$projectId/hosts'
| '/ssh/$projectId/cas'
| '/ssh/$projectId/certificates'
| '/ssh/$projectId/overview'
| '/ssh/$projectId/settings'
| '/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/secret-rotation'
| '/_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/settings'
| '/_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",
"parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId",
"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/settings",
"/_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",
"parent": "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/hosts": {
"filePath": "ssh/SshHostsPage/route.tsx",
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/cas": {
"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"
},
"/_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"
},
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings": {
+3 -2
View File
@@ -306,8 +306,9 @@ const kmsRoutes = route("/kms/$projectId", [
const sshRoutes = route("/ssh/$projectId", [
layout("ssh-layout", "ssh/layout.tsx", [
route("/overview", "ssh/OverviewPage/route.tsx"),
route("/hosts", "ssh/SshHostsPage/route.tsx"),
route("/overview", "ssh/SshHostsPage/route.tsx"),
route("/certificates", "ssh/SshCertsPage/route.tsx"),
route("/cas", "ssh/SshCasPage/route.tsx"),
route("/ca/$caId", "ssh/SshCaByIDPage/route.tsx"),
route("/settings", "ssh/SettingsPage/route.tsx"),
route("/access-management", "project/AccessControlPage/route-ssh.tsx"),