-
Notifications
You must be signed in to change notification settings - Fork 758
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SQC-352 SQC-353 Create cert upload command for client side mtls certi…
…ficates and ca chain certificates
- Loading branch information
Adrian Gracia
committed
Jan 14, 2025
1 parent
9989022
commit 545288b
Showing
8 changed files
with
1,109 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
--- | ||
"wrangler": minor | ||
--- | ||
|
||
feat: implement the `wrangler cert upload` command | ||
|
||
This command allows users to upload a mTLS certificate/private key or certificate-authority certificate chain. | ||
|
||
For uploading mTLS certificate, run: | ||
|
||
- `wrangler cert upload mtls-certificate --cert cert.pem --key key.pem --name MY_CERT` | ||
|
||
For uploading CA certificate chain, run: | ||
- `wrangler cert upload certificate-authority --ca-cert server-ca.pem --name SERVER_CA` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { WranglerE2ETestHelper } from "./helpers/e2e-wrangler-test"; | ||
import { normalizeOutput } from "./helpers/normalize"; | ||
|
||
import forge from 'node-forge'; | ||
|
||
// Generate X509 self signed root key pair and certificate | ||
function generateRootCertificate() { | ||
const rootKeys = forge.pki.rsa.generateKeyPair(2048); | ||
const rootCert = forge.pki.createCertificate(); | ||
rootCert.publicKey = rootKeys.publicKey; | ||
rootCert.serialNumber = '01'; | ||
rootCert.validity.notBefore = new Date(); | ||
rootCert.validity.notAfter = new Date(); | ||
rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 10); // 10 years validity | ||
|
||
const rootAttrs = [ | ||
{ name: 'commonName', value: 'Root CA' }, | ||
{ name: 'countryName', value: 'US' }, | ||
{ shortName: 'ST', value: 'California' }, | ||
{ name: 'organizationName', value: 'Localhost Root CA' } | ||
]; | ||
rootCert.setSubject(rootAttrs); | ||
rootCert.setIssuer(rootAttrs); // Self-signed | ||
|
||
rootCert.sign(rootKeys.privateKey, forge.md.sha256.create()); | ||
|
||
return { certificate: rootCert, privateKey: rootKeys.privateKey }; | ||
} | ||
|
||
// Generate X509 leaf certificate signed by the root | ||
function generateLeafCertificate(rootCert: forge.pki.Certificate, rootKey: forge.pki.PrivateKey) { | ||
const leafKeys = forge.pki.rsa.generateKeyPair(2048); | ||
const leafCert = forge.pki.createCertificate(); | ||
leafCert.publicKey = leafKeys.publicKey; | ||
leafCert.serialNumber = '02'; | ||
leafCert.validity.notBefore = new Date(); | ||
leafCert.validity.notAfter = new Date(); | ||
leafCert.validity.notAfter.setFullYear(2034, 10, 18); | ||
|
||
const leafAttrs = [ | ||
{ name: 'commonName', value: 'example.org' }, | ||
{ name: 'countryName', value: 'US' }, | ||
{ shortName: 'ST', value: 'California' }, | ||
{ name: 'organizationName', value: 'Example Inc' } | ||
]; | ||
leafCert.setSubject(leafAttrs); | ||
leafCert.setIssuer(rootCert.subject.attributes); // Signed by root | ||
|
||
leafCert.sign(rootKey, forge.md.sha256.create()); // Signed using root's private key | ||
|
||
const pemLeafCert = forge.pki.certificateToPem(leafCert); | ||
const pemLeafKey = forge.pki.privateKeyToPem(leafKeys.privateKey); | ||
|
||
return { certificate: pemLeafCert, privateKey: pemLeafKey }; | ||
} | ||
|
||
// Generate self signed X509 CA root certificate | ||
function generateRootCaCert() { | ||
// Create a key pair (private and public keys) | ||
const keyPair = forge.pki.rsa.generateKeyPair(2048); | ||
|
||
// Create a new X.509 certificate | ||
const cert = forge.pki.createCertificate(); | ||
|
||
// Set certificate fields | ||
cert.publicKey = keyPair.publicKey; | ||
cert.serialNumber = '01'; | ||
cert.validity.notBefore = new Date(); | ||
cert.validity.notAfter = new Date(); | ||
cert.validity.notAfter.setFullYear(2034, 10, 18); | ||
|
||
// Add issuer and subject fields (for a root CA, they are the same) | ||
const attrs = [ | ||
{ name: 'commonName', value: 'Localhost CA' }, | ||
{ name: 'countryName', value: 'US' }, | ||
{ shortName: 'ST', value: 'California' }, | ||
{ name: 'localityName', value: 'San Francisco' }, | ||
{ name: 'organizationName', value: 'Localhost' }, | ||
{ shortName: 'OU', value: 'SSL Department' } | ||
]; | ||
cert.setSubject(attrs); | ||
cert.setIssuer(attrs); | ||
|
||
// Add basic constraints and key usage extensions | ||
cert.setExtensions([ | ||
{ | ||
name: 'basicConstraints', | ||
cA: true, | ||
}, | ||
{ | ||
name: 'keyUsage', | ||
keyCertSign: true, | ||
digitalSignature: true, | ||
cRLSign: true, | ||
} | ||
]); | ||
|
||
// Self-sign the certificate with the private key | ||
cert.sign(keyPair.privateKey, forge.md.sha256.create()); | ||
|
||
// Convert the certificate and private key to PEM format | ||
const pemCert = forge.pki.certificateToPem(cert); | ||
const pemPrivateKey = forge.pki.privateKeyToPem(keyPair.privateKey); | ||
|
||
return { certificate: pemCert, privateKey: pemPrivateKey }; | ||
} | ||
|
||
describe("cert", () => { | ||
const normalize = (str: string) => | ||
normalizeOutput(str, { | ||
[process.env.CLOUDFLARE_ACCOUNT_ID as string]: "CLOUDFLARE_ACCOUNT_ID", | ||
}); | ||
const helper = new WranglerE2ETestHelper(); | ||
// Generate root and leaf certificates | ||
const { certificate: rootCert, privateKey: rootKey } = generateRootCertificate(); | ||
const { certificate: leafCert, privateKey: leafKey } = generateLeafCertificate(rootCert, rootKey); | ||
const { certificate: caCert, privateKey: caCertKey } = generateRootCaCert(); | ||
|
||
it("upload mtls-certificate", async () => { | ||
// locally generated certs/key | ||
await helper.seed({"mtls_client_cert_file.pem": leafCert}); | ||
await helper.seed({"mtls_client_private_key_file.pem": leafKey}); | ||
|
||
const output = await helper.run(`wrangler cert upload mtls-certificate --name MTLS_CERTIFICATE_UPLOAD_CERT --cert mtls_client_cert_file.pem --key mtls_client_private_key_file.pem`); | ||
expect(normalize(output.stdout)).toMatchInlineSnapshot(` | ||
"Uploading mTLS Certificate MTLS_CERTIFICATE_UPLOAD_CERT... | ||
Success! Uploaded mTLS Certificate MTLS_CERTIFICATE_UPLOAD_CERT | ||
ID: 00000000-0000-0000-0000-000000000000 | ||
Issuer: CN=Root CA,O=Localhost Root CA,ST=California,C=US | ||
Expires on 11/18/2034" | ||
`); | ||
}); | ||
|
||
it("upload certificate-authority", async () => { | ||
await helper.seed({"ca_chain_cert.pem": caCert}); | ||
|
||
const output = await helper.run(`wrangler cert upload certificate-authority --name CERTIFICATE_AUTHORITY_UPLOAD_CERT --ca-cert ca_chain_cert.pem`); | ||
expect(normalize(output.stdout)).toMatchInlineSnapshot(` | ||
"Uploading CA Certificate CERTIFICATE_AUTHORITY_UPLOAD_CERT... | ||
Success! Uploaded CA Certificate CERTIFICATE_AUTHORITY_UPLOAD_CERT | ||
ID: 00000000-0000-0000-0000-000000000000 | ||
Issuer: CN=Localhost CA,OU=SSL Department,O=Localhost,L=San Francisco,ST=California,C=US | ||
Expires on 11/18/2034" | ||
`); | ||
}); | ||
|
||
it("list cert", async () => { | ||
const output = await helper.run(`wrangler cert list`); | ||
let result = normalize(output.stdout); | ||
expect(result).toContain("Name: MTLS_CERTIFICATE_UPLOAD_CERT"); | ||
expect(result).toContain('Name: CERTIFICATE_AUTHORITY_UPLOAD_CERT'); | ||
}); | ||
|
||
it("delete mtls cert", async () => { | ||
const delete_mtls_cert_output = await helper.run(`wrangler cert delete --name MTLS_CERTIFICATE_UPLOAD_CERT`); | ||
expect(normalize(delete_mtls_cert_output.stdout)).toMatchInlineSnapshot( | ||
` | ||
"? Are you sure you want to delete certificate 00000000-0000-0000-0000-000000000000 (MTLS_CERTIFICATE_UPLOAD_CERT)? | ||
🤖 Using fallback value in non-interactive context: yes | ||
Deleted certificate 00000000-0000-0000-0000-000000000000 (MTLS_CERTIFICATE_UPLOAD_CERT) successfully" | ||
` | ||
) | ||
}); | ||
|
||
it("delete ca chain cert", async() => { | ||
const delete_ca_cert_output = await helper.run(`wrangler cert delete --name CERTIFICATE_AUTHORITY_UPLOAD_CERT`); | ||
expect(normalize(delete_ca_cert_output.stdout)).toMatchInlineSnapshot( | ||
` | ||
"? Are you sure you want to delete certificate 00000000-0000-0000-0000-000000000000 (CERTIFICATE_AUTHORITY_UPLOAD_CERT)? | ||
🤖 Using fallback value in non-interactive context: yes | ||
Deleted certificate 00000000-0000-0000-0000-000000000000 (CERTIFICATE_AUTHORITY_UPLOAD_CERT) successfully" | ||
` | ||
) | ||
}) | ||
}); |
Oops, something went wrong.