mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Add alg column and index
This commit is contained in:
@@ -10,6 +10,8 @@ import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintI
|
||||
const OLD_ENROLLMENT_TYPE_CHECK_CONSTRAINT = "pki_certificate_profiles_enrollmentType_check";
|
||||
const NEW_ENROLLMENT_TYPE_CHECK_CONSTRAINT = "pki_certificate_profiles_enrollment_type_check";
|
||||
|
||||
const PUBLIC_KEY_ALG_INDEX = "pki_acme_accounts_publicKey_alg_index";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
// Create PkiAcmeEnrollmentConfig table
|
||||
if (!(await knex.schema.hasTable(TableName.PkiAcmeEnrollmentConfig))) {
|
||||
@@ -56,6 +58,9 @@ export async function up(knex: Knex): Promise<void> {
|
||||
|
||||
// Public key (JWK format)
|
||||
t.jsonb("publicKey").notNullable();
|
||||
// The JWS algorithm used to sign the public key when creating the account, e.g. "RS256", "ES256", "PS256", etc.
|
||||
t.string("alg").notNullable();
|
||||
t.index(["publicKey", "alg"], PUBLIC_KEY_ALG_INDEX);
|
||||
|
||||
t.timestamps(true, true, true);
|
||||
});
|
||||
|
||||
@@ -61,13 +61,13 @@ export const pkiAcmeAccountDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findByPublicKey = async (publicKey: unknown, tx?: Knex) => {
|
||||
const findByPublicKey = async (publicKey: unknown, alg: string, tx?: Knex) => {
|
||||
try {
|
||||
const account = await (tx || db)(TableName.PkiAcmeAccount).where({ publicKey }).first();
|
||||
const account = await (tx || db)(TableName.PkiAcmeAccount).where({ publicKey, alg }).first();
|
||||
|
||||
return account || null;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME account by public key" });
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME account by public key and alg" });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
138
backend/src/ee/services/pki-acme/pki-acme-auth-dal.ts
Normal file
138
backend/src/ee/services/pki-acme/pki-acme-auth-dal.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { TPkiAcmeAuthsInsert, TPkiAcmeAuthsUpdate } from "@app/db/schemas/pki-acme-auths";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TPkiAcmeAuthDALFactory = ReturnType<typeof pkiAcmeAuthDALFactory>;
|
||||
|
||||
export const pkiAcmeAuthDALFactory = (db: TDbClient) => {
|
||||
const pkiAcmeAuthOrm = ormify(db, TableName.PkiAcmeAuth);
|
||||
|
||||
const create = async (data: TPkiAcmeAuthsInsert, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeAuth).insert(data).returning("*");
|
||||
const [auth] = result;
|
||||
|
||||
if (!auth) {
|
||||
throw new Error("Failed to create PKI ACME auth");
|
||||
}
|
||||
|
||||
return auth;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Create PKI ACME auth" });
|
||||
}
|
||||
};
|
||||
|
||||
const updateById = async (id: string, data: TPkiAcmeAuthsUpdate, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeAuth).where({ id }).update(data).returning("*");
|
||||
const [auth] = result;
|
||||
|
||||
if (!auth) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return auth;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Update PKI ACME auth" });
|
||||
}
|
||||
};
|
||||
|
||||
const findById = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const auth = await (tx || db)(TableName.PkiAcmeAuth).where({ id }).first();
|
||||
|
||||
return auth || null;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME auth by id" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByAccountId = async (accountId: string, tx?: Knex) => {
|
||||
try {
|
||||
const auths = await (tx || db)(TableName.PkiAcmeAuth).where({ accountId });
|
||||
|
||||
return auths;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME auths by account id" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByStatus = async (status: string, tx?: Knex) => {
|
||||
try {
|
||||
const auths = await (tx || db)(TableName.PkiAcmeAuth).where({ status });
|
||||
|
||||
return auths;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME auths by status" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByAccountIdAndStatus = async (accountId: string, status: string, tx?: Knex) => {
|
||||
try {
|
||||
const auths = await (tx || db)(TableName.PkiAcmeAuth).where({ accountId, status });
|
||||
|
||||
return auths;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME auths by account id and status" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByIdentifier = async (identifierType: string, identifierValue: string, tx?: Knex) => {
|
||||
try {
|
||||
const auths = await (tx || db)(TableName.PkiAcmeAuth).where({ identifierType, identifierValue });
|
||||
|
||||
return auths;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME auths by identifier" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByCertificateId = async (certificateId: string, tx?: Knex) => {
|
||||
try {
|
||||
const auths = await (tx || db)(TableName.PkiAcmeAuth).where({ certificateId });
|
||||
|
||||
return auths;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME auths by certificate id" });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteById = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeAuth).where({ id }).delete().returning("*");
|
||||
const [auth] = result;
|
||||
|
||||
return auth || null;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Delete PKI ACME auth by id" });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteByAccountId = async (accountId: string, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeAuth).where({ accountId }).delete().returning("*");
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Delete PKI ACME auths by account id" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...pkiAcmeAuthOrm,
|
||||
create,
|
||||
updateById,
|
||||
findById,
|
||||
findByAccountId,
|
||||
findByStatus,
|
||||
findByAccountIdAndStatus,
|
||||
findByIdentifier,
|
||||
findByCertificateId,
|
||||
deleteById,
|
||||
deleteByAccountId
|
||||
};
|
||||
};
|
||||
116
backend/src/ee/services/pki-acme/pki-acme-order-dal.ts
Normal file
116
backend/src/ee/services/pki-acme/pki-acme-order-dal.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { TPkiAcmeOrdersInsert, TPkiAcmeOrdersUpdate } from "@app/db/schemas/pki-acme-orders";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TPkiAcmeOrderDALFactory = ReturnType<typeof pkiAcmeOrderDALFactory>;
|
||||
|
||||
export const pkiAcmeOrderDALFactory = (db: TDbClient) => {
|
||||
const pkiAcmeOrderOrm = ormify(db, TableName.PkiAcmeOrder);
|
||||
|
||||
const create = async (data: TPkiAcmeOrdersInsert, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeOrder).insert(data).returning("*");
|
||||
const [order] = result;
|
||||
|
||||
if (!order) {
|
||||
throw new Error("Failed to create PKI ACME order");
|
||||
}
|
||||
|
||||
return order;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Create PKI ACME order" });
|
||||
}
|
||||
};
|
||||
|
||||
const updateById = async (id: string, data: TPkiAcmeOrdersUpdate, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeOrder).where({ id }).update(data).returning("*");
|
||||
const [order] = result;
|
||||
|
||||
if (!order) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return order;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Update PKI ACME order" });
|
||||
}
|
||||
};
|
||||
|
||||
const findById = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const order = await (tx || db)(TableName.PkiAcmeOrder).where({ id }).first();
|
||||
|
||||
return order || null;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME order by id" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByAccountId = async (accountId: string, tx?: Knex) => {
|
||||
try {
|
||||
const orders = await (tx || db)(TableName.PkiAcmeOrder).where({ accountId });
|
||||
|
||||
return orders;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME orders by account id" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByStatus = async (status: string, tx?: Knex) => {
|
||||
try {
|
||||
const orders = await (tx || db)(TableName.PkiAcmeOrder).where({ status });
|
||||
|
||||
return orders;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME orders by status" });
|
||||
}
|
||||
};
|
||||
|
||||
const findByAccountIdAndStatus = async (accountId: string, status: string, tx?: Knex) => {
|
||||
try {
|
||||
const orders = await (tx || db)(TableName.PkiAcmeOrder).where({ accountId, status });
|
||||
|
||||
return orders;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find PKI ACME orders by account id and status" });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteById = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeOrder).where({ id }).delete().returning("*");
|
||||
const [order] = result;
|
||||
|
||||
return order || null;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Delete PKI ACME order by id" });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteByAccountId = async (accountId: string, tx?: Knex) => {
|
||||
try {
|
||||
const result = await (tx || db)(TableName.PkiAcmeOrder).where({ accountId }).delete().returning("*");
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Delete PKI ACME orders by account id" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...pkiAcmeOrderOrm,
|
||||
create,
|
||||
updateById,
|
||||
findById,
|
||||
findByAccountId,
|
||||
findByStatus,
|
||||
findByAccountIdAndStatus,
|
||||
deleteById,
|
||||
deleteByAccountId
|
||||
};
|
||||
};
|
||||
@@ -5,11 +5,14 @@ import { TCertificateProfileDALFactory } from "@app/services/certificate-profile
|
||||
|
||||
import { AcmeAccountDoesNotExistError, AcmeBadPublicKeyError, AcmeMalformedError } from "./pki-acme-errors";
|
||||
|
||||
import { TPkiAcmeAccounts } from "@app/db/schemas/pki-acme-accounts";
|
||||
import {
|
||||
EnrollmentType,
|
||||
TCertificateProfileWithConfigs
|
||||
} from "@app/services/certificate-profile/certificate-profile-types";
|
||||
import { flattenedVerify, importJWK, JWK, JWSHeaderParameters } from "jose";
|
||||
import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal";
|
||||
import { TPkiAcmeOrderDALFactory } from "./pki-acme-order-dal";
|
||||
import { ProtectedHeaderSchema } from "./pki-acme-schemas";
|
||||
import {
|
||||
TAcmeResponse,
|
||||
@@ -24,24 +27,22 @@ import {
|
||||
TGetAcmeAuthorizationResponse,
|
||||
TGetAcmeDirectoryResponse,
|
||||
TGetAcmeOrderResponse,
|
||||
TJwsPayload,
|
||||
TJwsPayloadWithJwk,
|
||||
TListAcmeOrdersResponse,
|
||||
TPkiAcmeServiceFactory,
|
||||
TRawJwsPayload,
|
||||
TRespondToAcmeChallengeResponse
|
||||
} from "./pki-acme-types";
|
||||
import { TPkiAcmeAccounts } from "@app/db/schemas/pki-acme-accounts";
|
||||
import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal";
|
||||
|
||||
type TPkiAcmeServiceFactoryDep = {
|
||||
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findById">;
|
||||
pkiAcmeAccountDAL: Pick<TPkiAcmeAccountDALFactory, "findByPublicKey" | "create">;
|
||||
acmeAccountDAL: Pick<TPkiAcmeAccountDALFactory, "findByPublicKey" | "create">;
|
||||
acmeOrderDAL: Pick<TPkiAcmeOrderDALFactory, "create">;
|
||||
};
|
||||
|
||||
export const pkiAcmeServiceFactory = ({
|
||||
certificateProfileDAL,
|
||||
pkiAcmeAccountDAL
|
||||
acmeAccountDAL
|
||||
}: TPkiAcmeServiceFactoryDep): TPkiAcmeServiceFactory => {
|
||||
const validateAcmeProfile = async (profileId: string): Promise<TCertificateProfileWithConfigs> => {
|
||||
const profile = await certificateProfileDAL.findById(profileId);
|
||||
@@ -81,6 +82,32 @@ export const pkiAcmeServiceFactory = ({
|
||||
throw new AcmeMalformedError({ detail: "Invalid protected header" });
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const payload = JSON.parse(decoder.decode(rawPayload)) as TCreateAcmeAccountPayload;
|
||||
// TODO: also consume the nonce here
|
||||
return { payload, protectedHeader, jwk: protectedHeader.jwk as JsonWebKey, alg: protectedHeader.alg };
|
||||
};
|
||||
|
||||
const validateJwsPayload = async (rawJwsPayload: TRawJwsPayload): Promise<TJwsPayloadWithJwk> => {
|
||||
const { payload: rawPayload, protectedHeader: rawProtectedHeader } = await flattenedVerify(
|
||||
rawJwsPayload,
|
||||
async (protectedHeader: JWSHeaderParameters | undefined) => {
|
||||
if (protectedHeader === undefined) {
|
||||
throw new AcmeMalformedError({ detail: "Protected header is required" });
|
||||
}
|
||||
if (protectedHeader.kid === undefined) {
|
||||
throw new AcmeBadPublicKeyError({ detail: "Kid is required in the protected header" });
|
||||
}
|
||||
|
||||
const imported = await importJWK(protectedHeader.jwk as JWK, protectedHeader.alg);
|
||||
return imported;
|
||||
}
|
||||
);
|
||||
const { success, data: protectedHeader } = ProtectedHeaderSchema.safeParse(rawProtectedHeader);
|
||||
if (!success) {
|
||||
throw new AcmeMalformedError({ detail: "Invalid protected header" });
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const payload = JSON.parse(decoder.decode(rawPayload)) as TCreateAcmeAccountPayload;
|
||||
// TODO: also consume the nonce here
|
||||
@@ -109,7 +136,7 @@ export const pkiAcmeServiceFactory = ({
|
||||
{ onlyReturnExisting, contact }: TCreateAcmeAccountPayload
|
||||
): Promise<TAcmeResponse<TCreateAcmeAccountResponse>> => {
|
||||
const profile = await validateAcmeProfile(profileId);
|
||||
const existingAccount: TPkiAcmeAccounts | null = await pkiAcmeAccountDAL.findByPublicKey(jwk);
|
||||
const existingAccount: TPkiAcmeAccounts | null = await acmeAccountDAL.findByPublicKey(jwk, alg);
|
||||
if (onlyReturnExisting && !existingAccount) {
|
||||
throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" });
|
||||
}
|
||||
@@ -128,7 +155,7 @@ export const pkiAcmeServiceFactory = ({
|
||||
};
|
||||
}
|
||||
|
||||
const newAccount = await pkiAcmeAccountDAL.create({
|
||||
const newAccount = await acmeAccountDAL.create({
|
||||
profileId: profile.id,
|
||||
publicKey: jwk,
|
||||
emails: contact ?? []
|
||||
@@ -150,17 +177,23 @@ export const pkiAcmeServiceFactory = ({
|
||||
const createAcmeOrder = async (
|
||||
profileId: string,
|
||||
payload: TCreateAcmeOrderPayload
|
||||
): Promise<TCreateAcmeOrderResponse> => {
|
||||
): Promise<TAcmeResponse<TCreateAcmeOrderResponse>> => {
|
||||
const profile = await validateAcmeProfile(profileId);
|
||||
|
||||
// FIXME: Implement ACME new order creation
|
||||
const orderId = "FIXME-order-id";
|
||||
return {
|
||||
status: "pending",
|
||||
expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
||||
identifiers: [],
|
||||
authorizations: [],
|
||||
finalize: buildUrl(`/api/v1/pki/acme/profiles/${profileId}/orders/${orderId}/finalize`)
|
||||
status: 201,
|
||||
body: {
|
||||
status: "pending",
|
||||
expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
||||
identifiers: [],
|
||||
authorizations: [],
|
||||
finalize: buildUrl(`/api/v1/pki/acme/profiles/${profile.id}/orders/${orderId}/finalize`)
|
||||
},
|
||||
headers: {
|
||||
Location: buildUrl(`/api/v1/pki/acme/profiles/${profile.id}/orders/${orderId}`)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ export type TFinalizeAcmeOrderPayload = z.infer<typeof FinalizeAcmeOrderBodySche
|
||||
|
||||
export type TJwsPayloadWithJwk = TJwsPayload & {
|
||||
jwk: JsonWebKey;
|
||||
alg: string;
|
||||
};
|
||||
export type TAcmeResponse<TPayload> = {
|
||||
status: number;
|
||||
@@ -57,7 +58,10 @@ export type TPkiAcmeServiceFactory = {
|
||||
jwk: JsonWebKey,
|
||||
body: TCreateAcmeAccountPayload
|
||||
) => Promise<TAcmeResponse<TCreateAcmeAccountResponse>>;
|
||||
createAcmeOrder: (profileId: string, body: TCreateAcmeOrderPayload) => Promise<TCreateAcmeOrderResponse>;
|
||||
createAcmeOrder: (
|
||||
profileId: string,
|
||||
body: TCreateAcmeOrderPayload
|
||||
) => Promise<TAcmeResponse<TCreateAcmeOrderResponse>>;
|
||||
deactivateAcmeAccount: (
|
||||
profileId: string,
|
||||
accountId: string,
|
||||
|
||||
Reference in New Issue
Block a user