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
@@ -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"),