Update ssh issue/sign fns to be based on certificate template id

This commit is contained in:
Tuan Dang
2024-12-17 10:37:05 -08:00
parent 1fa99e5585
commit 86800c0cdb
7 changed files with 28 additions and 53 deletions
+8 -4
View File
@@ -20,8 +20,10 @@ export const registerSshRouter = async (server: FastifyZodProvider) => {
schema: { schema: {
description: "Sign SSH public key", description: "Sign SSH public key",
body: z.object({ body: z.object({
projectId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.projectId), certificateTemplateId: z
templateName: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.templateName), .string()
.trim()
.describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.certificateTemplateId),
publicKey: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.publicKey), publicKey: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.SIGN_SSH_KEY.publicKey),
certType: z certType: z
.nativeEnum(SshCertType) .nativeEnum(SshCertType)
@@ -87,8 +89,10 @@ export const registerSshRouter = async (server: FastifyZodProvider) => {
schema: { schema: {
description: "Issue SSH credentials (certificate + key)", description: "Issue SSH credentials (certificate + key)",
body: z.object({ body: z.object({
projectId: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.projectId), certificateTemplateId: z
templateName: z.string().trim().describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.templateName), .string()
.trim()
.describe(SSH_CERTIFICATE_AUTHORITIES.ISSUE_SSH_CREDENTIALS.certificateTemplateId),
keyAlgorithm: z keyAlgorithm: z
.nativeEnum(CertKeyAlgorithm) .nativeEnum(CertKeyAlgorithm)
.default(CertKeyAlgorithm.RSA_2048) .default(CertKeyAlgorithm.RSA_2048)
@@ -28,9 +28,6 @@ export const createSshCertSerialNumber = () => {
/** /**
* Return a pair of SSH CA keys based on the specified key algorithm [keyAlgorithm]. * Return a pair of SSH CA keys based on the specified key algorithm [keyAlgorithm].
* We use this function because the key format generated by `ssh-keygen` is unique. * We use this function because the key format generated by `ssh-keygen` is unique.
* @param keyAlgorithm - The key algorithm to use for generating the SSH key pair
* @param comment - The comment to use for the SSH key pair
* @returns The public and private keys for the SSH key pair
*/ */
export const createSshKeyPair = async (keyAlgorithm: CertKeyAlgorithm) => { export const createSshKeyPair = async (keyAlgorithm: CertKeyAlgorithm) => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ssh-key-")); const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ssh-key-"));
@@ -82,7 +79,6 @@ export const createSshKeyPair = async (keyAlgorithm: CertKeyAlgorithm) => {
/** /**
* Return the SSH public key for the given SSH private key. * Return the SSH public key for the given SSH private key.
* @param privateKey - The SSH private key to get the public key for
*/ */
export const getSshPublicKey = async (privateKey: string) => { export const getSshPublicKey = async (privateKey: string) => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ssh-key-")); const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ssh-key-"));
@@ -101,8 +97,6 @@ export const getSshPublicKey = async (privateKey: string) => {
/** /**
* Validate the requested SSH certificate type based on the SSH certificate template configuration. * Validate the requested SSH certificate type based on the SSH certificate template configuration.
* @param template - The SSH certificate template configuration
* @param certType - The SSH certificate type
*/ */
export const validateSshCertificateType = (template: TSshCertificateTemplates, certType: SshCertType) => { export const validateSshCertificateType = (template: TSshCertificateTemplates, certType: SshCertType) => {
if (!template.allowUserCertificates && certType === SshCertType.USER) { if (!template.allowUserCertificates && certType === SshCertType.USER) {
@@ -116,10 +110,6 @@ export const validateSshCertificateType = (template: TSshCertificateTemplates, c
/** /**
* Validate the requested SSH certificate principals based on the SSH certificate template configuration. * Validate the requested SSH certificate principals based on the SSH certificate template configuration.
* @param certType - The SSH certificate type
* @param template - The SSH certificate template configuration
* @param principals - The requested SSH certificate principals
* @returns The validated SSH certificate principals
*/ */
export const validateSshCertificatePrincipals = ( export const validateSshCertificatePrincipals = (
certType: SshCertType, certType: SshCertType,
@@ -128,8 +118,6 @@ export const validateSshCertificatePrincipals = (
) => { ) => {
/** /**
* Validate and sanitize a principal string * Validate and sanitize a principal string
* @param principal - the principal to validate and sanitize
* @returns the sanitized principal
*/ */
const validatePrincipal = (principal: string) => { const validatePrincipal = (principal: string) => {
const sanitized = principal.trim(); const sanitized = principal.trim();
@@ -257,9 +245,6 @@ export const validateSshCertificatePrincipals = (
/** /**
* Validate the requested SSH certificate TTL based on the SSH certificate template configuration. * Validate the requested SSH certificate TTL based on the SSH certificate template configuration.
* @param template - The SSH certificate template configuration
* @param ttl - The TTL to validate
* @returns The TTL (in seconds) to use for issuing the SSH certificate
*/ */
export const validateSshCertificateTtl = (template: TSshCertificateTemplates, ttl?: string) => { export const validateSshCertificateTtl = (template: TSshCertificateTemplates, ttl?: string) => {
if (!ttl) { if (!ttl) {
@@ -279,7 +264,6 @@ export const validateSshCertificateTtl = (template: TSshCertificateTemplates, tt
/** /**
* Validate the requested SSH certificate key ID to ensure * Validate the requested SSH certificate key ID to ensure
* that it only contains alphanumeric characters with no spaces. * that it only contains alphanumeric characters with no spaces.
* @param keyId - The key ID to validate
*/ */
export const validateSshCertificateKeyId = (keyId: string) => { export const validateSshCertificateKeyId = (keyId: string) => {
const regex = /^[A-Za-z0-9-]+$/; const regex = /^[A-Za-z0-9-]+$/;
@@ -299,7 +283,6 @@ export const validateSshCertificateKeyId = (keyId: string) => {
/** /**
* Validate the format of the SSH public key * Validate the format of the SSH public key
* @param publicKey - the public key to validate
*/ */
const validateSshPublicKey = async (publicKey: string) => { const validateSshPublicKey = async (publicKey: string) => {
const validPrefixes = ["ssh-rsa", "ssh-ed25519", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]; const validPrefixes = ["ssh-rsa", "ssh-ed25519", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"];
@@ -1,4 +1,5 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas"; import { ProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@@ -10,6 +11,7 @@ import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certific
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } 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 { SshCertTemplateStatus } from "../ssh-certificate-template/ssh-certificate-template-types"; import { SshCertTemplateStatus } from "../ssh-certificate-template/ssh-certificate-template-types";
import { createSshCert, createSshKeyPair, getSshPublicKey } from "./ssh-certificate-authority-fns"; import { createSshCert, createSshKeyPair, getSshPublicKey } from "./ssh-certificate-authority-fns";
import { import {
@@ -30,7 +32,7 @@ type TSshCertificateAuthorityServiceFactoryDep = {
"transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" "transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne"
>; >;
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "create" | "findOne">; sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "create" | "findOne">;
sshCertificateTemplateDAL: Pick<TSshCertificateTemplateDALFactory, "find" | "getByName">; sshCertificateTemplateDAL: Pick<TSshCertificateTemplateDALFactory, "find" | "getById">;
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">; sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">;
sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">; sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">;
kmsService: Pick< kmsService: Pick<
@@ -42,8 +44,6 @@ type TSshCertificateAuthorityServiceFactoryDep = {
export type TSshCertificateAuthorityServiceFactory = ReturnType<typeof sshCertificateAuthorityServiceFactory>; export type TSshCertificateAuthorityServiceFactory = ReturnType<typeof sshCertificateAuthorityServiceFactory>;
// TODO: secretManagerEncryptor -> sshEncryptor (cc akhil)
export const sshCertificateAuthorityServiceFactory = ({ export const sshCertificateAuthorityServiceFactory = ({
sshCertificateAuthorityDAL, sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL, sshCertificateAuthoritySecretDAL,
@@ -92,7 +92,6 @@ export const sshCertificateAuthorityServiceFactory = ({
const { publicKey, privateKey } = await createSshKeyPair(keyAlgorithm); const { publicKey, privateKey } = await createSshKeyPair(keyAlgorithm);
// TODO: update to sshEncryptor
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId projectId
@@ -135,7 +134,6 @@ export const sshCertificateAuthorityServiceFactory = ({
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id }); const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id });
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId: ca.projectId projectId: ca.projectId
@@ -159,7 +157,6 @@ export const sshCertificateAuthorityServiceFactory = ({
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id }); const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id });
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId: ca.projectId projectId: ca.projectId
@@ -208,7 +205,6 @@ export const sshCertificateAuthorityServiceFactory = ({
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id }); const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: ca.id });
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId: ca.projectId projectId: ca.projectId
@@ -254,8 +250,7 @@ export const sshCertificateAuthorityServiceFactory = ({
* SSH public key is signed using CA behind SSH certificate with name [templateName]. * SSH public key is signed using CA behind SSH certificate with name [templateName].
*/ */
const issueSshCreds = async ({ const issueSshCreds = async ({
projectId, certificateTemplateId,
templateName,
keyAlgorithm, keyAlgorithm,
certType, certType,
principals, principals,
@@ -266,7 +261,7 @@ export const sshCertificateAuthorityServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TIssueSshCredsDTO) => { }: TIssueSshCredsDTO) => {
const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, projectId); const sshCertificateTemplate = await sshCertificateTemplateDAL.getById(certificateTemplateId);
if (!sshCertificateTemplate) { if (!sshCertificateTemplate) {
throw new NotFoundError({ throw new NotFoundError({
message: "No SSH certificate template found with specified name" message: "No SSH certificate template found with specified name"
@@ -306,10 +301,9 @@ export const sshCertificateAuthorityServiceFactory = ({
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId }); const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId projectId: sshCertificateTemplate.projectId
}); });
const decryptedCaPrivateKey = secretManagerDecryptor({ const decryptedCaPrivateKey = secretManagerDecryptor({
@@ -329,7 +323,6 @@ export const sshCertificateAuthorityServiceFactory = ({
certType certType
}); });
// TODO: update to sshEncryptor
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId: sshCertificateTemplate.projectId projectId: sshCertificateTemplate.projectId
@@ -379,8 +372,7 @@ export const sshCertificateAuthorityServiceFactory = ({
* using CA behind SSH certificate template with name [templateName] * using CA behind SSH certificate template with name [templateName]
*/ */
const signSshKey = async ({ const signSshKey = async ({
projectId, certificateTemplateId,
templateName,
publicKey, publicKey,
certType, certType,
principals, principals,
@@ -391,7 +383,7 @@ export const sshCertificateAuthorityServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TSignSshKeyDTO) => { }: TSignSshKeyDTO) => {
const sshCertificateTemplate = await sshCertificateTemplateDAL.getByName(templateName, projectId); const sshCertificateTemplate = await sshCertificateTemplateDAL.getById(certificateTemplateId);
if (!sshCertificateTemplate) { if (!sshCertificateTemplate) {
throw new NotFoundError({ throw new NotFoundError({
message: "No SSH certificate template found with specified name" message: "No SSH certificate template found with specified name"
@@ -431,10 +423,9 @@ export const sshCertificateAuthorityServiceFactory = ({
const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId }); const sshCaSecret = await sshCertificateAuthoritySecretDAL.findOne({ sshCaId: sshCertificateTemplate.sshCaId });
// TODO: update to sshDecryptor
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId projectId: sshCertificateTemplate.projectId
}); });
const decryptedCaPrivateKey = secretManagerDecryptor({ const decryptedCaPrivateKey = secretManagerDecryptor({
@@ -451,7 +442,6 @@ export const sshCertificateAuthorityServiceFactory = ({
certType certType
}); });
// TODO: update to sshEncryptor
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId: sshCertificateTemplate.projectId projectId: sshCertificateTemplate.projectId
@@ -36,22 +36,22 @@ export type TDeleteSshCaDTO = {
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TIssueSshCredsDTO = { export type TIssueSshCredsDTO = {
templateName: string; certificateTemplateId: string;
keyAlgorithm: CertKeyAlgorithm; keyAlgorithm: CertKeyAlgorithm;
certType: SshCertType; certType: SshCertType;
principals: string[]; principals: string[];
ttl?: string; ttl?: string;
keyId?: string; keyId?: string;
} & TProjectPermission; } & Omit<TProjectPermission, "projectId">;
export type TSignSshKeyDTO = { export type TSignSshKeyDTO = {
templateName: string; certificateTemplateId: string;
publicKey: string; publicKey: string;
certType: SshCertType; certType: SshCertType;
principals: string[]; principals: string[];
ttl?: string; ttl?: string;
keyId?: string; keyId?: string;
} & TProjectPermission; } & Omit<TProjectPermission, "projectId">;
export type TGetSshCaCertificateTemplatesDTO = { export type TGetSshCaCertificateTemplatesDTO = {
caId: string; caId: string;
+2 -4
View File
@@ -1221,8 +1221,7 @@ export const SSH_CERTIFICATE_AUTHORITIES = {
sshCaId: "The ID of the SSH CA to get the certificate templates for." sshCaId: "The ID of the SSH CA to get the certificate templates for."
}, },
SIGN_SSH_KEY: { SIGN_SSH_KEY: {
projectId: "The ID of the project to sign the SSH public key for.", certificateTemplateId: "The ID of the SSH certificate template to sign the SSH public key with.",
templateName: "The name of the SSH certificate template to sign the SSH public key with.",
publicKey: "The SSH public key to sign.", publicKey: "The SSH public key to sign.",
certType: "The type of certificate to issue. This can be one of user or host.", certType: "The type of certificate to issue. This can be one of user or host.",
principals: "The list of principals (usernames, hostnames) to include in the certificate.", principals: "The list of principals (usernames, hostnames) to include in the certificate.",
@@ -1232,8 +1231,7 @@ export const SSH_CERTIFICATE_AUTHORITIES = {
signedKey: "The SSH certificate or signed SSH public key." signedKey: "The SSH certificate or signed SSH public key."
}, },
ISSUE_SSH_CREDENTIALS: { ISSUE_SSH_CREDENTIALS: {
projectId: "The ID of the project to issue the SSH credentials for.", certificateTemplateId: "The ID of the SSH certificate template to issue the SSH credentials with.",
templateName: "The name of the SSH certificate template to issue the SSH credentials with.",
keyAlgorithm: "The type of public key algorithm and size, in bits, of the key pair for the SSH CA.", keyAlgorithm: "The type of public key algorithm and size, in bits, of the key pair for the SSH CA.",
certType: "The type of certificate to issue. This can be one of user or host.", certType: "The type of certificate to issue. This can be one of user or host.",
principals: "The list of principals (usernames, hostnames) to include in the certificate.", principals: "The list of principals (usernames, hostnames) to include in the certificate.",
+2 -2
View File
@@ -42,7 +42,7 @@ export type TDeleteSshCaDTO = {
export type TSignSshKeyDTO = { export type TSignSshKeyDTO = {
projectId: string; projectId: string;
templateName: string; certificateTemplateId: string;
publicKey?: string; publicKey?: string;
certType: SshCertType; certType: SshCertType;
principals: string[]; principals: string[];
@@ -57,7 +57,7 @@ export type TSignSshKeyResponse = {
export type TIssueSshCredsDTO = { export type TIssueSshCredsDTO = {
projectId: string; projectId: string;
templateName: string; certificateTemplateId: string;
keyAlgorithm: CertKeyAlgorithm; keyAlgorithm: CertKeyAlgorithm;
certType: SshCertType; certType: SshCertType;
principals: string[]; principals: string[];
@@ -126,8 +126,8 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
switch (operation) { switch (operation) {
case SshCertificateOperation.SIGN_SSH_KEY: { case SshCertificateOperation.SIGN_SSH_KEY: {
const { serialNumber, signedKey } = await signSshKey({ const { serialNumber, signedKey } = await signSshKey({
projectId: currentWorkspace?.id || "", projectId,
templateName: templateData.name, certificateTemplateId: templateData.id,
publicKey: existingPublicKey, publicKey: existingPublicKey,
certType, certType,
principals: principals.split(",").map((user) => user.trim()), principals: principals.split(",").map((user) => user.trim()),
@@ -144,7 +144,7 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
case SshCertificateOperation.ISSUE_SSH_CREDS: { case SshCertificateOperation.ISSUE_SSH_CREDS: {
const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({ const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({
projectId, projectId,
templateName: templateData.name, certificateTemplateId: templateData.id,
keyAlgorithm, keyAlgorithm,
certType, certType,
principals: principals.split(",").map((user) => user.trim()), principals: principals.split(",").map((user) => user.trim()),