mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 01:27:31 +00:00
feat: integrated to est routes
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
const hasEstConfigTable = await knex.schema.hasTable(TableName.CertificateAuthorityEstConfig);
|
const hasEstConfigTable = await knex.schema.hasTable(TableName.CertificateAuthorityEstConfig);
|
||||||
@@ -14,9 +15,12 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
tb.boolean("isEnabled");
|
tb.boolean("isEnabled");
|
||||||
tb.timestamps(true, true, true);
|
tb.timestamps(true, true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.CertificateAuthorityEstConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
await knex.schema.dropTableIfExists(TableName.CertificateAuthorityEstConfig);
|
await knex.schema.dropTableIfExists(TableName.CertificateAuthorityEstConfig);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.CertificateAuthorityEstConfig);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
import bcrypt from "bcrypt";
|
||||||
import { Certificate, ContentInfo, EncapsulatedContentInfo, SignedData } from "pkijs";
|
import { Certificate, ContentInfo, EncapsulatedContentInfo, SignedData } from "pkijs";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -39,41 +40,27 @@ export const registerCertificateEstRouter = async (server: FastifyZodProvider) =
|
|||||||
|
|
||||||
const urlFragments = req.url.split("/");
|
const urlFragments = req.url.split("/");
|
||||||
const certificateAuthorityId = urlFragments.slice(-2)[0];
|
const certificateAuthorityId = urlFragments.slice(-2)[0];
|
||||||
|
const caEstConfig = await server.services.certificateAuthority.getCaEstConfiguration({
|
||||||
|
isInternal: true,
|
||||||
|
caId: certificateAuthorityId
|
||||||
|
});
|
||||||
|
|
||||||
const hardcodedCertificateChain = `
|
if (!caEstConfig.isEnabled) {
|
||||||
-----BEGIN CERTIFICATE-----
|
throw new BadRequestError({
|
||||||
MIIEYzCCA0ugAwIBAgIUbxMrGIZnxNcX2kuYpGOFqix9P80wDQYJKoZIhvcNAQEL
|
message: "EST enrollment is disabled"
|
||||||
BQAwaTELMAkGA1UEBhMCUEgxDTALBgNVBAgMBENlYnUxDTALBgNVBAcMBENlYnUx
|
});
|
||||||
EjAQBgNVBAoMCUluZmlzaWNhbDEUMBIGA1UECwwLRW5naW5lZXJpbmcxEjAQBgNV
|
}
|
||||||
BAMMCWxvY2FsaG9zdDAeFw0yNDA4MTIxMzM4MTNaFw0yNTA4MTIxMzM4MTNaMGkx
|
|
||||||
CzAJBgNVBAYTAlBIMQ0wCwYDVQQIDARDZWJ1MQ0wCwYDVQQHDARDZWJ1MRIwEAYD
|
|
||||||
VQQKDAlJbmZpc2ljYWwxFDASBgNVBAsMC0VuZ2luZWVyaW5nMRIwEAYDVQQDDAls
|
|
||||||
b2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDqssBBMfzr
|
|
||||||
1DDRIxl8TcCHmQU+qhmw8ACkoNN0b+vD0USVv4SC1ABKtYQBBDvBOtQulqc4yTRw
|
|
||||||
A3Q0y3XUR+pyCFb5PcTG8ZFUZ7ewrrHrdExd0enY/R3eDPAb6H7hokDS10Sr5BRR
|
|
||||||
Oow109yzX7ipbw+kYSOOLTF1gX+ewbfpcGNylJNOvFNcu4V64Qg5NXp2Lo4o/VTj
|
|
||||||
IY9yxgVjep8utC/klughk3/EUqfyZ8/9BHyYj3KWDj7VpZNU4o506ZkYsCOPESe1
|
|
||||||
SMl8z4s4bEkfTd6+9SetKkwmCbRpZE5iS0XV0lrySK7AGwKHPuJ5RYj0WZp5O/SK
|
|
||||||
1zC0azN787T3AgMBAAGjggEBMIH+MB0GA1UdDgQWBBT25nGrtg4VmDaXscjwEv/B
|
|
||||||
CSFd2jCBpgYDVR0jBIGeMIGbgBT25nGrtg4VmDaXscjwEv/BCSFd2qFtpGswaTEL
|
|
||||||
MAkGA1UEBhMCUEgxDTALBgNVBAgMBENlYnUxDTALBgNVBAcMBENlYnUxEjAQBgNV
|
|
||||||
BAoMCUluZmlzaWNhbDEUMBIGA1UECwwLRW5naW5lZXJpbmcxEjAQBgNVBAMMCWxv
|
|
||||||
Y2FsaG9zdIIUbxMrGIZnxNcX2kuYpGOFqix9P80wDwYDVR0TAQH/BAUwAwEB/zAO
|
|
||||||
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEL
|
|
||||||
BQADggEBABPV6jpVHvnvp6cAPewL6SSN20KGdNX3MCpLIxPhz8dbGnc2SWMaR0Eo
|
|
||||||
GqAYvUgG0xpEWCTZ7RDtfrU7vt6+PnFpP2z0a4YToF24/tdAOMAUQ2AedULAb8UP
|
|
||||||
gwHDeZKKYhs7kscApO0VgYJgjqFe2Kjlt0zzVcMj0qrwgdDUFTNWGOdQy1ghmStc
|
|
||||||
nBw2xVppG0QAyIWnvxqPva+czHhMd8bmLR44VCuzO5xS5B/AUk7BeNBLuEEfM3DR
|
|
||||||
quZ0PRwgsaY/WND3ux93FaSiqfn5y9uZdJkqfJcPL6SKRms6v6da4Rh/DyFcWQFW
|
|
||||||
iwIeUl1cXagVKziyr4Ch5U5dnp+y8Es=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
`;
|
|
||||||
|
|
||||||
const sslClientCert = req.headers["x-ssl-client-cert"] as string;
|
const sslClientCert = req.headers["x-ssl-client-cert"] as string;
|
||||||
if (!sslClientCert) {
|
const leafCertificate = decodeURIComponent(sslClientCert).match(
|
||||||
|
/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g
|
||||||
|
)?.[0];
|
||||||
|
|
||||||
|
if (!sslClientCert || !leafCertificate) {
|
||||||
throw new UnauthorizedError({ message: "Missing client certificate" });
|
throw new UnauthorizedError({ message: "Missing client certificate" });
|
||||||
}
|
}
|
||||||
const clientCertBody = decodeURIComponent(sslClientCert)
|
|
||||||
|
const clientCertBody = leafCertificate
|
||||||
.replace("-----BEGIN CERTIFICATE-----", "")
|
.replace("-----BEGIN CERTIFICATE-----", "")
|
||||||
.replace("-----END CERTIFICATE-----", "")
|
.replace("-----END CERTIFICATE-----", "")
|
||||||
.replace(/\n/g, "")
|
.replace(/\n/g, "")
|
||||||
@@ -81,7 +68,7 @@ export const registerCertificateEstRouter = async (server: FastifyZodProvider) =
|
|||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
// validate SSL client cert against configured CA
|
// validate SSL client cert against configured CA
|
||||||
const chainCerts = hardcodedCertificateChain
|
const chainCerts = caEstConfig.caChain
|
||||||
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
|
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
|
||||||
?.map((cert) => {
|
?.map((cert) => {
|
||||||
const processedBody = cert
|
const processedBody = cert
|
||||||
@@ -126,8 +113,21 @@ export const registerCertificateEstRouter = async (server: FastifyZodProvider) =
|
|||||||
throw new UnauthorizedError({ message: "Missing HTTP credentials" });
|
throw new UnauthorizedError({ message: "Missing HTTP credentials" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expected format is user:password
|
||||||
const basicCredential = atob(rawCredential);
|
const basicCredential = atob(rawCredential);
|
||||||
// compare with EST configuration here
|
const password = basicCredential.split(":").pop();
|
||||||
|
if (!password) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "No password provided"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPasswordValid = await bcrypt.compare(password, caEstConfig.hashedPassphrase);
|
||||||
|
if (!isPasswordValid) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "Invalid credentials"
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
|
|||||||
@@ -810,6 +810,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const caEstConfig = await server.services.certificateAuthority.getCaEstConfiguration({
|
const caEstConfig = await server.services.certificateAuthority.getCaEstConfiguration({
|
||||||
|
isInternal: false,
|
||||||
caId: req.params.caId,
|
caId: req.params.caId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
|
|||||||
@@ -1535,30 +1535,28 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
return estConfig;
|
return estConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCaEstConfiguration = async ({
|
const getCaEstConfiguration = async (dto: TGetCaEstConfigurationDTO) => {
|
||||||
caId,
|
const ca = await certificateAuthorityDAL.findById(dto.caId);
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actor,
|
|
||||||
actorOrgId
|
|
||||||
}: TGetCaEstConfigurationDTO) => {
|
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
throw new NotFoundError({ message: "CA not found" });
|
throw new NotFoundError({ message: "CA not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
if (!dto.isInternal) {
|
||||||
actor,
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actorId,
|
dto.actor,
|
||||||
ca.projectId,
|
dto.actorId,
|
||||||
actorAuthMethod,
|
ca.projectId,
|
||||||
actorOrgId
|
dto.actorAuthMethod,
|
||||||
);
|
dto.actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
ProjectPermissionSub.CertificateAuthorities
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { caId } = dto;
|
||||||
|
|
||||||
const caEstConfig = await certificateAuthorityEstConfigDAL.findOne({
|
const caEstConfig = await certificateAuthorityEstConfigDAL.findOne({
|
||||||
caId
|
caId
|
||||||
@@ -1587,7 +1585,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
caId,
|
caId,
|
||||||
isEnabled: caEstConfig.isEnabled,
|
isEnabled: caEstConfig.isEnabled,
|
||||||
caChain: decryptedCaChain.toString()
|
caChain: decryptedCaChain.toString(),
|
||||||
|
hashedPassphrase: caEstConfig.hashedPassphrase
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -181,6 +181,12 @@ export type TUpdateCaEstConfigurationDTO = {
|
|||||||
isEnabled?: boolean;
|
isEnabled?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TGetCaEstConfigurationDTO = {
|
export type TGetCaEstConfigurationDTO =
|
||||||
caId: string;
|
| {
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
isInternal: true;
|
||||||
|
caId: string;
|
||||||
|
}
|
||||||
|
| ({
|
||||||
|
isInternal: false;
|
||||||
|
caId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">);
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export const CaEnrollmentModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
>
|
>
|
||||||
<TextArea
|
<TextArea
|
||||||
{...field}
|
{...field}
|
||||||
className="border-none bg-mineshaft-900 text-gray-400"
|
className="min-h-[15rem] border-none bg-mineshaft-900 text-gray-400"
|
||||||
reSize="none"
|
reSize="none"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
Reference in New Issue
Block a user