Socket
Socket
Sign inDemoInstall

@serverless/domain

Package Overview
Dependencies
Maintainers
5
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@serverless/domain - npm Package Compare versions

Comparing version 2.0.0 to 2.0.1

2

package.json
{
"name": "@serverless/domain",
"version": "2.0.0",
"version": "2.0.1",
"main": "./serverless.js",

@@ -5,0 +5,0 @@ "publishConfig": {

# Domain
Easily provision custom domains for:
- Static websites hosted on AWS CloudFront & AWS S3 via the Website Component.
- APIs built via the Backend Component.
### Inputs
```yaml
domain:
component: '@serverless/domain'
inputs:
domain: mywebsite.com
dns:
$: ${websiteComponentInstance}
api: ${backendComponentInstance}
admin: ${anotherWebsiteComponentInstance}
```
### Set-Up
First, you must purchase your domain within Route53 manually. Then add it to the Component, as demonstrated above.
### Using With The Website Component
When used with the Website Component, the domain component will...
- Create an AWS ACM Certificate, if one does not already exists for the domain.
- Create an AWS Cloudfront Distribution that uses the AWS ACM Certificate.
- Create Records in AWS Route 53 to point your custom domain to your AWS Cloudfront Distribution.
const { Component } = require('@serverless/core')
const {
getClients,
getDomains,
prepareDomains,
getDomainHostedZoneId,
getCertificateArn,
deployS3Domain,
deployApiDomain
describeCertificateByArn,
getCertificateArnByDomain,
createCertificate,
validateCertificate,
createCloudfrontDistribution,
getCloudFrontDistributionByDomain,
invalidateCloudfrontDistribution,
deployApiDomain,
removeApiDomain,
removeApiDomainDnsRecords,
configureDnsForCloudFrontDistribution,
getApiDomainName,
removeWebsiteDomainDnsRecords
} = require('./utils')
class Domain extends Component {
/**
* Remove
*/
async default(inputs = {}) {
this.context.status('Deploying')
this.context.debug(`Starting Domain component deployment.`)
this.context.debug(`Validating inputs.`)
inputs.region = inputs.region || 'us-east-1'
if (!inputs.domain) {
throw Error(`"domain" is a required input.`)
}
const domains = getDomains(inputs)
const clients = getClients(this.context.credentials.aws, inputs.region)
// TODO: Check if domain has changed.
// On domain change, call remove for all previous state.
// Save domain
this.state.domain = inputs.domain
await this.save()
// Get AWS SDK Clients
const clients = getClients(this.context.credentials.aws)
this.context.debug(`Formatting domains and identifying cloud services being used.`)
const domains = prepareDomains(inputs)
this.context.debug(`Getting the Hosted Zone ID for the domain ${inputs.domain}.`)
const domainHostedZoneId = await getDomainHostedZoneId(clients.route53, inputs.domain)
const certificateArn = await getCertificateArn(
clients.acm,
clients.route53,
inputs.domain,
domainHostedZoneId
// // Get AWS ACM Certificate by ARN
// this.context.debug(`Checking if an AWS ACM Certificate already exists.`)
// let certificate
// if (this.state.awsAcmCertificateArn) {
// try {
// certificate = await describeCertificateByArn(clients.acm, this.state.awsAcmCertificateArn)
// this.context.debug(`AWS ACM Certificate already exists.`)
// } catch (error) {
// if (error.code === 'ResourceNotFoundException') {
// this.context.debug(
// `Couldn't find Certificate based on ARN: ${this.state.awsAcmCertificateArn}.`
// )
// } else {
// throw new Error(error)
// }
// }
// }
this.context.debug(
`Searching for an AWS ACM Certificate based on the domain: ${inputs.domain}.`
)
let certificateArn = await getCertificateArnByDomain(clients.acm, inputs.domain)
if (!certificateArn) {
this.context.debug(`No existing AWS ACM Certificates found for the domain: ${inputs.domain}.`)
this.context.debug(`Creating a new AWS ACM Certificate for the domain: ${inputs.domain}.`)
certificateArn = await createCertificate(clients.acm, inputs.domain)
}
this.context.debug(`Checking the status of AWS ACM Certificate.`)
const certificate = await describeCertificateByArn(clients.acm, certificateArn)
if (certificate.Status === 'PENDING_VALIDATION') {
this.context.debug(`AWS ACM Certificate Validation Status is "PENDING_VALIDATION".`)
this.context.debug(`Validating AWS ACM Certificate via Route53 "DNS" method.`)
await validateCertificate(
clients.acm,
clients.route53,
certificate,
inputs.domain,
domainHostedZoneId
)
this.context.log(
'Your AWS ACM Certificate has been created and is being validated via DNS. This could take up to 30 minutes since it depends on DNS propagation. Continuining deployment, but you may have to wait for DNS propagation.'
)
}
if (certificate.Status !== 'ISSUED' && certificate.Status !== 'PENDING_VALIDATION') {
// TODO: Should we auto-create a new one in this scenario?
throw new Error(
`Your AWS ACM Certificate for the domain "${inputs.domain}" has an unsupported status of: "${certificate.Status}". Please remove it manually and deploy again.`
)
}
// Save dns info to state
this.state.dns = this.state.dns && typeof this.state.dns === 'object' ? this.state.dns : {}
// Setting up domains for different services
for (const domainConfig of domains) {
if (domainConfig.type === 's3') {
await deployS3Domain(
this.state.dns[domainConfig.domain] = this.state.dns[domainConfig.domain] || {}
this.state.dns[domainConfig.domain].type = domainConfig.type
if (domainConfig.type === 'awsS3Website') {
this.state.dns[domainConfig.domain].domain = domainConfig.domain
await this.save()
this.context.debug(`Configuring domain "${domainConfig.domain}" for S3 Bucket Website`)
this.context.debug(`Checking CloudFront distribution for domain "${domainConfig.domain}"`)
let distribution = await getCloudFrontDistributionByDomain(clients.cf, domainConfig.domain)
if (!distribution) {
this.context.debug(
`CloudFront distribution for domain "${domainConfig.domain}" not found. Creating...`
)
distribution = await createCloudfrontDistribution(
clients.cf,
domainConfig,
certificate.CertificateArn
)
}
this.context.debug(`Configuring DNS for distribution "${distribution.url}".`)
await configureDnsForCloudFrontDistribution(
clients.route53,
clients.cf,
domainConfig.domain,
domainConfig,
domainHostedZoneId,
certificateArn
distribution.url
)
} else if (domainConfig.type === 'api') {
return deployApiDomain(
this.context.debug(`Invalidating CloudFront distribution ${distribution.url}`)
await invalidateCloudfrontDistribution(clients.cf, distribution.id)
this.context.debug(`Using AWS Cloudfront Distribution with URL: "${domainConfig.domain}"`)
} else if (domainConfig.type === 'awsApiGateway') {
this.state.dns[domainConfig.domain].domain = domainConfig.domain
await this.save()
// Map APIG domain to API ID and recursively retry
// if APIG domain need to be created first, or TooManyRequests error is thrown
await deployApiDomain(
clients.apig,
clients.route53,
domainConfig.domain,
domainConfig.id,
certificateArn,
domainHostedZoneId
domainConfig.apiId,
certificate.CertificateArn,
domainHostedZoneId,
this // passing instance for helpful logs during the process
)
}
// TODO: Remove unused domains that are kept in state
}
const outputs = {}
let hasRoot = false
outputs.domains = domains.map((domainConfig) => {
if (domainConfig.root) {
hasRoot = true
}
return `https://${domainConfig.domain}`
})
if (hasRoot) {
outputs.domains.unshift(`https://www.${this.state.domain}`)
}
return outputs
}
async remove() {}
/**
* Remove
*/
async remove() {
this.context.status('Deploying')
this.context.debug(`Starting Domain component removal.`)
// Get AWS SDK Clients
const clients = getClients(this.context.credentials.aws)
this.context.debug(`Getting the Hosted Zone ID for the domain ${this.state.domain}.`)
const domainHostedZoneId = await getDomainHostedZoneId(clients.route53, this.state.domain)
for (const subdomain of Object.keys(this.state.dns)) {
const domainState = this.state.dns[subdomain]
if (domainState.type === 'awsS3Website') {
this.context.debug(
`Fetching CloudFront distribution info for removal for domain ${domainState.domain}.`
)
const distribution = await getCloudFrontDistributionByDomain(clients.cf, domainState.domain)
if (distribution) {
this.context.debug(`Removing DNS records for website domain ${domainState.domain}.`)
await removeWebsiteDomainDnsRecords(
clients.route53,
domainState.domain,
domainHostedZoneId,
distribution.url
)
await removeWebsiteDomainDnsRecords(
clients.route53,
`www.${domainState.domain}`, // it'll move on if it doesn't exist
domainHostedZoneId,
distribution.url
)
}
} else if (domainState.type === 'awsApiGateway') {
this.context.debug(
`Fetching API Gateway domain ${domainState.domain} information for removal.`
)
const domainRes = await getApiDomainName(clients.apig, domainState.domain)
if (domainRes) {
this.context.debug(`Removing API Gateway domain ${domainState.domain}.`)
await removeApiDomain(clients.apig, domainState.domain)
this.context.debug(`Removing DNS records for API Gateway domain ${domainState.domain}.`)
await removeApiDomainDnsRecords(
clients.route53,
domainState.domain,
domainHostedZoneId,
domainRes.distributionHostedZoneId,
domainRes.distributionDomainName
)
}
}
}
this.state = {}
await this.save()
return {}
}
}
module.exports = Domain
const aws = require('aws-sdk')
const { utils } = require('@serverless/core')
const getClients = (credentials, region) => {
const getOutdatedDomains = (inputs, state) => {
if (inputs.domain !== state.domain) {
return state
}
for (const domain of Object.keys(state.dns)) {
if (!inputs.dns[domain])
}
}
/**
* Get Clients
* - Gets AWS SDK clients to use within this Component
*/
const getClients = (credentials, region = 'us-east-1') => {
const route53 = new aws.Route53({

@@ -32,21 +47,32 @@ credentials,

}
const getDomains = (config) => {
/**
* Prepare Domains
* - Formats component domains & identifies cloud services they're using.
*/
const prepareDomains = (config) => {
const domains = []
for (const domain of config.dns || []) {
for (const subdomain in config.dns || []) {
const domainObj = {}
if (domain.domain === 'root') {
if (subdomain === '$') {
domainObj.domain = config.domain
domainObj.root = true
} else {
domainObj.domain = `${domain.domain}.${config.domain}`
domainObj.domain = `${subdomain}.${config.domain}`
}
if (domain.target.url.includes('api')) {
const id = domain.target.url.split('.')[0].split('//')[1]
domainObj.id = id
domainObj.type = 'api'
if (config.dns[subdomain].url.includes('s3')) {
domainObj.type = 'awsS3Website'
// Get S3 static hosting endpoint of existing bucket to use w/ CloudFront.
// The bucket name must be DNS compliant.
domainObj.s3BucketName = config.dns[subdomain].url.replace('http://', '').split('.')[0]
}
if (domain.target.url.includes('s3')) {
domainObj.type = 's3'
// Check if referenced Component is using AWS API Gateway...
if (config.dns[subdomain].url.includes('execute-api')) {
domainObj.apiId = config.dns[subdomain].url.split('.')[0].split('//')[1]
domainObj.type = 'awsApiGateway'
}

@@ -60,14 +86,15 @@

const getSecondLevelDomain = (domain) => {
return domain
.split('.')
.slice(domain.split('.').length - 2)
.join('.')
}
/**
* Get Domain Hosted Zone ID
* - Every Domain on Route53 always has a Hosted Zone w/ 2 Record Sets.
* - These Record Sets are: "Name Servers (NS)" & "Start of Authority (SOA)"
* - These don't need to be created and SHOULD NOT be modified.
*/
const getDomainHostedZoneId = async (route53, secondLevelDomain) => {
const getDomainHostedZoneId = async (route53, domain) => {
const hostedZonesRes = await route53.listHostedZonesByName().promise()
const hostedZone = hostedZonesRes.HostedZones.find(
(zone) => zone.Name.includes(secondLevelDomain) // Name has a period at the end, which is why we're using includes rather than equals
// Name has a period at the end, so we're using includes rather than equals
(zone) => zone.Name.includes(domain)
)

@@ -77,23 +104,186 @@

throw Error(
`Domain ${secondLevelDomain} was not found in your AWS account. Please purchase it from Route53 first then try again.`
`Domain ${domain} was not found in your AWS account. Please purchase it from Route53 first then try again.`
)
}
return hostedZone.Id.replace('/hostedzone/', '')
return hostedZone.Id.replace('/hostedzone/', '') // hosted zone id is always prefixed with this :(
}
const createApigDomain = async (apig, route53, domain, certificateArn, domainHostedZoneId) => {
/**
* Describe Certificate By Arn
* - Describe an AWS ACM Certificate by its ARN
*/
const describeCertificateByArn = async (acm, certificateArn) => {
const certificate = await acm.describeCertificate({ CertificateArn: certificateArn }).promise()
return certificate && certificate.Certificate ? certificate.Certificate : null
}
/**
* Get Certificate Arn By Domain
* - Gets an AWS ACM Certificate by a specified domain or return null
*/
const getCertificateArnByDomain = async (acm, domain) => {
const listRes = await acm.listCertificates().promise()
const certificate = listRes.CertificateSummaryList.find((cert) => cert.DomainName === domain)
return certificate && certificate.CertificateArn ? certificate.CertificateArn : null
}
/**
* Create Certificate
* - Creates an AWS ACM Certificate for the specified domain
*/
const createCertificate = async (acm, domain) => {
const wildcardSubDomain = `*.${domain}`
const params = {
domainName: domain,
certificateArn: certificateArn,
securityPolicy: 'TLS_1_2',
endpointConfiguration: {
types: ['EDGE']
DomainName: domain,
SubjectAlternativeNames: [domain, wildcardSubDomain],
ValidationMethod: 'DNS'
}
return acm.requestCertificate(params).promise().CertificateArn
}
/**
* Validate Certificate
* - Validate an AWS ACM Certificate via the "DNS" method
*/
const validateCertificate = async (acm, route53, certificate, domain, domainHostedZoneId) => {
let readinessCheckCount = 10
let statusCheckCount = 10
let validationResourceRecord
/**
* Check Readiness
* - Newly Created AWS ACM Certificates may not yet have the info needed to validate it
* - Specifically, the "ResourceRecord" object in the Domain Validation Options
* - Ensure this exists.
*/
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)
// Find root domain validation option resource record
cert.DomainValidationOptions.forEach((option) => {
if (domain === option.DomainName) {
validationResourceRecord = option.ResourceRecord
}
})
if (!validationResourceRecord) {
readinessCheckCount--
await utils.sleep(5000)
return await checkReadiness()
}
}
const apigDomainName = await apig.createDomainName(params).promise()
await checkReadiness()
const checkRecordsParams = {
HostedZoneId: domainHostedZoneId,
MaxItems: '10',
StartRecordName: validationResourceRecord.Name
}
// Check if the validation resource record sets already exist.
// This might be the case if the user is trying to deploy multiple times while validation is occurring.
const existingRecords = await route53.listResourceRecordSets(checkRecordsParams).promise()
if (!existingRecords.ResourceRecordSets.length) {
// Create CNAME record for DNS validation check
// NOTE: It can take 30 minutes or longer for DNS propagation so validation can complete, just continue on and don't wait for this...
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()
}
/**
* Check Validated Status
* - Newly Validated AWS ACM Certificates may not yet show up as valid
* - This gives them some time to update their status.
*/
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 utils.sleep(10000)
return await checkStatus()
}
}
await checkStatus()
}
/**
* Create AWS API Gateway Domain
*/
const createDomainInApig = async (apig, domain, certificateArn) => {
try {
const params = {
domainName: domain,
certificateArn: certificateArn,
securityPolicy: 'TLS_1_2',
endpointConfiguration: {
types: ['EDGE']
}
}
const res = await apig.createDomainName(params).promise()
return res
} catch (e) {
if (e.code === 'TooManyRequestsException') {
await utils.sleep(2000)
return createDomainInApig(apig, domain, certificateArn)
}
throw e
}
}
/**
* Configure DNS for the created API Gateway domain
*/
const configureDnsForApigDomain = async (
route53,
domain,
hostedZoneId,
distributionHostedZoneId,
distributionDomainName
) => {
const dnsRecord = {
HostedZoneId: domainHostedZoneId,
HostedZoneId: hostedZoneId,
ChangeBatch: {

@@ -107,4 +297,4 @@ Changes: [

AliasTarget: {
HostedZoneId: apigDomainName.distributionHostedZoneId,
DNSName: apigDomainName.distributionDomainName,
HostedZoneId: distributionHostedZoneId,
DNSName: distributionDomainName,
EvaluateTargetHealth: false

@@ -121,191 +311,349 @@ }

const deployS3Domain = async (route53, cf, domain, domainHostedZoneId, certificateArn) => {
/**
* Map API Gateway API to the created API Gateway Domain
*/
const mapDomainToApi = async (apig, domain, apiId) => {
try {
const params = {
DistributionConfig: {
CallerReference: String(Date.now()),
Aliases: {
Quantity: 2,
Items: [domain, `www.${domain}`]
},
DefaultRootObject: 'index.html',
Origins: {
Quantity: 1,
Items: [
{
Id: `S3-${domain}`,
DomainName: `${domain}.s3.amazonaws.com`,
OriginPath: '',
CustomHeaders: {
Quantity: 0,
Items: []
},
S3OriginConfig: {
OriginAccessIdentity: ''
}
domainName: domain,
restApiId: apiId,
basePath: '(none)',
stage: 'production'
}
return apig.createBasePathMapping(params).promise()
} catch (e) {
if (e.code === 'TooManyRequestsException') {
await utils.sleep(2000)
return mapDomainToApi(apig, domain, apiId)
}
throw e
}
}
const deployApiDomain = async (
apig,
route53,
domain,
apiId,
certificateArn,
domainHostedZoneId,
that
) => {
try {
that.context.debug(`Mapping domain ${domain} to API ID ${apiId}`)
await mapDomainToApi(apig, domain, apiId)
} catch (e) {
if (e.message === 'Invalid domain name identifier specified') {
that.context.debug(`Domain ${domain} not found in API Gateway. Creating...`)
const res = await createDomainInApig(apig, domain, certificateArn)
that.state.dns[domain].distributionHostedZoneId = res.distributionHostedZoneId
that.state.dns[domain].distributionDomainName = res.distributionDomainName
await that.save()
that.context.debug(`Configuring DNS for API Gateway domain ${domain}.`)
await configureDnsForApigDomain(
route53,
domain,
domainHostedZoneId,
res.distributionHostedZoneId,
res.distributionDomainName
)
// retry domain deployment now that domain is created
return deployApiDomain(apig, route53, domain, apiId, certificateArn, domainHostedZoneId, that)
}
if (e.message === 'Base path already exists for this domain name') {
that.context.debug(`Domain ${domain} is already mapped to API ID ${apiId}.`)
return
}
throw new Error(e)
}
}
/**
* Get CloudFront Distribution using a domain name
*/
const getCloudFrontDistributionByDomain = async (cf, domain) => {
const listRes = await cf.listDistributions({}).promise()
const distribution = listRes.DistributionList.Items.find((dist) =>
dist.Aliases.Items.includes(domain)
)
if (distribution) {
return {
arn: distribution.ARN,
id: distribution.Id,
url: distribution.DomainName
}
}
return null
}
/**
* Configure DNS records for a distribution domain
*/
const configureDnsForCloudFrontDistribution = async (
route53,
domainConfig,
domainHostedZoneId,
distributionUrl
) => {
const dnsRecordParams = {
HostedZoneId: domainHostedZoneId,
ChangeBatch: {
Changes: [
{
Action: 'UPSERT',
ResourceRecordSet: {
Name: domainConfig.domain,
Type: 'A',
AliasTarget: {
HostedZoneId: 'Z2FDTNDATAQYW2', // this is a constant that you can get from here https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
DNSName: distributionUrl,
EvaluateTargetHealth: false
}
]
},
OriginGroups: {
Quantity: 0,
Items: []
},
DefaultCacheBehavior: {
TargetOriginId: `S3-${domain}`,
ForwardedValues: {
QueryString: false,
Cookies: {
Forward: 'none'
},
Headers: {
}
}
]
}
}
if (domainConfig.root) {
dnsRecordParams.ChangeBatch.Changes.push({
Action: 'UPSERT',
ResourceRecordSet: {
Name: `www.${domainConfig.domain}`,
Type: 'A',
AliasTarget: {
HostedZoneId: 'Z2FDTNDATAQYW2', // this is a constant that you can get from here https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
DNSName: distributionUrl,
EvaluateTargetHealth: false
}
}
})
}
return route53.changeResourceRecordSets(dnsRecordParams).promise()
}
/**
* Create Cloudfront Distribution
*/
const createCloudfrontDistribution = async (cf, domainConfig, certificateArn) => {
const params = {
DistributionConfig: {
CallerReference: String(Date.now()),
Aliases: {
Quantity: 1,
Items: [domainConfig.domain]
},
DefaultRootObject: 'index.html',
Origins: {
Quantity: 1,
Items: [
{
Id: `S3-${domainConfig.s3BucketName}`,
DomainName: `${domainConfig.s3BucketName}.s3.amazonaws.com`,
OriginPath: '',
CustomHeaders: {
Quantity: 0,
Items: []
},
QueryStringCacheKeys: {
Quantity: 0,
Items: []
S3OriginConfig: {
OriginAccessIdentity: ''
}
}
]
},
OriginGroups: {
Quantity: 0,
Items: []
},
DefaultCacheBehavior: {
TargetOriginId: `S3-${domainConfig.s3BucketName}`,
ForwardedValues: {
QueryString: false,
Cookies: {
Forward: 'none'
},
TrustedSigners: {
Enabled: false,
Headers: {
Quantity: 0,
Items: []
},
ViewerProtocolPolicy: 'redirect-to-https',
MinTTL: 0,
AllowedMethods: {
Quantity: 2,
Items: ['HEAD', 'GET'],
CachedMethods: {
Quantity: 2,
Items: ['HEAD', 'GET']
}
},
SmoothStreaming: false,
DefaultTTL: 86400,
MaxTTL: 31536000,
Compress: false,
LambdaFunctionAssociations: {
QueryStringCacheKeys: {
Quantity: 0,
Items: []
},
FieldLevelEncryptionId: ''
}
},
CacheBehaviors: {
TrustedSigners: {
Enabled: false,
Quantity: 0,
Items: []
},
CustomErrorResponses: {
ViewerProtocolPolicy: 'redirect-to-https',
MinTTL: 0,
AllowedMethods: {
Quantity: 2,
Items: ['HEAD', 'GET'],
CachedMethods: {
Quantity: 2,
Items: ['HEAD', 'GET']
}
},
SmoothStreaming: false,
DefaultTTL: 86400,
MaxTTL: 31536000,
Compress: false,
LambdaFunctionAssociations: {
Quantity: 0,
Items: []
},
Comment: '',
Logging: {
Enabled: false,
IncludeCookies: false,
Bucket: '',
Prefix: ''
},
PriceClass: 'PriceClass_All',
Enabled: true,
ViewerCertificate: {
ACMCertificateArn: certificateArn,
SSLSupportMethod: 'sni-only',
MinimumProtocolVersion: 'TLSv1.1_2016',
Certificate: certificateArn,
CertificateSource: 'acm'
},
Restrictions: {
GeoRestriction: {
RestrictionType: 'none',
Quantity: 0,
Items: []
}
},
WebACLId: '',
HttpVersion: 'http2',
IsIPV6Enabled: true
}
FieldLevelEncryptionId: ''
},
CacheBehaviors: {
Quantity: 0,
Items: []
},
CustomErrorResponses: {
Quantity: 0,
Items: []
},
Comment: '',
Logging: {
Enabled: false,
IncludeCookies: false,
Bucket: '',
Prefix: ''
},
PriceClass: 'PriceClass_All',
Enabled: true,
ViewerCertificate: {
ACMCertificateArn: certificateArn,
SSLSupportMethod: 'sni-only',
MinimumProtocolVersion: 'TLSv1.1_2016',
Certificate: certificateArn,
CertificateSource: 'acm'
},
Restrictions: {
GeoRestriction: {
RestrictionType: 'none',
Quantity: 0,
Items: []
}
},
WebACLId: '',
HttpVersion: 'http2',
IsIPV6Enabled: true
}
}
const res = await cf.createDistribution(params).promise()
if (domainConfig.root) {
params.DistributionConfig.Aliases.Quantity = 2
params.DistributionConfig.Aliases.Items.push(`www.${domainConfig.domain}`)
}
const distributionUrl = res.Distribution.DomainName
const res = await cf.createDistribution(params).promise()
const dnsRecordParams = {
HostedZoneId: domainHostedZoneId,
ChangeBatch: {
Changes: [
{
Action: 'UPSERT',
ResourceRecordSet: {
Name: domain,
Type: 'A',
AliasTarget: {
HostedZoneId: 'Z2FDTNDATAQYW2', // this is a constant that you can get from here https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
DNSName: distributionUrl,
EvaluateTargetHealth: false
}
}
}
]
return {
id: res.Distribution.Id,
arn: res.Distribution.ARN,
url: res.Distribution.DomainName
}
// try {
// } catch (e) {
// if (e.code !== 'CNAMEAlreadyExists') {
// throw e
// }
// }
}
/**
* Invalidate Cloudfront Distribution
*/
const invalidateCloudfrontDistribution = async (cf, distributionId) => {
const params = {
DistributionId: distributionId,
InvalidationBatch: {
CallerReference: String(Date.now()),
Paths: {
Quantity: 1,
Items: ['/index.html']
}
}
await route53.changeResourceRecordSets(dnsRecordParams).promise()
} catch (e) {
if (e.code !== 'CNAMEAlreadyExists') {
throw e
}
}
await cf.createInvalidation(params).promise()
}
const deployApiDomain = async (
apig,
/**
* Remove AWS S3 Website DNS Records
*/
const removeWebsiteDomainDnsRecords = async (
route53,
domain,
apiId,
certificateArn,
domainHostedZoneId
domainHostedZoneId,
distributionUrl
) => {
const params = {
HostedZoneId: domainHostedZoneId,
ChangeBatch: {
Changes: [
{
Action: 'DELETE',
ResourceRecordSet: {
Name: domain,
Type: 'A',
AliasTarget: {
HostedZoneId: 'Z2FDTNDATAQYW2', // this is a constant that you can get from here https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
DNSName: distributionUrl,
EvaluateTargetHealth: false
}
}
}
]
}
}
try {
await apig
.createBasePathMapping({
domainName: domain,
restApiId: apiId,
basePath: '(none)',
stage: 'production'
})
.promise()
await route53.changeResourceRecordSets(params).promise()
} catch (e) {
if (e.message === 'Invalid domain name identifier specified') {
await createApigDomain(apig, route53, domain, certificateArn, domainHostedZoneId)
// avoiding "too many requests error"
await utils.sleep(1000)
return deployApiDomain(apig, route53, domain, apiId, certificateArn, domainHostedZoneId)
if (e.code !== 'InvalidChangeBatch') {
throw e
}
if (e.message === 'Base path already exists for this domain name') {
return
}
throw new Error(e)
}
}
const createCertificate = async (acm, route53, secondLevelDomain, domainHostedZoneId) => {
/**
* Remove API Gateway Domain
*/
const removeApiDomain = async (apig, domain) => {
const params = {
DomainName: secondLevelDomain,
SubjectAlternativeNames: `*.${secondLevelDomain}`,
ValidationMethod: 'DNS'
domainName: domain
}
const certificate = await acm.requestCertificate(params).promise()
return apig.deleteDomainName(params).promise()
}
const certificateDnsRecord = (await acm
.describeCertificate({ CertificateArn: certificate.CertificateArn })
.promise()).DomainValidationOptions.ResourceRecord
/**
* Remove API Gateway Domain DNS Records
*/
const recordParams = {
const removeApiDomainDnsRecords = async (
route53,
domain,
domainHostedZoneId,
distributionHostedZoneId,
distributionDomainName
) => {
const dnsRecord = {
HostedZoneId: domainHostedZoneId,

@@ -315,11 +663,11 @@ ChangeBatch: {

{
Action: 'UPSERT',
Action: 'DELETE',
ResourceRecordSet: {
Name: certificateDnsRecord.Name,
Type: certificateDnsRecord.Type,
ResourceRecords: [
{
Value: certificateDnsRecord.Value
}
]
Name: domain,
Type: 'A',
AliasTarget: {
HostedZoneId: distributionHostedZoneId,
DNSName: distributionDomainName,
EvaluateTargetHealth: false
}
}

@@ -331,34 +679,42 @@ }

await route53.changeResourceRecordSets(recordParams).promise()
return certificate
return route53.changeResourceRecordSets(dnsRecord).promise()
}
const getCertificateArn = async (acm, route53, secondLevelDomain, domainHostedZoneId) => {
const listRes = await acm
.listCertificates({
CertificateStatuses: ['ISSUED']
})
.promise()
/**
* Fetch API Gateway Domain Information
*/
let certificate = listRes.CertificateSummaryList.find(
(cert) => cert.DomainName === secondLevelDomain
)
if (!certificate) {
certificate = await createCertificate(acm, route53, secondLevelDomain, domainHostedZoneId)
const getApiDomainName = async (apig, domain) => {
try {
return apig.getDomainName({ domainName: domain }).promise()
} catch (e) {
if (e.code === 'NotFoundException:') {
return null
}
}
return certificate.CertificateArn
}
/**
* Exports
*/
module.exports = {
getClients,
getDomains,
getSecondLevelDomain,
prepareDomains,
describeCertificateByArn,
getCertificateArnByDomain,
createCertificate,
validateCertificate,
getDomainHostedZoneId,
deployS3Domain,
createCloudfrontDistribution,
invalidateCloudfrontDistribution,
mapDomainToApi,
createDomainInApig,
configureDnsForApigDomain,
deployApiDomain,
createCertificate,
getCertificateArn
removeApiDomain,
removeApiDomainDnsRecords,
getCloudFrontDistributionByDomain,
configureDnsForCloudFrontDistribution,
getApiDomainName,
removeWebsiteDomainDnsRecords
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc