mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 19:28:58 +00:00
Add ssh project default cas
This commit is contained in:
@@ -1,21 +1,29 @@
|
||||
import { execFile } from "child_process";
|
||||
import crypto from "crypto";
|
||||
import { promises as fs } from "fs";
|
||||
import { Knex } from "knex";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { promisify } from "util";
|
||||
|
||||
import { TSshCertificateTemplates } from "@app/db/schemas";
|
||||
import { SshCertKeyAlgorithm } from "@app/ee/services/ssh-certificate/ssh-certificate-types";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
|
||||
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
|
||||
import {
|
||||
isValidHostPattern,
|
||||
isValidUserPattern
|
||||
} from "../ssh-certificate-template/ssh-certificate-template-validators";
|
||||
import { SshCertType, TCreateSshCertDTO } from "./ssh-certificate-authority-types";
|
||||
import {
|
||||
SshCaKeySource,
|
||||
SshCaStatus,
|
||||
SshCertType,
|
||||
TCreateSshCaHelperDTO,
|
||||
TCreateSshCertDTO
|
||||
} from "./ssh-certificate-authority-types";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
@@ -31,31 +39,35 @@ export const createSshCertSerialNumber = () => {
|
||||
* 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.
|
||||
*/
|
||||
export const createSshKeyPair = async (keyAlgorithm: CertKeyAlgorithm) => {
|
||||
export const createSshKeyPair = async (keyAlgorithm: SshCertKeyAlgorithm) => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ssh-key-"));
|
||||
const privateKeyFile = path.join(tempDir, "id_key");
|
||||
const publicKeyFile = `${privateKeyFile}.pub`;
|
||||
|
||||
let keyType: string;
|
||||
let keyBits: string;
|
||||
let keyBits: string | null;
|
||||
|
||||
switch (keyAlgorithm) {
|
||||
case CertKeyAlgorithm.RSA_2048:
|
||||
case SshCertKeyAlgorithm.RSA_2048:
|
||||
keyType = "rsa";
|
||||
keyBits = "2048";
|
||||
break;
|
||||
case CertKeyAlgorithm.RSA_4096:
|
||||
case SshCertKeyAlgorithm.RSA_4096:
|
||||
keyType = "rsa";
|
||||
keyBits = "4096";
|
||||
break;
|
||||
case CertKeyAlgorithm.ECDSA_P256:
|
||||
case SshCertKeyAlgorithm.ECDSA_P256:
|
||||
keyType = "ecdsa";
|
||||
keyBits = "256";
|
||||
break;
|
||||
case CertKeyAlgorithm.ECDSA_P384:
|
||||
case SshCertKeyAlgorithm.ECDSA_P384:
|
||||
keyType = "ecdsa";
|
||||
keyBits = "384";
|
||||
break;
|
||||
case SshCertKeyAlgorithm.ED25519:
|
||||
keyType = "ed25519";
|
||||
keyBits = null;
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestError({
|
||||
message: "Failed to produce SSH CA key pair generation command due to unrecognized key algorithm"
|
||||
@@ -63,10 +75,16 @@ export const createSshKeyPair = async (keyAlgorithm: CertKeyAlgorithm) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const args = ["-t", keyType];
|
||||
if (keyBits !== null) {
|
||||
args.push("-b", keyBits);
|
||||
}
|
||||
args.push("-f", privateKeyFile, "-N", "");
|
||||
|
||||
// Generate the SSH key pair
|
||||
// The "-N ''" sets an empty passphrase
|
||||
// The keys are created in the temporary directory
|
||||
await execFileAsync("ssh-keygen", ["-t", keyType, "-b", keyBits, "-f", privateKeyFile, "-N", ""], {
|
||||
await execFileAsync("ssh-keygen", args, {
|
||||
timeout: EXEC_TIMEOUT_MS
|
||||
});
|
||||
|
||||
@@ -322,17 +340,22 @@ const validateSshPublicKey = async (publicKey: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getKeyAlgorithmFromFingerprintOutput = (output: string): CertKeyAlgorithm | undefined => {
|
||||
export const getKeyAlgorithmFromFingerprintOutput = (output: string): SshCertKeyAlgorithm | undefined => {
|
||||
const parts = output.trim().split(" ");
|
||||
const bitsInt = parseInt(parts[0], 10);
|
||||
const keyTypeRaw = parts.at(-1)?.replace(/[()]/g, ""); // remove surrounding parentheses
|
||||
|
||||
if (keyTypeRaw === "RSA") {
|
||||
return bitsInt === 2048 ? CertKeyAlgorithm.RSA_2048 : CertKeyAlgorithm.RSA_4096;
|
||||
return bitsInt === 2048 ? SshCertKeyAlgorithm.RSA_2048 : SshCertKeyAlgorithm.RSA_4096;
|
||||
}
|
||||
|
||||
if (keyTypeRaw === "ECDSA") {
|
||||
return bitsInt === 256 ? CertKeyAlgorithm.ECDSA_P256 : CertKeyAlgorithm.ECDSA_P384;
|
||||
return bitsInt === 256 ? SshCertKeyAlgorithm.ECDSA_P256 : SshCertKeyAlgorithm.ECDSA_P384;
|
||||
}
|
||||
|
||||
if (keyTypeRaw === "ED25519") {
|
||||
// TODO: test
|
||||
return SshCertKeyAlgorithm.ED25519;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -473,3 +496,68 @@ export const createSshCert = async ({
|
||||
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
export const createSshCaHelper = async ({
|
||||
projectId,
|
||||
friendlyName,
|
||||
keyAlgorithm: requestedKeyAlgorithm,
|
||||
keySource,
|
||||
externalPk,
|
||||
externalSk,
|
||||
sshCertificateAuthorityDAL,
|
||||
sshCertificateAuthoritySecretDAL,
|
||||
kmsService,
|
||||
tx: outerTx
|
||||
}: TCreateSshCaHelperDTO) => {
|
||||
// Function to handle the actual creation logic
|
||||
const processCreation = async (tx: Knex) => {
|
||||
let publicKey: string;
|
||||
let privateKey: string;
|
||||
let keyAlgorithm: SshCertKeyAlgorithm = requestedKeyAlgorithm;
|
||||
if (keySource === SshCaKeySource.INTERNAL) {
|
||||
// generate SSH CA key pair internally
|
||||
({ publicKey, privateKey } = await createSshKeyPair(requestedKeyAlgorithm));
|
||||
} else {
|
||||
// use external SSH CA key pair
|
||||
if (!externalPk || !externalSk) {
|
||||
throw new BadRequestError({
|
||||
message: "Public and private keys are required if generateSigningKey is false"
|
||||
});
|
||||
}
|
||||
publicKey = externalPk;
|
||||
privateKey = externalSk;
|
||||
keyAlgorithm = await validateExternalSshCaKeyPair(publicKey, privateKey);
|
||||
}
|
||||
const ca = await sshCertificateAuthorityDAL.create(
|
||||
{
|
||||
projectId,
|
||||
friendlyName,
|
||||
status: SshCaStatus.ACTIVE,
|
||||
keyAlgorithm,
|
||||
keySource
|
||||
},
|
||||
tx
|
||||
);
|
||||
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey(
|
||||
{
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
},
|
||||
tx
|
||||
);
|
||||
await sshCertificateAuthoritySecretDAL.create(
|
||||
{
|
||||
sshCaId: ca.id,
|
||||
encryptedPrivateKey: secretManagerEncryptor({ plainText: Buffer.from(privateKey, "utf8") }).cipherTextBlob
|
||||
},
|
||||
tx
|
||||
);
|
||||
return { ...ca, publicKey };
|
||||
};
|
||||
|
||||
if (outerTx) {
|
||||
return processCreation(outerTx);
|
||||
}
|
||||
|
||||
return sshCertificateAuthorityDAL.transaction(processCreation);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user