mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-09 23:48:05 -05:00
Merge pull request #5021 from Infisical/PKI-54-optional-acme-challenge
feature: add skip dns ownership validation option for acme cert profile
This commit is contained in:
@@ -192,3 +192,28 @@ Feature: Challenge
|
|||||||
And the value response with jq ".status" should be equal to 400
|
And the value response with jq ".status" should be equal to 400
|
||||||
And the value response with jq ".type" should be equal to "urn:ietf:params:acme:error:badCSR"
|
And the value response with jq ".type" should be equal to "urn:ietf:params:acme:error:badCSR"
|
||||||
And the value response with jq ".detail" should be equal to "Invalid CSR: Common name + SANs mismatch with order identifiers"
|
And the value response with jq ".detail" should be equal to "Invalid CSR: Common name + SANs mismatch with order identifiers"
|
||||||
|
|
||||||
|
Scenario: Get certificate without passing challenge when skip DNS ownership verification is enabled
|
||||||
|
Given I create an ACME profile with config as "acme_profile"
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"skipDnsOwnershipVerification": true
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
When I have an ACME client connecting to "{BASE_URL}/api/v1/cert-manager/acme/profiles/{acme_profile.id}/directory"
|
||||||
|
Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account
|
||||||
|
When I create certificate signing request as csr
|
||||||
|
Then I add names to certificate signing request csr
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"COMMON_NAME": "localhost"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
And I create a RSA private key pair as cert_key
|
||||||
|
And I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format
|
||||||
|
And I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order
|
||||||
|
And the value order.body with jq ".status" should be equal to "ready"
|
||||||
|
And I poll and finalize the ACME order order as finalized_order
|
||||||
|
And the value finalized_order.body with jq ".status" should be equal to "valid"
|
||||||
|
And I parse the full-chain certificate from order finalized_order as cert
|
||||||
|
And the value cert with jq ".subject.common_name" should be equal to "localhost"
|
||||||
|
|||||||
@@ -266,6 +266,46 @@ def step_impl(context: Context, ca_id: str, template_id: str, profile_var: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@given(
|
||||||
|
'I create an ACME profile with config as "{profile_var}"'
|
||||||
|
)
|
||||||
|
def step_impl(context: Context, profile_var: str):
|
||||||
|
profile_slug = faker.slug()
|
||||||
|
jwt_token = context.vars["AUTH_TOKEN"]
|
||||||
|
acme_config = replace_vars(json.loads(context.text), context.vars)
|
||||||
|
response = context.http_client.post(
|
||||||
|
"/api/v1/cert-manager/certificate-profiles",
|
||||||
|
headers=dict(authorization="Bearer {}".format(jwt_token)),
|
||||||
|
json={
|
||||||
|
"projectId": context.vars["PROJECT_ID"],
|
||||||
|
"slug": profile_slug,
|
||||||
|
"description": "ACME Profile created by BDD test",
|
||||||
|
"enrollmentType": "acme",
|
||||||
|
"caId": context.vars["CERT_CA_ID"],
|
||||||
|
"certificateTemplateId": context.vars["CERT_TEMPLATE_ID"],
|
||||||
|
"acmeConfig": acme_config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
resp_json = response.json()
|
||||||
|
profile_id = resp_json["certificateProfile"]["id"]
|
||||||
|
kid = profile_id
|
||||||
|
|
||||||
|
response = context.http_client.get(
|
||||||
|
f"/api/v1/cert-manager/certificate-profiles/{profile_id}/acme/eab-secret/reveal",
|
||||||
|
headers=dict(authorization="Bearer {}".format(jwt_token)),
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
resp_json = response.json()
|
||||||
|
secret = resp_json["eabSecret"]
|
||||||
|
|
||||||
|
context.vars[profile_var] = AcmeProfile(
|
||||||
|
profile_id,
|
||||||
|
eab_kid=kid,
|
||||||
|
eab_secret=secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@given('I have an ACME cert profile with external ACME CA as "{profile_var}"')
|
@given('I have an ACME cert profile with external ACME CA as "{profile_var}"')
|
||||||
def step_impl(context: Context, profile_var: str):
|
def step_impl(context: Context, profile_var: str):
|
||||||
profile_id = context.vars.get("PROFILE_ID")
|
profile_id = context.vars.get("PROFILE_ID")
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.PkiAcmeEnrollmentConfig)) {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.PkiAcmeEnrollmentConfig, "skipDnsOwnershipVerification"))) {
|
||||||
|
await knex.schema.alterTable(TableName.PkiAcmeEnrollmentConfig, (t) => {
|
||||||
|
t.boolean("skipDnsOwnershipVerification").defaultTo(false).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.PkiAcmeEnrollmentConfig)) {
|
||||||
|
if (await knex.schema.hasColumn(TableName.PkiAcmeEnrollmentConfig, "skipDnsOwnershipVerification")) {
|
||||||
|
await knex.schema.alterTable(TableName.PkiAcmeEnrollmentConfig, (t) => {
|
||||||
|
t.dropColumn("skipDnsOwnershipVerification");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,8 @@ export const PkiAcmeEnrollmentConfigsSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
encryptedEabSecret: zodBuffer,
|
encryptedEabSecret: zodBuffer,
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
skipDnsOwnershipVerification: z.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TPkiAcmeEnrollmentConfigs = z.infer<typeof PkiAcmeEnrollmentConfigsSchema>;
|
export type TPkiAcmeEnrollmentConfigs = z.infer<typeof PkiAcmeEnrollmentConfigsSchema>;
|
||||||
|
|||||||
@@ -567,6 +567,8 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
accountId: string;
|
accountId: string;
|
||||||
payload: TCreateAcmeOrderPayload;
|
payload: TCreateAcmeOrderPayload;
|
||||||
}): Promise<TAcmeResponse<TAcmeOrderResource>> => {
|
}): Promise<TAcmeResponse<TAcmeOrderResource>> => {
|
||||||
|
const profile = await validateAcmeProfile(profileId);
|
||||||
|
const skipDnsOwnershipVerification = profile.acmeConfig?.skipDnsOwnershipVerification ?? false;
|
||||||
// TODO: check and see if we have existing orders for this account that meet the criteria
|
// TODO: check and see if we have existing orders for this account that meet the criteria
|
||||||
// if we do, return the existing order
|
// if we do, return the existing order
|
||||||
// TODO: check the identifiers and see if are they even allowed for this profile.
|
// TODO: check the identifiers and see if are they even allowed for this profile.
|
||||||
@@ -592,7 +594,7 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
const createdOrder = await acmeOrderDAL.create(
|
const createdOrder = await acmeOrderDAL.create(
|
||||||
{
|
{
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
status: AcmeOrderStatus.Pending,
|
status: skipDnsOwnershipVerification ? AcmeOrderStatus.Ready : AcmeOrderStatus.Pending,
|
||||||
notBefore: payload.notBefore ? new Date(payload.notBefore) : undefined,
|
notBefore: payload.notBefore ? new Date(payload.notBefore) : undefined,
|
||||||
notAfter: payload.notAfter ? new Date(payload.notAfter) : undefined,
|
notAfter: payload.notAfter ? new Date(payload.notAfter) : undefined,
|
||||||
// TODO: read config from the profile to get the expiration time instead
|
// TODO: read config from the profile to get the expiration time instead
|
||||||
@@ -611,7 +613,7 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
const auth = await acmeAuthDAL.create(
|
const auth = await acmeAuthDAL.create(
|
||||||
{
|
{
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
status: AcmeAuthStatus.Pending,
|
status: skipDnsOwnershipVerification ? AcmeAuthStatus.Valid : AcmeAuthStatus.Pending,
|
||||||
identifierType: identifier.type,
|
identifierType: identifier.type,
|
||||||
identifierValue: identifier.value,
|
identifierValue: identifier.value,
|
||||||
// RFC 8555 suggests a token with at least 128 bits of entropy
|
// RFC 8555 suggests a token with at least 128 bits of entropy
|
||||||
@@ -623,15 +625,17 @@ export const pkiAcmeServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
// TODO: support other challenge types here. Currently only HTTP-01 is supported.
|
if (!skipDnsOwnershipVerification) {
|
||||||
await acmeChallengeDAL.create(
|
// TODO: support other challenge types here. Currently only HTTP-01 is supported.
|
||||||
{
|
await acmeChallengeDAL.create(
|
||||||
authId: auth.id,
|
{
|
||||||
status: AcmeChallengeStatus.Pending,
|
authId: auth.id,
|
||||||
type: AcmeChallengeType.HTTP_01
|
status: AcmeChallengeStatus.Pending,
|
||||||
},
|
type: AcmeChallengeType.HTTP_01
|
||||||
tx
|
},
|
||||||
);
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
return auth;
|
return auth;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
|||||||
renewBeforeDays: z.number().min(1).max(30).optional()
|
renewBeforeDays: z.number().min(1).max(30).optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
acmeConfig: z.object({}).optional(),
|
acmeConfig: z
|
||||||
|
.object({
|
||||||
|
skipDnsOwnershipVerification: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
externalConfigs: ExternalConfigUnionSchema
|
externalConfigs: ExternalConfigUnionSchema
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
@@ -245,7 +249,8 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
|||||||
acmeConfig: z
|
acmeConfig: z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
directoryUrl: z.string()
|
directoryUrl: z.string(),
|
||||||
|
skipDnsOwnershipVerification: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
externalConfigs: ExternalConfigUnionSchema
|
externalConfigs: ExternalConfigUnionSchema
|
||||||
@@ -434,6 +439,11 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
|||||||
renewBeforeDays: z.number().min(1).max(30).optional()
|
renewBeforeDays: z.number().min(1).max(30).optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
acmeConfig: z
|
||||||
|
.object({
|
||||||
|
skipDnsOwnershipVerification: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
externalConfigs: ExternalConfigUnionSchema
|
externalConfigs: ExternalConfigUnionSchema
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
|
|||||||
@@ -168,7 +168,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigAutoRenew"),
|
db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigAutoRenew"),
|
||||||
db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigRenewBeforeDays"),
|
db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigRenewBeforeDays"),
|
||||||
db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeConfigId"),
|
db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeConfigId"),
|
||||||
db.ref("encryptedEabSecret").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeConfigEncryptedEabSecret")
|
db.ref("encryptedEabSecret").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeConfigEncryptedEabSecret"),
|
||||||
|
db
|
||||||
|
.ref("skipDnsOwnershipVerification")
|
||||||
|
.withSchema(TableName.PkiAcmeEnrollmentConfig)
|
||||||
|
.as("acmeConfigSkipDnsOwnershipVerification")
|
||||||
)
|
)
|
||||||
.where(`${TableName.PkiCertificateProfile}.id`, id)
|
.where(`${TableName.PkiCertificateProfile}.id`, id)
|
||||||
.first();
|
.first();
|
||||||
@@ -198,7 +202,8 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
const acmeConfig = result.acmeConfigId
|
const acmeConfig = result.acmeConfigId
|
||||||
? ({
|
? ({
|
||||||
id: result.acmeConfigId,
|
id: result.acmeConfigId,
|
||||||
encryptedEabSecret: result.acmeConfigEncryptedEabSecret
|
encryptedEabSecret: result.acmeConfigEncryptedEabSecret,
|
||||||
|
skipDnsOwnershipVerification: result.acmeConfigSkipDnsOwnershipVerification ?? false
|
||||||
} as TCertificateProfileWithConfigs["acmeConfig"])
|
} as TCertificateProfileWithConfigs["acmeConfig"])
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
@@ -356,7 +361,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"),
|
db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"),
|
||||||
db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenew"),
|
db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenew"),
|
||||||
db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays"),
|
db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays"),
|
||||||
db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeId")
|
db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeId"),
|
||||||
|
db
|
||||||
|
.ref("skipDnsOwnershipVerification")
|
||||||
|
.withSchema(TableName.PkiAcmeEnrollmentConfig)
|
||||||
|
.as("acmeSkipDnsOwnershipVerification")
|
||||||
);
|
);
|
||||||
|
|
||||||
if (processedRules) {
|
if (processedRules) {
|
||||||
@@ -393,7 +402,8 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const acmeConfig = result.acmeId
|
const acmeConfig = result.acmeId
|
||||||
? {
|
? {
|
||||||
id: result.acmeId as string
|
id: result.acmeId as string,
|
||||||
|
skipDnsOwnershipVerification: !!result.acmeSkipDnsOwnershipVerification
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,11 @@ export const createCertificateProfileSchema = z
|
|||||||
renewBeforeDays: z.number().min(1).max(30).optional()
|
renewBeforeDays: z.number().min(1).max(30).optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
acmeConfig: z.object({}).optional()
|
acmeConfig: z
|
||||||
|
.object({
|
||||||
|
skipDnsOwnershipVerification: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -155,6 +159,11 @@ export const updateCertificateProfileSchema = z
|
|||||||
autoRenew: z.boolean().default(false),
|
autoRenew: z.boolean().default(false),
|
||||||
renewBeforeDays: z.number().min(1).max(30).optional()
|
renewBeforeDays: z.number().min(1).max(30).optional()
|
||||||
})
|
})
|
||||||
|
.optional(),
|
||||||
|
acmeConfig: z
|
||||||
|
.object({
|
||||||
|
skipDnsOwnershipVerification: z.boolean().optional()
|
||||||
|
})
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
|
|||||||
@@ -403,7 +403,13 @@ export const certificateProfileServiceFactory = ({
|
|||||||
apiConfigId = apiConfig.id;
|
apiConfigId = apiConfig.id;
|
||||||
} else if (data.enrollmentType === EnrollmentType.ACME && data.acmeConfig) {
|
} else if (data.enrollmentType === EnrollmentType.ACME && data.acmeConfig) {
|
||||||
const { encryptedEabSecret } = await generateAndEncryptAcmeEabSecret(projectId, kmsService, projectDAL);
|
const { encryptedEabSecret } = await generateAndEncryptAcmeEabSecret(projectId, kmsService, projectDAL);
|
||||||
const acmeConfig = await acmeEnrollmentConfigDAL.create({ encryptedEabSecret }, tx);
|
const acmeConfig = await acmeEnrollmentConfigDAL.create(
|
||||||
|
{
|
||||||
|
skipDnsOwnershipVerification: data.acmeConfig.skipDnsOwnershipVerification ?? false,
|
||||||
|
encryptedEabSecret
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
acmeConfigId = acmeConfig.id;
|
acmeConfigId = acmeConfig.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,7 +511,7 @@ export const certificateProfileServiceFactory = ({
|
|||||||
const updatedData =
|
const updatedData =
|
||||||
finalIssuerType === IssuerType.SELF_SIGNED && existingProfile.caId ? { ...data, caId: null } : data;
|
finalIssuerType === IssuerType.SELF_SIGNED && existingProfile.caId ? { ...data, caId: null } : data;
|
||||||
|
|
||||||
const { estConfig, apiConfig, ...profileUpdateData } = updatedData;
|
const { estConfig, apiConfig, acmeConfig, ...profileUpdateData } = updatedData;
|
||||||
|
|
||||||
const updatedProfile = await certificateProfileDAL.transaction(async (tx) => {
|
const updatedProfile = await certificateProfileDAL.transaction(async (tx) => {
|
||||||
if (estConfig && existingProfile.estConfigId) {
|
if (estConfig && existingProfile.estConfigId) {
|
||||||
@@ -547,6 +553,16 @@ export const certificateProfileServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (acmeConfig && existingProfile.acmeConfigId) {
|
||||||
|
await acmeEnrollmentConfigDAL.updateById(
|
||||||
|
existingProfile.acmeConfigId,
|
||||||
|
{
|
||||||
|
skipDnsOwnershipVerification: acmeConfig.skipDnsOwnershipVerification ?? false
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const profileResult = await certificateProfileDAL.updateById(profileId, profileUpdateData, tx);
|
const profileResult = await certificateProfileDAL.updateById(profileId, profileUpdateData, tx);
|
||||||
return profileResult;
|
return profileResult;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ export type TCertificateProfileUpdate = Omit<
|
|||||||
autoRenew?: boolean;
|
autoRenew?: boolean;
|
||||||
renewBeforeDays?: number;
|
renewBeforeDays?: number;
|
||||||
};
|
};
|
||||||
acmeConfig?: unknown;
|
acmeConfig?: {
|
||||||
|
skipDnsOwnershipVerification?: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateProfileWithConfigs = TCertificateProfile & {
|
export type TCertificateProfileWithConfigs = TCertificateProfile & {
|
||||||
@@ -83,6 +85,7 @@ export type TCertificateProfileWithConfigs = TCertificateProfile & {
|
|||||||
id: string;
|
id: string;
|
||||||
directoryUrl: string;
|
directoryUrl: string;
|
||||||
encryptedEabSecret?: Buffer;
|
encryptedEabSecret?: Buffer;
|
||||||
|
skipDnsOwnershipVerification?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +1,13 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
import { TAcmeEnrollmentConfigInsert, TAcmeEnrollmentConfigUpdate } from "./enrollment-config-types";
|
|
||||||
|
|
||||||
export type TAcmeEnrollmentConfigDALFactory = ReturnType<typeof acmeEnrollmentConfigDALFactory>;
|
export type TAcmeEnrollmentConfigDALFactory = ReturnType<typeof acmeEnrollmentConfigDALFactory>;
|
||||||
|
|
||||||
export const acmeEnrollmentConfigDALFactory = (db: TDbClient) => {
|
export const acmeEnrollmentConfigDALFactory = (db: TDbClient) => {
|
||||||
const acmeEnrollmentConfigOrm = ormify(db, TableName.PkiAcmeEnrollmentConfig);
|
const acmeEnrollmentConfigOrm = ormify(db, TableName.PkiAcmeEnrollmentConfig);
|
||||||
|
|
||||||
const create = async (data: TAcmeEnrollmentConfigInsert, tx?: Knex) => {
|
|
||||||
try {
|
|
||||||
const result = await (tx || db)(TableName.PkiAcmeEnrollmentConfig).insert(data).returning("*");
|
|
||||||
const [acmeConfig] = result;
|
|
||||||
|
|
||||||
if (!acmeConfig) {
|
|
||||||
throw new Error("Failed to create ACME enrollment config");
|
|
||||||
}
|
|
||||||
|
|
||||||
return acmeConfig;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "Create ACME enrollment config" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateById = async (id: string, data: TAcmeEnrollmentConfigUpdate, tx?: Knex) => {
|
|
||||||
try {
|
|
||||||
const result = await (tx || db)(TableName.PkiAcmeEnrollmentConfig).where({ id }).update(data).returning("*");
|
|
||||||
const [acmeConfig] = result;
|
|
||||||
|
|
||||||
if (!acmeConfig) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acmeConfig;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "Update ACME enrollment config" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const findById = async (id: string, tx?: Knex) => {
|
|
||||||
try {
|
|
||||||
const acmeConfig = await (tx || db)(TableName.PkiAcmeEnrollmentConfig).where({ id }).first();
|
|
||||||
|
|
||||||
return acmeConfig || null;
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "Find ACME enrollment config by id" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...acmeEnrollmentConfigOrm,
|
...acmeEnrollmentConfigOrm
|
||||||
create,
|
|
||||||
updateById,
|
|
||||||
findById
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,4 +37,6 @@ export interface TApiConfigData {
|
|||||||
renewBeforeDays?: number;
|
renewBeforeDays?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TAcmeConfigData {}
|
export interface TAcmeConfigData {
|
||||||
|
skipDnsOwnershipVerification?: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export type TCertificateProfileWithDetails = TCertificateProfile & {
|
|||||||
acmeConfig?: {
|
acmeConfig?: {
|
||||||
id: string;
|
id: string;
|
||||||
directoryUrl: string;
|
directoryUrl: string;
|
||||||
|
skipDnsOwnershipVerification?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,11 @@ const createSchema = z
|
|||||||
renewBeforeDays: z.number().min(1).max(365).optional()
|
renewBeforeDays: z.number().min(1).max(365).optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
acmeConfig: z.object({}).optional(),
|
acmeConfig: z
|
||||||
|
.object({
|
||||||
|
skipDnsOwnershipVerification: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
externalConfigs: z
|
externalConfigs: z
|
||||||
.object({
|
.object({
|
||||||
template: z.string().min(1, "Azure ADCS template is required")
|
template: z.string().min(1, "Azure ADCS template is required")
|
||||||
@@ -219,7 +223,11 @@ const editSchema = z
|
|||||||
renewBeforeDays: z.number().min(1).max(365).optional()
|
renewBeforeDays: z.number().min(1).max(365).optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
acmeConfig: z.object({}).optional(),
|
acmeConfig: z
|
||||||
|
.object({
|
||||||
|
skipDnsOwnershipVerification: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
externalConfigs: z
|
externalConfigs: z
|
||||||
.object({
|
.object({
|
||||||
template: z.string().optional()
|
template: z.string().optional()
|
||||||
@@ -406,7 +414,13 @@ export const CreateProfileModal = ({
|
|||||||
renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30
|
renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
acmeConfig: profile.enrollmentType === EnrollmentType.ACME ? {} : undefined,
|
acmeConfig:
|
||||||
|
profile.enrollmentType === EnrollmentType.ACME
|
||||||
|
? {
|
||||||
|
skipDnsOwnershipVerification:
|
||||||
|
profile.acmeConfig?.skipDnsOwnershipVerification || false
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
externalConfigs: profile.externalConfigs
|
externalConfigs: profile.externalConfigs
|
||||||
? {
|
? {
|
||||||
template:
|
template:
|
||||||
@@ -429,7 +443,9 @@ export const CreateProfileModal = ({
|
|||||||
autoRenew: false,
|
autoRenew: false,
|
||||||
renewBeforeDays: 30
|
renewBeforeDays: 30
|
||||||
},
|
},
|
||||||
acmeConfig: {},
|
acmeConfig: {
|
||||||
|
skipDnsOwnershipVerification: false
|
||||||
|
},
|
||||||
externalConfigs: undefined
|
externalConfigs: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -476,7 +492,13 @@ export const CreateProfileModal = ({
|
|||||||
renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30
|
renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
acmeConfig: profile.enrollmentType === EnrollmentType.ACME ? {} : undefined,
|
acmeConfig:
|
||||||
|
profile.enrollmentType === EnrollmentType.ACME
|
||||||
|
? {
|
||||||
|
skipDnsOwnershipVerification:
|
||||||
|
profile.acmeConfig?.skipDnsOwnershipVerification || false
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
externalConfigs: profile.externalConfigs
|
externalConfigs: profile.externalConfigs
|
||||||
? {
|
? {
|
||||||
template:
|
template:
|
||||||
@@ -667,7 +689,9 @@ export const CreateProfileModal = ({
|
|||||||
renewBeforeDays: 30
|
renewBeforeDays: 30
|
||||||
});
|
});
|
||||||
setValue("estConfig", undefined);
|
setValue("estConfig", undefined);
|
||||||
setValue("acmeConfig", undefined);
|
setValue("acmeConfig", {
|
||||||
|
skipDnsOwnershipVerification: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
onChange(value);
|
onChange(value);
|
||||||
}}
|
}}
|
||||||
@@ -797,7 +821,9 @@ export const CreateProfileModal = ({
|
|||||||
} else if (watchedEnrollmentType === "acme") {
|
} else if (watchedEnrollmentType === "acme") {
|
||||||
setValue("estConfig", undefined);
|
setValue("estConfig", undefined);
|
||||||
setValue("apiConfig", undefined);
|
setValue("apiConfig", undefined);
|
||||||
setValue("acmeConfig", {});
|
setValue("acmeConfig", {
|
||||||
|
skipDnsOwnershipVerification: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
onChange(value);
|
onChange(value);
|
||||||
}}
|
}}
|
||||||
@@ -846,7 +872,9 @@ export const CreateProfileModal = ({
|
|||||||
} else if (value === "acme") {
|
} else if (value === "acme") {
|
||||||
setValue("apiConfig", undefined);
|
setValue("apiConfig", undefined);
|
||||||
setValue("estConfig", undefined);
|
setValue("estConfig", undefined);
|
||||||
setValue("acmeConfig", {});
|
setValue("acmeConfig", {
|
||||||
|
skipDnsOwnershipVerification: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
onChange(value);
|
onChange(value);
|
||||||
}}
|
}}
|
||||||
@@ -975,10 +1003,24 @@ export const CreateProfileModal = ({
|
|||||||
<div className="mb-4 space-y-4">
|
<div className="mb-4 space-y-4">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="acmeConfig"
|
name="acmeConfig.skipDnsOwnershipVerification"
|
||||||
render={({ fieldState: { error } }) => (
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
<div className="flex items-center gap-2">{/* FIXME: ACME configuration */}</div>
|
<div className="flex items-center gap-3 rounded-md border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<Checkbox
|
||||||
|
id="skipDnsOwnershipVerification"
|
||||||
|
isChecked={value || false}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
/>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<span className="text-sm font-medium text-mineshaft-100">
|
||||||
|
Skip DNS Ownership Validation
|
||||||
|
</span>
|
||||||
|
<p className="text-xs text-bunker-300">
|
||||||
|
Skip DNS ownership verification during ACME certificate issuance.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user