Updates a custom certificate on Cloudflare from inside a Let's Encrypt renewal hook. Check the blog post at https://www.moritzfriedrich.com/posts/feature-branch-previews for more info..
#!/usr/bin/env php | |
<?php | |
// This should be the main domain your certificate is valid for. It's used for reverse | |
// matching the installed certificate and resolving the file path to your certificate | |
// files on the local file system. | |
$domain = 'your.domain.name.tld'; | |
// This should be your Cloudflare zone ID. You can find it in the right sidebar on your | |
// Cloudflaare account dashboard. | |
$cloudflareZone = 'your_cloudflare_zone'; | |
// This should be the same settings file used for the Let's Encrypt Cloudflare plugin. | |
// See https://certbot-dns-cloudflare.readthedocs.io/en/stable/ | |
$settings = parse_ini_file('/etc/letsencrypt/cloudflare.ini'); | |
out('Checking for existing certificates...'); | |
// Create a request to fetch all installed certificates. | |
// See https://api.cloudflare.com/#custom-ssl-for-a-zone-list-ssl-configurations | |
$ch = curl_init("https://api.cloudflare.com/client/v4/zones/{$cloudflareZone}/custom_certificates"); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
// As soon as the Let's Encrypt Cloudflare DNS plugin supports the "new" tokens, you | |
// should use them instead of auth email and key. | |
curl_setopt($ch, CURLOPT_HTTPHEADER, [ | |
'X-Auth-Email: ' . $settings['dns_cloudflare_email'], | |
'X-Auth-Key: ' . $settings['dns_cloudflare_api_key'], | |
'Content-Type: application/json', | |
]); | |
$response = json_decode(curl_exec($ch), true); | |
$certificates = $response['result'] ?? []; | |
out('Found %d certificates.', count($certificates)); | |
$certificateId = null; | |
// Iterate the certificates, try to find ours | |
foreach ($certificates as $certificate) { | |
if (in_array($domain, $certificate['hosts'], true)) { | |
$certificateId = $certificate['id']; | |
out('Found existing certificate %s for %s', $certificateId, $domain); | |
break; | |
} | |
} | |
// If we have an existing certificate, we'll want to PATCH the certificate, | |
// otherwise we POST to create a new one | |
$requestMethod = $certificateId ? 'PATCH' : 'POST'; | |
// Right-trim the URL to remove the last slash if we don't have an existing certificate ID | |
$url = rtrim("https://api.cloudflare.com/client/v4/zones/{$cloudflareZone}/custom_certificates/{$certificateId}", '/'); | |
// Read certificate and private key from the default letsencrypt directories. You should obviously | |
// adjust these paths if you have non-standard locations. | |
$certificate = file_get_contents("/etc/letsencrypt/live/{$domain}/fullchain.pem"); | |
$privateKey = file_get_contents("/etc/letsencrypt/live/{$domain}/privkey.pem"); | |
// Review the documentation for these parameters here: | |
// https://api.cloudflare.com/#custom-ssl-for-a-zone-create-ssl-configuration | |
$payload = json_encode([ | |
'certificate' => $certificate, | |
'private_key' => $privateKey, | |
'bundle_method' => 'optimal', | |
'type' => 'sni_custom' | |
]); | |
if ($certificateId) { | |
out('Updating existing certificate %s on Cloudflare', $certificateId); | |
} else { | |
out('Dispatching new certificate to Cloudflare'); | |
} | |
$ch = curl_init($url); | |
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $requestMethod); | |
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($ch, CURLOPT_HTTPHEADER, [ | |
'X-Auth-Email: ' . $settings['dns_cloudflare_email'], | |
'X-Auth-Key: ' . $settings['dns_cloudflare_api_key'], | |
'Content-Type: application/json', | |
]); | |
$response = json_decode(curl_exec($ch), true); | |
if ($response['success'] ?? false) { | |
out('Successfully set certificate %s', $response['result']['id'] ?? '-'); | |
exit(0); | |
} | |
error('Failed to update certificate: %s', json_encode($response, JSON_PRETTY_PRINT)); | |
exit(1); | |
function out(string $line, ...$replacements): void | |
{ | |
write(STDOUT, $line, $replacements); | |
} | |
function error(string $line, ...$replacements): void | |
{ | |
write(STDERR, $line, $replacements); | |
} | |
function write($stream, string $line, $replacements = []): void | |
{ | |
$microTime = explode(" ",microtime()); | |
$timestamp = date("d.m.Y H:i:s", $microTime[1]) . substr((string) $microTime[0], 1, 4); | |
fwrite($stream, $timestamp . "\t " . vsprintf($line, $replacements) . PHP_EOL); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment