@sls-next/domain
Version:
Easily provision custom domains for:
325 lines (324 loc) • 13.1 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isMinimumProtocolVersionValid = exports.removeDomainFromCloudFrontDistribution = exports.addDomainToCloudfrontDistribution = exports.removeCloudFrontDomainDnsRecords = exports.configureDnsForCloudFrontDistribution = exports.getDomainHostedZoneId = exports.validateCertificate = exports.createCertificate = exports.getCertificateArnByDomain = exports.describeCertificateByArn = exports.getOutdatedDomains = exports.prepareSubdomains = exports.getClients = void 0;
const aws_sdk_1 = __importDefault(require("aws-sdk"));
const core_1 = require("@serverless/core");
const HOSTED_ZONE_ID = "Z2FDTNDATAQYW2";
const getClients = (credentials, region = "us-east-1") => {
if (aws_sdk_1.default && aws_sdk_1.default.config) {
aws_sdk_1.default.config.update({
maxRetries: parseInt(process.env.SLS_NEXT_MAX_RETRIES || "10"),
retryDelayOptions: { base: 200 }
});
}
const route53 = new aws_sdk_1.default.Route53({
credentials,
region
});
const acm = new aws_sdk_1.default.ACM({
credentials,
region: "us-east-1"
});
const cf = new aws_sdk_1.default.CloudFront({
credentials,
region
});
return {
route53,
acm,
cf
};
};
exports.getClients = getClients;
const prepareSubdomains = (inputs) => {
const subdomains = [];
for (const subdomain in inputs.subdomains || {}) {
const domainObj = {};
domainObj.domain = `${subdomain}.${inputs.domain}`;
if (inputs.subdomains[subdomain].url.includes("cloudfront")) {
domainObj.distributionId = inputs.subdomains[subdomain].id;
domainObj.url = inputs.subdomains[subdomain].url;
domainObj.type = "awsCloudFront";
}
subdomains.push(domainObj);
}
return subdomains;
};
exports.prepareSubdomains = prepareSubdomains;
const getOutdatedDomains = (inputs, state) => {
if (inputs.domain !== state.domain) {
return state;
}
const outdatedDomains = {
domain: state.domain,
subdomains: []
};
for (const domain of state.subdomains) {
if (!inputs.subdomains[domain.domain]) {
outdatedDomains.subdomains.push(domain);
}
}
return outdatedDomains;
};
exports.getOutdatedDomains = getOutdatedDomains;
const getDomainHostedZoneId = async (route53, domain, privateZone) => {
const params = {
DNSName: domain
};
const hostedZonesRes = await route53.listHostedZonesByName(params).promise();
const hostedZone = hostedZonesRes.HostedZones.find((zone) => zone.Config.PrivateZone === privateZone && zone.Name.includes(domain));
if (!hostedZone) {
throw Error(`Domain ${domain} was not found in your AWS account. Please purchase it from Route53 first then try again.`);
}
return hostedZone.Id.replace("/hostedzone/", "");
};
exports.getDomainHostedZoneId = getDomainHostedZoneId;
const describeCertificateByArn = async (acm, certificateArn) => {
const certificate = await acm
.describeCertificate({ CertificateArn: certificateArn })
.promise();
return certificate && certificate.Certificate
? certificate.Certificate
: null;
};
exports.describeCertificateByArn = describeCertificateByArn;
const getCertificateArnByDomain = async (acm, domain) => {
const listRes = await acm.listCertificates().promise();
for (const certificate of listRes.CertificateSummaryList) {
if (certificate.DomainName === domain && certificate.CertificateArn) {
if (domain.startsWith("www.")) {
const nakedDomain = domain.replace("www.", "");
const certDetail = await describeCertificateByArn(acm, certificate.CertificateArn);
const nakedDomainCert = certDetail.DomainValidationOptions.find(({ DomainName }) => DomainName === nakedDomain);
if (!nakedDomainCert) {
continue;
}
}
return certificate.CertificateArn;
}
}
return null;
};
exports.getCertificateArnByDomain = getCertificateArnByDomain;
const createCertificate = async (acm, domain) => {
const wildcardSubDomain = `*.${domain}`;
const params = {
DomainName: domain,
SubjectAlternativeNames: [domain, wildcardSubDomain],
ValidationMethod: "DNS"
};
const res = await acm.requestCertificate(params).promise();
return res.CertificateArn;
};
exports.createCertificate = createCertificate;
const validateCertificate = async (acm, route53, certificate, domain, domainHostedZoneId) => {
let readinessCheckCount = 16;
let statusCheckCount = 16;
let validationResourceRecord;
const checkReadiness = async function () {
if (readinessCheckCount < 1) {
throw new Error("Your newly created AWS ACM Certificate is taking a while to initialize. Please try running this component again in a few minutes.");
}
const cert = await describeCertificateByArn(acm, certificate.CertificateArn);
cert.DomainValidationOptions.forEach((option) => {
if (domain === option.DomainName) {
validationResourceRecord = option.ResourceRecord;
}
});
if (!validationResourceRecord) {
readinessCheckCount--;
await core_1.utils.sleep(5000);
return await checkReadiness();
}
};
await checkReadiness();
const checkRecordsParams = {
HostedZoneId: domainHostedZoneId,
MaxItems: "10",
StartRecordName: validationResourceRecord.Name
};
const existingRecords = await route53
.listResourceRecordSets(checkRecordsParams)
.promise();
if (!existingRecords.ResourceRecordSets.length) {
const recordParams = {
HostedZoneId: domainHostedZoneId,
ChangeBatch: {
Changes: [
{
Action: "UPSERT",
ResourceRecordSet: {
Name: validationResourceRecord.Name,
Type: validationResourceRecord.Type,
TTL: 300,
ResourceRecords: [
{
Value: validationResourceRecord.Value
}
]
}
}
]
}
};
await route53.changeResourceRecordSets(recordParams).promise();
}
const checkStatus = async function () {
if (statusCheckCount < 1) {
throw new Error("Your newly validated AWS ACM Certificate is taking a while to register as valid. Please try running this component again in a few minutes.");
}
const cert = await describeCertificateByArn(acm, certificate.CertificateArn);
if (cert.Status !== "ISSUED") {
statusCheckCount--;
await core_1.utils.sleep(10000);
return await checkStatus();
}
};
await checkStatus();
};
exports.validateCertificate = validateCertificate;
const configureDnsForCloudFrontDistribution = (route53, subdomain, domainHostedZoneId, distributionUrl, domainType, context) => {
const dnsRecordParams = {
HostedZoneId: domainHostedZoneId,
ChangeBatch: {
Changes: []
}
};
if (!subdomain.domain.startsWith("www.") || domainType !== "apex") {
dnsRecordParams.ChangeBatch.Changes.push({
Action: "UPSERT",
ResourceRecordSet: {
Name: subdomain.domain,
Type: "A",
AliasTarget: {
HostedZoneId: HOSTED_ZONE_ID,
DNSName: distributionUrl,
EvaluateTargetHealth: false
}
}
});
}
if (subdomain.domain.startsWith("www.") && domainType !== "www") {
dnsRecordParams.ChangeBatch.Changes.push({
Action: "UPSERT",
ResourceRecordSet: {
Name: subdomain.domain.replace("www.", ""),
Type: "A",
AliasTarget: {
HostedZoneId: HOSTED_ZONE_ID,
DNSName: distributionUrl,
EvaluateTargetHealth: false
}
}
});
}
context.debug("Updating Route53 DNS records with parameters:\n" +
JSON.stringify(dnsRecordParams, null, 2));
return route53.changeResourceRecordSets(dnsRecordParams).promise();
};
exports.configureDnsForCloudFrontDistribution = configureDnsForCloudFrontDistribution;
const removeCloudFrontDomainDnsRecords = async (route53, domain, domainHostedZoneId, distributionUrl, context) => {
const params = {
HostedZoneId: domainHostedZoneId,
ChangeBatch: {
Changes: [
{
Action: "DELETE",
ResourceRecordSet: {
Name: domain,
Type: "A",
AliasTarget: {
HostedZoneId: HOSTED_ZONE_ID,
DNSName: distributionUrl,
EvaluateTargetHealth: false
}
}
}
]
}
};
try {
context.debug("Updating Route53 with parameters:\n" + JSON.stringify(params, null, 2));
await route53.changeResourceRecordSets(params).promise();
}
catch (e) {
if (e.code !== "InvalidChangeBatch") {
throw e;
}
}
};
exports.removeCloudFrontDomainDnsRecords = removeCloudFrontDomainDnsRecords;
const addDomainToCloudfrontDistribution = async (cf, subdomain, certificateArn, domainMinimumProtocolVersion, domainType, defaultCloudfrontInputs, context) => {
const params = await cf
.getDistributionConfig({ Id: subdomain.distributionId })
.promise();
params.IfMatch = params.ETag;
delete params.ETag;
params.Id = subdomain.distributionId;
params.DistributionConfig.Aliases = {
Items: [subdomain.domain]
};
if (subdomain.domain.startsWith("www.")) {
if (domainType === "apex") {
params.DistributionConfig.Aliases.Items = [
`${subdomain.domain.replace("www.", "")}`
];
}
else if (domainType !== "www") {
params.DistributionConfig.Aliases.Items.push(`${subdomain.domain.replace("www.", "")}`);
}
}
params.DistributionConfig.Aliases.Quantity =
params.DistributionConfig.Aliases.Items.length;
params.DistributionConfig.ViewerCertificate = {
ACMCertificateArn: certificateArn,
SSLSupportMethod: "sni-only",
MinimumProtocolVersion: domainMinimumProtocolVersion,
Certificate: certificateArn,
CertificateSource: "acm",
...defaultCloudfrontInputs.viewerCertificate
};
context.debug("Updating CloudFront distribution with parameters:\n" +
JSON.stringify(params, null, 2));
const res = await cf.updateDistribution(params).promise();
return {
id: res.Distribution.Id,
arn: res.Distribution.ARN,
url: res.Distribution.DomainName
};
};
exports.addDomainToCloudfrontDistribution = addDomainToCloudfrontDistribution;
const removeDomainFromCloudFrontDistribution = async (cf, subdomain, domainMinimumProtocolVersion, context) => {
const params = await cf
.getDistributionConfig({ Id: subdomain.distributionId })
.promise();
params.IfMatch = params.ETag;
delete params.ETag;
params.Id = subdomain.distributionId;
params.DistributionConfig.Aliases = {
Quantity: 0,
Items: []
};
params.DistributionConfig.ViewerCertificate = {
SSLSupportMethod: "sni-only",
MinimumProtocolVersion: domainMinimumProtocolVersion
};
context.debug("Updating CloudFront distribution with parameters:\n" +
JSON.stringify(params, null, 2));
const res = await cf.updateDistribution(params).promise();
return {
id: res.Distribution.Id,
arn: res.Distribution.ARN,
url: res.Distribution.DomainName
};
};
exports.removeDomainFromCloudFrontDistribution = removeDomainFromCloudFrontDistribution;
const isMinimumProtocolVersionValid = (minimumProtocolVersion) => {
var _a;
const validMinimumProtocolVersions = /(^SSLv3$|^TLSv1$|^TLSv1.1_2016$|^TLSv1.2_2018$|^TLSv1.2_2019$|^TLSv1.2_2021$|^TLSv1_2016$)/g;
return (((_a = minimumProtocolVersion.match(validMinimumProtocolVersions)) === null || _a === void 0 ? void 0 : _a.length) === 1);
};
exports.isMinimumProtocolVersionValid = isMinimumProtocolVersionValid;