# AWS — балансировщик, CloudFront, API Gateway

> Функция Lambda раз в сутки забирает свежий сертификат и обновляет его в AWS Certificate Manager на том же ARN — балансировщик подхватит сам.

URL: https://zrazu.com/ru/docs/ssl-auto-aws/

Подходит, если сайт отдаёт сертификат через 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

1. **Secrets Manager → Store a new secret**.
2. **Other type of secret** → вкладка **Plaintext** → удалите пример и вставьте весь текст ключа со строками `-----BEGIN … PRIVATE KEY-----` и `-----END … PRIVATE KEY-----`.
3. **Next** → название `zrazu-ssl-key` → **Next** → **Next** → **Store**.
4. Откройте секрет и скопируйте **Secret ARN**.

## Шаг 2. Первый импорт в ACM (если сертификата там ещё нет)

1. **Certificate Manager → Import a certificate**.
2. **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`.
3. **Import**. Откройте сертификат и скопируйте **ARN**.
4. Подключите сертификат к сервису, если ещё не подключили: в балансировщике — **Listeners → HTTPS:443 → Edit → Default SSL/TLS certificate**; в CloudFront — **Distribution → Edit → Custom SSL certificate**.

## Шаг 3. Функция Lambda

1. **Lambda → Create function → Author from scratch**. Название `zrazu-ssl-update`, **Runtime** — Node.js 22.x, **Create function**.
2. Во вкладке **Code** откройте `index.mjs`, замените весь текст на код ниже. Вставьте свои значения в строки `CERT_ARN` (из шага 2) и `KEY_SECRET` (из шага 1). Нажмите **Deploy**.

```js file=zrazu-aws-lambda.mjs
// 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}`;
};
```

3. **Configuration → General configuration → Edit** → **Timeout** 30 секунд → **Save**.

## Шаг 4. Права функции — только на этот сертификат и этот секрет

1. **Configuration → Permissions** → щёлкните название роли (откроется IAM).
2. **Add permissions → Create inline policy** → вкладка **JSON** → вставьте текст ниже, подставив два ARN.
3. **Next** → название `zrazu-ssl-update` → **Create policy**.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Action": ["acm:DescribeCertificate", "acm:ImportCertificate"], "Resource": "ARN_СЕРТИФИКАТА" },
    { "Effect": "Allow", "Action": "secretsmanager:GetSecretValue", "Resource": "ARN_СЕКРЕТА" }
  ]
}
```

## Шаг 5. Расписание

1. В функции **Add trigger** → **EventBridge (CloudWatch Events)**.
2. **Create a new rule** → название `zrazu-ssl-daily` → **Schedule expression** → `rate(1 day)` → **Add**.

## Проверка

1. В функции вкладка **Test** → **Test** (событие любое). Результат — «новый сертификат установлен» или «сертификат не изменился».
2. В **Certificate Manager** у сертификата — новая дата **Expires**.
3. Сайт: [https://zrazu.com/tools/ssl/example.com/](https://zrazu.com/tools/ssl/example.com/). CloudFront разносит новый сертификат по своим узлам несколько минут.
4. Журнал каждого запуска — **Monitor → View CloudWatch logs**.

## Если не получилось

- `AccessDeniedException` — в политике из шага 4 не тот ARN или не тот регион.
- `HTTP 404` — ссылка отключена или заменена: создайте новую в карточке сертификата и замените строку `PULL`.
- «сертификат выпущен на другой ключ» — сертификат перевыпускали с новым ключом: обновите секрет (**Retrieve secret value → Edit**).
- `ValidationException` про ключ — ключ с паролем или не тот формат: нужен PEM без пароля.
- Task timed out — увеличьте **Timeout** (шаг 3).
