mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(PKI): External CA EAB Support + DigiCert Docs
This commit is contained in:
@@ -2148,7 +2148,9 @@ export const CertificateAuthorities = {
|
||||
directoryUrl: `The directory URL for the ACME Certificate Authority.`,
|
||||
accountEmail: `The email address for the ACME Certificate Authority.`,
|
||||
provider: `The DNS provider for the ACME Certificate Authority.`,
|
||||
hostedZoneId: `The hosted zone ID for the ACME Certificate Authority.`
|
||||
hostedZoneId: `The hosted zone ID for the ACME Certificate Authority.`,
|
||||
eabKid: `The External Account Binding (EAB) Key ID for the ACME Certificate Authority. Required if the ACME provider uses EAB.`,
|
||||
eabHmacKey: `The External Account Binding (EAB) HMAC key for the ACME Certificate Authority. Required if the ACME provider uses EAB.`
|
||||
},
|
||||
INTERNAL: {
|
||||
type: "The type of CA to create.",
|
||||
|
||||
@@ -64,6 +64,8 @@ type DBConfigurationColumn = {
|
||||
directoryUrl: string;
|
||||
accountEmail: string;
|
||||
hostedZoneId: string;
|
||||
eabKid?: string;
|
||||
eabHmacKey?: string;
|
||||
};
|
||||
|
||||
export const castDbEntryToAcmeCertificateAuthority = (
|
||||
@@ -89,7 +91,9 @@ export const castDbEntryToAcmeCertificateAuthority = (
|
||||
hostedZoneId: dbConfigurationCol.hostedZoneId
|
||||
},
|
||||
directoryUrl: dbConfigurationCol.directoryUrl,
|
||||
accountEmail: dbConfigurationCol.accountEmail
|
||||
accountEmail: dbConfigurationCol.accountEmail,
|
||||
eabKid: dbConfigurationCol.eabKid,
|
||||
eabHmacKey: dbConfigurationCol.eabHmacKey
|
||||
},
|
||||
status: ca.status as CaStatus
|
||||
};
|
||||
@@ -128,7 +132,7 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
});
|
||||
}
|
||||
|
||||
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProviderConfig } = configuration;
|
||||
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProviderConfig, eabKid, eabHmacKey } = configuration;
|
||||
const appConnection = await appConnectionDAL.findById(dnsAppConnectionId);
|
||||
|
||||
if (!appConnection) {
|
||||
@@ -171,7 +175,9 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
directoryUrl,
|
||||
accountEmail,
|
||||
dnsProvider: dnsProviderConfig.provider,
|
||||
hostedZoneId: dnsProviderConfig.hostedZoneId
|
||||
hostedZoneId: dnsProviderConfig.hostedZoneId,
|
||||
eabKid,
|
||||
eabHmacKey
|
||||
}
|
||||
},
|
||||
tx
|
||||
@@ -214,7 +220,7 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
}) => {
|
||||
const updatedCa = await certificateAuthorityDAL.transaction(async (tx) => {
|
||||
if (configuration) {
|
||||
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProviderConfig } = configuration;
|
||||
const { dnsAppConnectionId, directoryUrl, accountEmail, dnsProviderConfig, eabKid, eabHmacKey } = configuration;
|
||||
const appConnection = await appConnectionDAL.findById(dnsAppConnectionId);
|
||||
|
||||
if (!appConnection) {
|
||||
@@ -254,7 +260,9 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
directoryUrl,
|
||||
accountEmail,
|
||||
dnsProvider: dnsProviderConfig.provider,
|
||||
hostedZoneId: dnsProviderConfig.hostedZoneId
|
||||
hostedZoneId: dnsProviderConfig.hostedZoneId,
|
||||
eabKid,
|
||||
eabHmacKey
|
||||
}
|
||||
},
|
||||
tx
|
||||
@@ -354,10 +362,19 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
|
||||
await blockLocalAndPrivateIpAddresses(acmeCa.configuration.directoryUrl);
|
||||
|
||||
const acmeClient = new acme.Client({
|
||||
const acmeClientOptions: acme.ClientOptions = {
|
||||
directoryUrl: acmeCa.configuration.directoryUrl,
|
||||
accountKey
|
||||
});
|
||||
};
|
||||
|
||||
if (acmeCa.configuration.eabKid && acmeCa.configuration.eabHmacKey) {
|
||||
acmeClientOptions.externalAccountBinding = {
|
||||
kid: acmeCa.configuration.eabKid,
|
||||
hmacKey: acmeCa.configuration.eabHmacKey
|
||||
};
|
||||
}
|
||||
|
||||
const acmeClient = new acme.Client(acmeClientOptions);
|
||||
|
||||
const alg = keyAlgorithmToAlgCfg(CertKeyAlgorithm.RSA_2048);
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ export const AcmeCertificateAuthorityConfigurationSchema = z.object({
|
||||
hostedZoneId: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.hostedZoneId)
|
||||
}),
|
||||
directoryUrl: z.string().url().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.directoryUrl),
|
||||
accountEmail: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.accountEmail)
|
||||
accountEmail: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.accountEmail),
|
||||
eabKid: z.string().trim().max(64).optional().describe(CertificateAuthorities.CONFIGURATIONS.ACME.eabKid),
|
||||
eabHmacKey: z.string().trim().max(512).optional().describe(CertificateAuthorities.CONFIGURATIONS.ACME.eabHmacKey)
|
||||
});
|
||||
|
||||
export const AcmeCertificateAuthorityCredentialsSchema = z.object({
|
||||
|
||||
@@ -147,6 +147,8 @@ In the following steps, we explore how to set up ACME Certificate Authority inte
|
||||
- **Directory URL**: Enter the ACME v2 directory URL for your chosen CA provider (e.g., `https://acme-v02.api.letsencrypt.org/directory` for Let's Encrypt).
|
||||
- **Account Email**: Email address to associate with your ACME account. This email will receive important notifications about your certificates.
|
||||
- **Enable Direct Issuance**: Toggle on to allow direct certificate issuance without requiring subscribers.
|
||||
- **EAB Key Identifier (KID)**: (Optional) The Key Identifier (KID) provided by your ACME CA for External Account Binding (EAB). This is required by some ACME providers (e.g., ZeroSSL, DigiCert) to link your ACME account to an external account you've pre-registered with them.
|
||||
- **EAB HMAC Key**: (Optional) The HMAC Key provided by your ACME CA for External Account Binding (EAB). This key is used in conjunction with the KID to prove ownership of the external account during ACME account registration.
|
||||
|
||||
Finally, press **Create** to register the ACME CA with Infisical.
|
||||
</Step>
|
||||
@@ -277,6 +279,19 @@ Let's Encrypt is a free, automated, and open Certificate Authority that provides
|
||||
Always test your ACME integration using Let's Encrypt's staging environment first. This allows you to verify your DNS configuration and certificate issuance process without consuming your production rate limits.
|
||||
</Note>
|
||||
|
||||
## Example: DigiCert Integration
|
||||
|
||||
DigiCert is a leading commercial Certificate Authority providing a wide range of trusted SSL/TLS certificates. Infisical can integrate with DigiCert's ACME service to automate the provisioning and management of these certificates.
|
||||
|
||||
- **Directory URL**: `https://acme.digicert.com/v2/acme/directory`
|
||||
- **External Account Binding (EAB)**: Required. You will need a Key Identifier (KID) and HMAC Key from your DigiCert account to register the ACME CA in Infisical.
|
||||
- **Certificate Validity**: Typically 90 days, with automatic renewal through Infisical.
|
||||
- **Trusted By**: All major browsers and operating systems.
|
||||
|
||||
<Note>
|
||||
When integrating with DigiCert ACME, ensure you have obtained the necessary External Account Binding (EAB) Key Identifier (KID) and HMAC Key from your DigiCert account.
|
||||
</Note>
|
||||
|
||||
## FAQ
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 539 KiB After Width: | Height: | Size: 587 KiB |
@@ -16,6 +16,8 @@ export type TAcmeCertificateAuthority = {
|
||||
};
|
||||
directoryUrl: string;
|
||||
accountEmail: string;
|
||||
eabKid?: string;
|
||||
eabHmacKey?: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -42,6 +42,17 @@ import {
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
import { slugSchema } from "@app/lib/schemas";
|
||||
|
||||
const REQUIRED_EAB_DIRECTORIES = [
|
||||
"https://acme.digicert.com/v2/acme/directory",
|
||||
"https://acme.zerossl.com/v2/DV90",
|
||||
"https://acme.ssl.com/sslcom-dv-rsa",
|
||||
"https://acme.ssl.com/sslcom-dv-ecc",
|
||||
"https://dv.acme-v02.api.pki.goog/directory",
|
||||
"https://acme.sectigo.com/v2/OV",
|
||||
"https://acme.sectigo.com/v2/EV",
|
||||
"https://acme.cisco.com/ACMEv2/directory"
|
||||
];
|
||||
|
||||
const baseSchema = z.object({
|
||||
type: z.nativeEnum(CaType),
|
||||
name: slugSchema({
|
||||
@@ -51,18 +62,39 @@ const baseSchema = z.object({
|
||||
status: z.nativeEnum(CaStatus)
|
||||
});
|
||||
|
||||
const acmeConfigurationSchema = z.object({
|
||||
dnsAppConnection: z.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
}),
|
||||
dnsProviderConfig: z.object({
|
||||
provider: z.nativeEnum(AcmeDnsProvider),
|
||||
hostedZoneId: z.string()
|
||||
}),
|
||||
directoryUrl: z.string(),
|
||||
accountEmail: z.string()
|
||||
});
|
||||
const acmeConfigurationSchema = z
|
||||
.object({
|
||||
dnsAppConnection: z.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
}),
|
||||
dnsProviderConfig: z.object({
|
||||
provider: z.nativeEnum(AcmeDnsProvider),
|
||||
hostedZoneId: z.string()
|
||||
}),
|
||||
directoryUrl: z.string(),
|
||||
accountEmail: z.string(),
|
||||
eabKid: z.string().optional(),
|
||||
eabHmacKey: z.string().optional()
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (REQUIRED_EAB_DIRECTORIES.includes(data.directoryUrl)) {
|
||||
if (!data.eabKid || data.eabKid.trim() === "") {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "EAB Key Identifier (KID) is required for this directory URL",
|
||||
path: ["eabKid"]
|
||||
});
|
||||
}
|
||||
if (!data.eabHmacKey || data.eabHmacKey.trim() === "") {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "EAB HMAC Key is required for this directory URL",
|
||||
path: ["eabHmacKey"]
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const azureAdCsConfigurationSchema = z.object({
|
||||
azureAdcsConnection: z.object({
|
||||
@@ -122,6 +154,10 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
caType === CaType.ACME && configuration && "dnsProviderConfig" in configuration
|
||||
? configuration.dnsProviderConfig.provider
|
||||
: undefined;
|
||||
const directoryUrl =
|
||||
caType === CaType.ACME && configuration && "directoryUrl" in configuration
|
||||
? configuration.directoryUrl
|
||||
: undefined;
|
||||
|
||||
useEffect(() => {
|
||||
const initialType = (popUp?.ca?.data as { type: CaType })?.type;
|
||||
@@ -155,7 +191,9 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
hostedZoneId: ""
|
||||
},
|
||||
directoryUrl: "",
|
||||
accountEmail: ""
|
||||
accountEmail: "",
|
||||
eabKid: "",
|
||||
eabHmacKey: ""
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -192,7 +230,8 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
availableAzureConnections
|
||||
]);
|
||||
|
||||
const isPending = isRoute53Pending || isCloudflarePending || isAzurePending;
|
||||
const isPending =
|
||||
isRoute53Pending || isCloudflarePending || (isAzurePending && caType === CaType.AZURE_AD_CS);
|
||||
|
||||
const dnsAppConnection =
|
||||
caType === CaType.ACME && configuration && "dnsAppConnection" in configuration
|
||||
@@ -227,7 +266,9 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
hostedZoneId: ca.configuration.dnsProviderConfig.hostedZoneId
|
||||
},
|
||||
directoryUrl: ca.configuration.directoryUrl,
|
||||
accountEmail: ca.configuration.accountEmail
|
||||
accountEmail: ca.configuration.accountEmail,
|
||||
eabKid: ca.configuration.eabKid,
|
||||
eabHmacKey: ca.configuration.eabHmacKey
|
||||
}
|
||||
});
|
||||
} else if (ca.type === CaType.AZURE_AD_CS && availableConnections?.length) {
|
||||
@@ -268,7 +309,9 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
dnsProviderConfig: formConfiguration.dnsProviderConfig,
|
||||
directoryUrl: formConfiguration.directoryUrl,
|
||||
accountEmail: formConfiguration.accountEmail,
|
||||
dnsAppConnectionId: formConfiguration.dnsAppConnection.id
|
||||
dnsAppConnectionId: formConfiguration.dnsAppConnection.id,
|
||||
eabKid: formConfiguration.eabKid,
|
||||
eabHmacKey: formConfiguration.eabHmacKey
|
||||
};
|
||||
} else if (type === CaType.AZURE_AD_CS && "azureAdcsConnection" in formConfiguration) {
|
||||
configPayload = {
|
||||
@@ -499,6 +542,44 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="configuration.eabKid"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="EAB Key Identifier (KID)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isOptional={!REQUIRED_EAB_DIRECTORIES.includes(directoryUrl || "")}
|
||||
isRequired={REQUIRED_EAB_DIRECTORIES.includes(directoryUrl || "")}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="abc123def456ghi789jkl012mno345pqr678stu901vwx234yz"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="configuration.eabHmacKey"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="EAB HMAC Key"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isOptional={!REQUIRED_EAB_DIRECTORIES.includes(directoryUrl || "")}
|
||||
isRequired={REQUIRED_EAB_DIRECTORIES.includes(directoryUrl || "")}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="dGhpc2lzYW5leGFtcGxlaG1hY2tleWZvcmRpZ2ljZXJ0YWNtZXRlc3RpbmcxMjM0NTY3ODkw"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{caType === CaType.AZURE_AD_CS && (
|
||||
|
||||
Reference in New Issue
Block a user