SSL: автоматическое обновление
AWS — балансировщик, CloudFront, API Gateway
Функция Lambda раз в сутки забирает свежий сертификат и обновляет его в AWS Certificate Manager на том же ARN — балансировщик подхватит сам.
Обновляется сам
Подходит, если сайт отдаёт сертификат через AWS: Application или Network Load Balancer, CloudFront, API Gateway, Elastic Beanstalk. Сертификат там лежит в AWS Certificate Manager (ACM). Мы не просим ключей от вашего AWS: функция работает в вашем аккаунте и имеет право обновлять только один сертификат.
Как это работает: ACM позволяет импортировать новый сертификат в тот же ARN, и все сервисы, которые им пользуются, переходят на новый без перенастройки. Функция Lambda раз в сутки сравнивает сертификат по ссылке с тем, что в ACM, и если он новый — импортирует его вместе с вашим ключом, который лежит в Secrets Manager.
Что понадобится
- Ссылка на сертификат из кабинета:
https://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН. - Закрытый ключ без пароля (RSA 2048 или ECDSA P-256) — тот, с которым заказывали сертификат.
- Доступ к AWS Console с правами создавать функции Lambda, секреты и роли IAM.
- 20 минут.
Регион. Для CloudFront сертификат должен быть в регионе US East (N. Virginia) us-east-1 — и секрет, и функцию делайте там же. Для балансировщика — регион балансировщика.
Шаг 1. Ключ — в Secrets Manager
- Secrets Manager → Store a new secret.
- Other type of secret → вкладка Plaintext → удалите пример и вставьте весь текст ключа со строками
-----BEGIN … PRIVATE KEY-----и-----END … PRIVATE KEY-----. - Next → название
zrazu-ssl-key→ Next → Next → Store. - Откройте секрет и скопируйте Secret ARN.
Шаг 2. Первый импорт в ACM (если сертификата там ещё нет)
- Certificate Manager → Import a certificate.
- Certificate body — содержимое
https://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН/cert.pem(откройте в браузере, скопируйте весь текст); Certificate private key — ваш ключ; Certificate chain — содержимоеhttps://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН/chain.pem. - Import. Откройте сертификат и скопируйте ARN.
- Подключите сертификат к сервису, если ещё не подключили: в балансировщике — Listeners → HTTPS:443 → Edit → Default SSL/TLS certificate; в CloudFront — Distribution → Edit → Custom SSL certificate.
Шаг 3. Функция Lambda
- Lambda → Create function → Author from scratch. Название
zrazu-ssl-update, Runtime — Node.js 22.x, Create function. - Во вкладке Code откройте
index.mjs, замените весь текст на код ниже. Вставьте свои значения в строкиCERT_ARN(из шага 2) иKEY_SECRET(из шага 1). Нажмите Deploy.
// ZRAZU: обновление сертификата в AWS Certificate Manager по личной ссылке. Запускается раз в сутки.
import { ACMClient, DescribeCertificateCommand, ImportCertificateCommand } from "@aws-sdk/client-acm";
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
import { X509Certificate, createPrivateKey } from "node:crypto";
const PULL = "https://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН";
const CERT_ARN = "arn:aws:acm:РЕГИОН:АККАУНТ:certificate/…";
const KEY_SECRET = "arn:aws:secretsmanager:РЕГИОН:АККАУНТ:secret:zrazu-ssl-key-…";
async function pem(part) {
const res = await fetch(`${PULL}/${part}.pem`);
if (!res.ok) throw new Error(`ZRAZU: ${part}.pem — HTTP ${res.status}`);
const text = await res.text();
if (!text.includes("BEGIN CERTIFICATE")) throw new Error("ZRAZU: по ссылке не сертификат");
return text;
}
const serial = (s) => (s ?? "").replace(/:/g, "").replace(/^0+/, "").toUpperCase();
export const handler = async () => {
const acm = new ACMClient({ region: CERT_ARN.split(":")[3] });
const [cert, chain] = await Promise.all([pem("cert"), pem("chain")]);
const leaf = new X509Certificate(cert);
const current = await acm.send(new DescribeCertificateCommand({ CertificateArn: CERT_ARN }));
if (serial(current.Certificate?.Serial) === serial(leaf.serialNumber)) return "ZRAZU: сертификат не изменился";
if (new Date(leaf.validTo) <= new Date()) throw new Error("ZRAZU: сертификат уже не действует");
const secrets = new SecretsManagerClient({ region: KEY_SECRET.split(":")[3] });
const { SecretString: key } = await secrets.send(new GetSecretValueCommand({ SecretId: KEY_SECRET }));
if (!leaf.checkPrivateKey(createPrivateKey(key))) {
throw new Error("ZRAZU: сертификат выпущен на другой ключ — положите новый ключ в секрет");
}
await acm.send(new ImportCertificateCommand({
CertificateArn: CERT_ARN,
Certificate: Buffer.from(cert),
CertificateChain: Buffer.from(chain),
PrivateKey: Buffer.from(key),
}));
return `ZRAZU: новый сертификат установлен, действует до ${leaf.validTo}`;
};- Configuration → General configuration → Edit → Timeout 30 секунд → Save.
Шаг 4. Права функции — только на этот сертификат и этот секрет
- Configuration → Permissions → щёлкните название роли (откроется IAM).
- Add permissions → Create inline policy → вкладка JSON → вставьте текст ниже, подставив два ARN.
- Next → название
zrazu-ssl-update→ Create policy.
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["acm:DescribeCertificate", "acm:ImportCertificate"], "Resource": "ARN_СЕРТИФИКАТА" },
{ "Effect": "Allow", "Action": "secretsmanager:GetSecretValue", "Resource": "ARN_СЕКРЕТА" }
]
}Шаг 5. Расписание
- В функции Add trigger → EventBridge (CloudWatch Events).
- Create a new rule → название
zrazu-ssl-daily→ Schedule expression →rate(1 day)→ Add.
Проверка
- В функции вкладка Test → Test (событие любое). Результат — «новый сертификат установлен» или «сертификат не изменился».
- В Certificate Manager у сертификата — новая дата Expires.
- Сайт: https://zrazu.com/tools/ssl/example.com/. CloudFront разносит новый сертификат по своим узлам несколько минут.
- Журнал каждого запуска — Monitor → View CloudWatch logs.
Если не получилось
AccessDeniedException— в политике из шага 4 не тот ARN или не тот регион.HTTP 404— ссылка отключена или заменена: создайте новую в карточке сертификата и замените строкуPULL.- «сертификат выпущен на другой ключ» — сертификат перевыпускали с новым ключом: обновите секрет (Retrieve secret value → Edit).
ValidationExceptionпро ключ — ключ с паролем или не тот формат: нужен PEM без пароля.- Task timed out — увеличьте Timeout (шаг 3).