Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@serverless/aws-cloudfront

Package Overview
Dependencies
Maintainers
5
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@serverless/aws-cloudfront - npm Package Compare versions

Comparing version 2.0.0 to 3.0.0

lib/addLambdaAtEdgeToCacheBehavior.js

20

__mocks__/aws-sdk.js

@@ -22,2 +22,10 @@ const promisifyMock = (mockFn) => {

const mockCreateCloudFrontOriginAccessIdentity = jest.fn()
const mockCreateCloudFrontOriginAccessIdentityPromise = promisifyMock(
mockCreateCloudFrontOriginAccessIdentity
)
const mockPutBucketPolicy = jest.fn()
const mockPutBucketPolicyPromise = promisifyMock(mockPutBucketPolicy)
module.exports = {

@@ -28,2 +36,6 @@ mockCreateDistribution,

mockDeleteDistribution,
mockCreateCloudFrontOriginAccessIdentity,
mockPutBucketPolicy,
mockPutBucketPolicyPromise,
mockCreateDistributionPromise,

@@ -33,2 +45,3 @@ mockUpdateDistributionPromise,

mockDeleteDistributionPromise,
mockCreateCloudFrontOriginAccessIdentityPromise,

@@ -39,4 +52,9 @@ CloudFront: jest.fn(() => ({

getDistributionConfig: mockGetDistributionConfig,
deleteDistribution: mockDeleteDistribution
deleteDistribution: mockDeleteDistribution,
createCloudFrontOriginAccessIdentity: mockCreateCloudFrontOriginAccessIdentity
})),
S3: jest.fn(() => ({
putBucketPolicy: mockPutBucketPolicy
}))
}

@@ -24,4 +24,10 @@ const { createComponent, assertHasOrigin } = require('../test-utils')

it('creates distribution with custom url origin', async () => {
it('creates distribution with custom url origin and sets defaults', async () => {
await component.default({
defaults: {
ttl: 10,
'lambda@edge': {
'origin-request': 'arn:aws:lambda:us-east-1:123:function:originRequestFunction'
}
},
origins: ['https://mycustomorigin.com']

@@ -28,0 +34,0 @@ })

185

__tests__/s3-origin.test.js

@@ -6,3 +6,5 @@ const {

mockGetDistributionConfigPromise,
mockUpdateDistributionPromise
mockUpdateDistributionPromise,
mockCreateCloudFrontOriginAccessIdentityPromise,
mockPutBucketPolicy
} = require('aws-sdk')

@@ -12,3 +14,3 @@

describe('Input origin as an S3 bucket url', () => {
describe('S3 origins', () => {
let component

@@ -26,53 +28,154 @@

it('creates distribution with S3 origin', async () => {
await component.default({
origins: ['https://mybucket.s3.amazonaws.com']
describe('When origin is an S3 bucket URL', () => {
it('creates distribution', async () => {
await component.default({
origins: ['https://mybucket.s3.amazonaws.com']
})
assertHasOrigin(mockCreateDistribution, {
Id: 'mybucket',
DomainName: 'mybucket.s3.amazonaws.com',
S3OriginConfig: {
OriginAccessIdentity: ''
},
CustomHeaders: {
Quantity: 0,
Items: []
},
OriginPath: ''
})
expect(mockCreateDistribution.mock.calls[0][0]).toMatchSnapshot()
})
assertHasOrigin(mockCreateDistribution, {
Id: 'mybucket',
DomainName: 'mybucket.s3.amazonaws.com',
S3OriginConfig: {
OriginAccessIdentity: ''
},
CustomHeaders: {
Quantity: 0,
Items: []
},
OriginPath: ''
it('updates distribution', async () => {
mockGetDistributionConfigPromise.mockResolvedValueOnce({
ETag: 'etag',
DistributionConfig: {
Origins: {
Items: []
}
}
})
mockUpdateDistributionPromise.mockResolvedValueOnce({
Distribution: {
Id: 'distributionwithS3originupdated'
}
})
await component.default({
origins: ['https://mybucket.s3.amazonaws.com']
})
await component.default({
origins: ['https://anotherbucket.s3.amazonaws.com']
})
assertHasOrigin(mockUpdateDistribution, {
Id: 'anotherbucket',
DomainName: 'anotherbucket.s3.amazonaws.com'
})
expect(mockUpdateDistribution.mock.calls[0][0]).toMatchSnapshot()
})
expect(mockCreateDistribution.mock.calls[0][0]).toMatchSnapshot()
})
it('updates distribution', async () => {
mockGetDistributionConfigPromise.mockResolvedValueOnce({
ETag: 'etag',
DistributionConfig: {
Origins: {
Items: []
describe('When origin is an S3 URL only accessible via CloudFront', () => {
it('creates distribution', async () => {
mockCreateCloudFrontOriginAccessIdentityPromise.mockResolvedValueOnce({
CloudFrontOriginAccessIdentity: {
Id: 'access-identity-xyz',
S3CanonicalUserId: 's3-canonical-user-id-xyz'
}
}
})
mockUpdateDistributionPromise.mockResolvedValueOnce({
Distribution: {
Id: 'distributionwithS3originupdated'
}
})
})
await component.default({
origins: ['https://mybucket.s3.amazonaws.com']
})
await component.default({
origins: [
{
url: 'https://mybucket.s3.amazonaws.com',
private: true
}
]
})
await component.default({
origins: ['https://anotherbucket.s3.amazonaws.com']
expect(mockPutBucketPolicy).toBeCalledWith({
Bucket: 'mybucket',
Policy: expect.stringContaining('"CanonicalUser":"s3-canonical-user-id-xyz"')
})
assertHasOrigin(mockCreateDistribution, {
Id: 'mybucket',
DomainName: 'mybucket.s3.amazonaws.com',
S3OriginConfig: {
OriginAccessIdentity: 'origin-access-identity/cloudfront/access-identity-xyz'
},
CustomHeaders: {
Quantity: 0,
Items: []
},
OriginPath: ''
})
expect(mockCreateDistribution.mock.calls[0][0]).toMatchSnapshot()
})
assertHasOrigin(mockUpdateDistribution, {
Id: 'anotherbucket',
DomainName: 'anotherbucket.s3.amazonaws.com'
it('updates distribution', async () => {
mockCreateCloudFrontOriginAccessIdentityPromise.mockResolvedValue({
CloudFrontOriginAccessIdentity: {
Id: 'access-identity-xyz',
S3CanonicalUserId: 's3-canonical-user-id-xyz'
}
})
mockGetDistributionConfigPromise.mockResolvedValueOnce({
ETag: 'etag',
DistributionConfig: {
Origins: {
Items: []
}
}
})
mockUpdateDistributionPromise.mockResolvedValueOnce({
Distribution: {
Id: 'distributionwithS3originupdated'
}
})
await component.default({
origins: [
{
url: 'https://mybucket.s3.amazonaws.com',
private: true
}
]
})
await component.default({
origins: [
{
url: 'https://anotherbucket.s3.amazonaws.com',
private: true
}
]
})
expect(mockPutBucketPolicy).toBeCalledWith({
Bucket: 'anotherbucket',
Policy: expect.stringContaining('"CanonicalUser":"s3-canonical-user-id-xyz"')
})
assertHasOrigin(mockUpdateDistribution, {
Id: 'anotherbucket',
DomainName: 'anotherbucket.s3.amazonaws.com',
S3OriginConfig: {
OriginAccessIdentity: 'origin-access-identity/cloudfront/access-identity-xyz'
},
OriginPath: '',
CustomHeaders: { Items: [], Quantity: 0 }
})
expect(mockCreateDistribution.mock.calls[0][0]).toMatchSnapshot()
})
expect(mockUpdateDistribution.mock.calls[0][0]).toMatchSnapshot()
})
})

@@ -1,3 +0,5 @@

module.exports = (originId) => {
return {
const addLambdaAtEdgeToCacheBehavior = require('./addLambdaAtEdgeToCacheBehavior')
module.exports = (originId, defaults = {}) => {
const defaultCacheBehavior = {
TargetOriginId: originId,

@@ -34,3 +36,3 @@ ForwardedValues: {

SmoothStreaming: false,
DefaultTTL: 86400,
DefaultTTL: defaults.ttl || 86400,
MaxTTL: 31536000,

@@ -44,2 +46,6 @@ Compress: false,

}
addLambdaAtEdgeToCacheBehavior(defaultCacheBehavior, defaults['lambda@edge'])
return defaultCacheBehavior
}
const url = require('url')
module.exports = (origin) => {
module.exports = (origin, { originAccessIdentityId = '' }) => {
const originUrl = typeof origin === 'string' ? origin : origin.url
const { hostname } = url.parse(originUrl)
const { hostname, pathname } = url.parse(originUrl)

@@ -15,3 +15,3 @@ const originConfig = {

},
OriginPath: ''
OriginPath: pathname === '/' ? '' : pathname
}

@@ -24,3 +24,5 @@

originConfig.S3OriginConfig = {
OriginAccessIdentity: ''
OriginAccessIdentity: originAccessIdentityId
? `origin-access-identity/cloudfront/${originAccessIdentityId}`
: ''
}

@@ -27,0 +29,0 @@ } else {

const parseInputOrigins = require('./parseInputOrigins')
const getDefaultCacheBehavior = require('./getDefaultCacheBehavior')
const createOriginAccessIdentity = require('./createOriginAccessIdentity')
const grantCloudFrontBucketAccess = require('./grantCloudFrontBucketAccess')
const createCloudFrontDistribution = async (cf, inputs) => {
const servePrivateContentEnabled = (inputs) =>
inputs.origins.some((origin) => {
return origin && origin.private === true
})
const updateBucketsPolicies = async (s3, origins, s3CanonicalUserId) => {
// update bucket policies with cloudfront access
const bucketNames = origins.Items.filter((origin) => origin.S3OriginConfig).map(
(origin) => origin.Id
)
return Promise.all(
bucketNames.map((bucketName) => grantCloudFrontBucketAccess(s3, bucketName, s3CanonicalUserId))
)
}
const createCloudFrontDistribution = async (cf, s3, inputs) => {
const params = {

@@ -25,8 +43,22 @@ DistributionConfig: {

const { Origins, CacheBehaviors } = parseInputOrigins(inputs.origins)
let originAccessIdentityId
let s3CanonicalUserId
if (servePrivateContentEnabled(inputs)) {
;({ originAccessIdentityId, s3CanonicalUserId } = await createOriginAccessIdentity(cf))
}
const { Origins, CacheBehaviors } = parseInputOrigins(inputs.origins, { originAccessIdentityId })
if (s3CanonicalUserId) {
await updateBucketsPolicies(s3, Origins, s3CanonicalUserId)
}
distributionConfig.Origins = Origins
// set first origin declared as the default cache behavior
distributionConfig.DefaultCacheBehavior = getDefaultCacheBehavior(Origins.Items[0].Id)
distributionConfig.DefaultCacheBehavior = getDefaultCacheBehavior(
Origins.Items[0].Id,
inputs.defaults
)

@@ -46,3 +78,3 @@ if (CacheBehaviors) {

const updateCloudFrontDistribution = async (cf, distributionId, inputs) => {
const updateCloudFrontDistribution = async (cf, s3, distributionId, inputs) => {
// Update logic is a bit weird...

@@ -68,5 +100,22 @@ // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudFront.html#updateDistribution-property

const { Origins, CacheBehaviors } = parseInputOrigins(inputs.origins)
let s3CanonicalUserId
let originAccessIdentityId
params.DistributionConfig.DefaultCacheBehavior = getDefaultCacheBehavior(Origins.Items[0].Id)
if (servePrivateContentEnabled(inputs)) {
// presumably it's ok to call create origin access identity again
// aws api returns cached copy of what was previously created
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudFront.html#createCloudFrontOriginAccessIdentity-property
;({ originAccessIdentityId, s3CanonicalUserId } = await createOriginAccessIdentity(cf))
}
const { Origins, CacheBehaviors } = parseInputOrigins(inputs.origins, { originAccessIdentityId })
if (s3CanonicalUserId) {
await updateBucketsPolicies(s3, Origins, s3CanonicalUserId)
}
params.DistributionConfig.DefaultCacheBehavior = getDefaultCacheBehavior(
Origins.Items[0].Id,
inputs.defaults
)
params.DistributionConfig.Origins = Origins

@@ -73,0 +122,0 @@

const getOriginConfig = require('./getOriginConfig')
const getCacheBehavior = require('./getCacheBehavior')
const addLambdaAtEdgeToCacheBehavior = require('./addLambdaAtEdgeToCacheBehavior')
const validLambdaTriggers = [
'viewer-request',
'origin-request',
'origin-response',
'viewer-response'
]
module.exports = (origins) => {
module.exports = (origins, options) => {
const distributionOrigins = {

@@ -15,6 +10,10 @@ Quantity: 0,

}
let distributionCacheBehaviors
const distributionCacheBehaviors = {
Quantity: 0,
Items: []
}
for (const origin of origins) {
const originConfig = getOriginConfig(origin)
const originConfig = getOriginConfig(origin, options)

@@ -30,25 +29,4 @@ distributionOrigins.Quantity = distributionOrigins.Quantity + 1

const lambdaAtEdge = pathPatternConfig['lambda@edge'] || {}
addLambdaAtEdgeToCacheBehavior(cacheBehavior, pathPatternConfig['lambda@edge'])
Object.keys(lambdaAtEdge).forEach((eventType) => {
if (!validLambdaTriggers.includes(eventType)) {
throw new Error(
`"${eventType}" is not a valid lambda trigger. See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-cloudfront-trigger-events.html for valid event types.`
)
}
cacheBehavior.LambdaFunctionAssociations.Quantity =
cacheBehavior.LambdaFunctionAssociations.Quantity + 1
cacheBehavior.LambdaFunctionAssociations.Items.push({
EventType: eventType,
LambdaFunctionARN: lambdaAtEdge[eventType],
IncludeBody: true
})
})
distributionCacheBehaviors = {
Quantity: 0,
Items: []
}
distributionCacheBehaviors.Quantity = distributionCacheBehaviors.Quantity + 1

@@ -55,0 +33,0 @@ distributionCacheBehaviors.Items.push(cacheBehavior)

{
"name": "@serverless/aws-cloudfront",
"version": "2.0.0",
"version": "3.0.0",
"main": "./serverless.js",

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

@@ -53,2 +53,6 @@ # aws-cloudfront

enabled: true # optional
defaults: # optional
ttl: 15
lambda@edge: # added to cloudfront default cache behavior
viewer-request: arn:aws:lambda:us-east-1:123:function:myFunc:version
origins:

@@ -90,2 +94,21 @@ - https://my-bucket.s3.amazonaws.com

#### Private S3 Content
To restrict access to content that you serve from S3 you can mark as `private` your S3 origins:
```yml
# serverless.yml
distribution:
component: '@serverless/aws-cloudfront'
inputs:
origins:
- url: https://my-private-bucket.s3.amazonaws.com
private: true
```
A bucket policy will be added that grants CloudFront with access to the bucket objects. Note that it doesn't remove any existing permissions on the bucket. If users currently have permission to access the files in your bucket using Amazon S3 URLs you will need to manually remove those.
This is documented in more detail here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html
### 4. Deploy

@@ -92,0 +115,0 @@

@@ -33,10 +33,18 @@ const aws = require('aws-sdk')

const s3 = new aws.S3({
credentials: this.context.credentials.aws,
region: inputs.region
})
if (this.state.id) {
if (!equals(this.state.origins, inputs.origins)) {
if (
!equals(this.state.origins, inputs.origins) ||
!equals(this.state.defaults, inputs.defaults)
) {
this.context.debug(`Updating CloudFront distribution of ID ${this.state.id}.`)
this.state = await updateCloudFrontDistribution(cf, this.state.id, inputs)
this.state = await updateCloudFrontDistribution(cf, s3, this.state.id, inputs)
}
} else {
this.context.debug(`Creating CloudFront distribution in the ${inputs.region} region.`)
this.state = await createCloudFrontDistribution(cf, inputs)
this.state = await createCloudFrontDistribution(cf, s3, inputs)
}

@@ -46,2 +54,3 @@

this.state.origins = inputs.origins
this.state.defaults = inputs.defaults
await this.save()

@@ -48,0 +57,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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