New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@collaborne/custom-cloudformation-resources

Package Overview
Dependencies
Maintainers
4
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@collaborne/custom-cloudformation-resources - npm Package Compare versions

Comparing version 0.4.0 to 0.5.0

9

dist/acm-certificate/index.d.ts
import type { SchemaType } from '@collaborne/json-schema-to-type';
import { CustomResource, Response } from '../custom-resource';
import { ContinuationRequired, CustomResource, Response } from '../custom-resource';
import { Logger } from '../logger';

@@ -65,9 +65,9 @@ declare const SCHEMA: {

}
export declare class ACMCertificate extends CustomResource<ResourceAttributes, typeof SCHEMA> {
export declare class ACMCertificate extends CustomResource<ResourceAttributes, typeof SCHEMA, Response<ResourceAttributes>> {
private acm;
private route53;
constructor(logicalResourceId: string, logger: Logger);
createResource(physicalResourceId: string, params: SchemaType<typeof SCHEMA>): Promise<Response<ResourceAttributes>>;
createResource(physicalResourceId: string, params: SchemaType<typeof SCHEMA>, continuationAttributes: Response<ResourceAttributes>): Promise<Response<ResourceAttributes> | ContinuationRequired<Response<ResourceAttributes>>>;
deleteResource(physicalResourceId: string, params: SchemaType<typeof SCHEMA>): Promise<Response<ResourceAttributes>>;
updateResource(physicalResourceId: string, params: SchemaType<typeof SCHEMA>, oldParams: unknown): Promise<Response<ResourceAttributes>>;
updateResource(physicalResourceId: string, params: SchemaType<typeof SCHEMA>, oldParams: unknown, continuationAttributes: Response<ResourceAttributes>): Promise<Response<ResourceAttributes> | ContinuationRequired<Response<ResourceAttributes>>>;
private findCertificateArn;

@@ -80,3 +80,4 @@ /**

private findChangedAttributes;
protected maybeContinuation(response: Response<ResourceAttributes>): Promise<Response<ResourceAttributes> | ContinuationRequired<Response<ResourceAttributes>>>;
}
export {};

@@ -86,8 +86,15 @@ "use strict";

}
async createResource(physicalResourceId, params) {
const attributes = await this.createCertificate(params);
return {
physicalResourceId: `${physicalResourceId}/${attributes.CertificateId}`,
attributes,
};
async createResource(physicalResourceId, params, continuationAttributes) {
let response;
if (continuationAttributes) {
response = continuationAttributes;
}
else {
const attributes = await this.createCertificate(params);
response = {
physicalResourceId: `${physicalResourceId}/${attributes.CertificateId}`,
attributes,
};
}
return this.maybeContinuation(response);
}

@@ -117,3 +124,6 @@ async deleteResource(physicalResourceId, params) {

}
async updateResource(physicalResourceId, params, oldParams) {
async updateResource(physicalResourceId, params, oldParams, continuationAttributes) {
if (continuationAttributes) {
return this.maybeContinuation(continuationAttributes);
}
const { CertificateTransparencyLoggingPreference: ctLoggingPreference, Tags: tags = [], } = params;

@@ -206,6 +216,6 @@ const changedAttributes = this.findChangedAttributes(params, oldParams);

this.logger.log(`Replaced certificate ${physicalResourceId} with ${newPhysicalResourceId}`);
return {
return this.maybeContinuation({
physicalResourceId: newPhysicalResourceId,
attributes: newAttributes,
};
});
}

@@ -354,7 +364,2 @@ }

}
// In theory we could proceed, as we have the ARN -- but this will just lead to failures downstream
// as the certificate isn't necessarily issued yet.
await this.acm
.waitFor('certificateValidated', { CertificateArn: certificateArn })
.promise();
return {

@@ -384,4 +389,34 @@ Arn: certificateArn,

}
async maybeContinuation(response) {
var _a, _b;
// Initially wee have the id in the response, so we simply check now whether the certificate is valid.
// If it is, great, return the response. Otherwise stuff the whole response into continuationAttributes, and
// try again in a minute or two.
const certificateArn = (_a = response.attributes) === null || _a === void 0 ? void 0 : _a.Arn;
if (!certificateArn) {
throw new Error('Missing certificate ARN');
}
const { Certificate: certificate } = await this.acm
.describeCertificate({ CertificateArn: certificateArn })
.promise();
if (!certificate) {
throw new Error(`Cannot find certificate ${certificateArn}`);
}
if (certificate.Status === 'ISSUED') {
this.logger.log('Certificate is ISSUED, stopping continuations');
return response;
}
else if (certificate.Status === 'PENDING_VALIDATION') {
this.logger.log(`Certificate is PENDING_VALIDATION, requesting continuation`);
return {
continuationAfter: Number((_b = process.env.RETRY_INTERVAL_SECONDS) !== null && _b !== void 0 ? _b : '300'),
continuationAttributes: response,
};
}
else {
throw new Error(`Certificate is in invalid status ${certificate.Status}`);
}
}
}
exports.ACMCertificate = ACMCertificate;
//# sourceMappingURL=index.js.map

@@ -8,17 +8,31 @@ import type { ObjectSchema, SchemaType } from '@collaborne/json-schema-to-type';

}
export declare abstract class CustomResource<ResourceAttributes extends unknown, S extends ObjectSchema> {
/**
* Response returned by the resource methods to indicate that long-running continuation is needed
*/
export interface ContinuationRequired<ContinuationAttributes extends unknown> {
/** Number of seconds after which to retry the function */
continuationAfter: number;
/** Additional properties that should be provided in the continuation invocation */
continuationAttributes: ContinuationAttributes;
}
declare type ContinuedCustomResourceRequest<ContinuationAttributes> = CustomResourceRequest & {
ContinuationAttributes: ContinuationAttributes;
};
export declare abstract class CustomResource<ResourceAttributes extends unknown, S extends ObjectSchema, ContinuationAttributes extends unknown = unknown> {
protected readonly schema: S;
protected readonly logicalResourceId: string;
protected readonly logger: Logger;
private static CW_EVENTS_TARGET_ID;
private cwEvents;
private requestQueue;
constructor(schema: S, logicalResourceId: string, logger: Logger);
abstract createResource(physicalResourceId: string, params: SchemaType<S>): Promise<Response<ResourceAttributes>>;
abstract deleteResource(physicalResourceId: string, params: SchemaType<S>): Promise<Response<ResourceAttributes>>;
abstract updateResource(physicalResourceId: string, params: SchemaType<S>, oldParams: unknown): Promise<Response<ResourceAttributes>>;
handleRequest(request: CustomResourceRequest): Promise<ResponseStatus>;
protected processRequest(request: CustomResourceRequest): Promise<{
abstract createResource(physicalResourceId: string, params: SchemaType<S>, continuationAttributes?: ContinuationAttributes): Promise<Response<ResourceAttributes> | ContinuationRequired<ContinuationAttributes>>;
abstract deleteResource(physicalResourceId: string, params: SchemaType<S>, continuationAttributes?: ContinuationAttributes): Promise<Response<ResourceAttributes> | ContinuationRequired<ContinuationAttributes>>;
abstract updateResource(physicalResourceId: string, params: SchemaType<S>, oldParams: unknown, continuationAttributes?: ContinuationAttributes): Promise<Response<ResourceAttributes> | ContinuationRequired<ContinuationAttributes>>;
handleRequest(request: CustomResourceRequest | ContinuedCustomResourceRequest<ContinuationAttributes>): Promise<ResponseStatus>;
protected processRequest(request: CustomResourceRequest | ContinuedCustomResourceRequest<ContinuationAttributes>): Promise<{
status: ResponseStatus;
statusReason?: string;
response: Response<ResourceAttributes>;
}>;
private getContinuationRuleName;
}
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomResource = void 0;
const aws_sdk_1 = require("aws-sdk");
const crc_1 = require("crc");
const cfn_response_1 = require("./cfn-response");
function isContinuationRequired(response) {
return typeof response === 'object' && 'continuationAfter' in response;
}
function isContinuedCustomResourceRequest(request) {
return ('ContinuationAttributes' in request &&
typeof request.ContinuationAttributes === 'object');
}
class CustomResource {

@@ -10,2 +19,3 @@ constructor(schema, logicalResourceId, logger) {

this.logger = logger;
this.cwEvents = new aws_sdk_1.CloudWatchEvents({ apiVersion: '2015-10-07' });
this.requestQueue = [];

@@ -48,2 +58,7 @@ }

[request.StackId, request.LogicalResourceId, request.RequestId].join('/');
let continuationAttributes;
if (isContinuedCustomResourceRequest(request)) {
this.logger.log('Request is a continuation of an earlier request');
continuationAttributes = request.ContinuationAttributes;
}
let status = 'FAILED';

@@ -56,6 +71,6 @@ let statusReason;

case 'Create':
response = await this.createResource(physicalResourceId, properties);
response = await this.createResource(physicalResourceId, properties, continuationAttributes);
break;
case 'Delete':
response = await this.deleteResource(physicalResourceId, properties);
response = await this.deleteResource(physicalResourceId, properties, continuationAttributes);
break;

@@ -67,3 +82,3 @@ case 'Update':

const { ServiceToken: _ignoredOldServiceToken, ...oldProperties } = request.OldResourceProperties;
response = await this.updateResource(physicalResourceId, properties, oldProperties);
response = await this.updateResource(physicalResourceId, properties, oldProperties, continuationAttributes);
}

@@ -78,15 +93,99 @@ break;

}
const responsePhysicalResourceId = (response === null || response === void 0 ? void 0 : response.physicalResourceId) || physicalResourceId;
await cfn_response_1.send(request, status, statusReason, responsePhysicalResourceId, response === null || response === void 0 ? void 0 : response.attributes);
if (isContinuationRequired(response)) {
// Schedule the invocation of the function again, with the additional attributes from
// the response
const { continuationAfter, continuationAttributes } = response;
const continuationRuleName = this.getContinuationRuleName(request);
// XXX: Magic! Needs to be documented that this can be set to an ARN of the role to use.
const ruleRoleArn = process.env.CW_EVENTS_CONTINUATION_RULE_ROLE_ARN;
// Ideally we want to put the target for the continuation first so that we don't miss
// our own goal, but CWE doesn't work this way.
// What does work however is to create the rule with a schedule expression pointing to a
// day in the past, keep it "DISABLED", then add the target, and then update the rule to
// enable it with a suitably-in-the-future expression.
// Note that if the rule already exists this will similarly first disable it; as the rule name
// and target ID are constant over the life-time of the rule this should all be idempotent.
const prepPutRuleParams = {
Name: continuationRuleName,
RoleArn: ruleRoleArn,
ScheduleExpression: `cron(30 7 19 6 ? 2018)`,
State: 'DISABLED',
};
await this.cwEvents.putRule(prepPutRuleParams).promise();
const putTargetsParams = {
Rule: continuationRuleName,
Targets: [
{
Arn: request.ServiceToken,
Id: CustomResource.CW_EVENTS_TARGET_ID,
Input: JSON.stringify({
...request,
ContinuationAttributes: continuationAttributes,
}),
RoleArn: process.env.CW_EVENTS_CONTINUATION_TARGET_ROLE_ARN,
},
],
};
await this.cwEvents.putTargets(putTargetsParams).promise();
const now = new Date();
const when = new Date(now.getTime() + continuationAfter * 1000);
// Round up to the next full minute, as we won't be getting scheduling if the time isn't in the future.
const cronExpression = `${Math.max(now.getMinutes() + 1, when.getMinutes())} ${when.getHours()} ${when.getDate()} ${when.getMonth() + 1} ? ${when.getFullYear()}`;
this.logger.log(`Scheduling continuation using CWE rule ${continuationRuleName} after ${continuationAfter}s (at ${cronExpression}) `);
const schedulePutRuleParams = {
Name: continuationRuleName,
RoleArn: ruleRoleArn,
ScheduleExpression: `cron(${cronExpression})`,
State: 'ENABLED',
};
await this.cwEvents.putRule(schedulePutRuleParams).promise();
}
else {
// A "definite" status, so write that into the response document
const responsePhysicalResourceId = (response === null || response === void 0 ? void 0 : response.physicalResourceId) || physicalResourceId;
await cfn_response_1.send(request, status, statusReason, responsePhysicalResourceId, response === null || response === void 0 ? void 0 : response.attributes);
// If there are continuation attributes in the request, we know that this was a continuation
// and therefore we now want to remove the rule.
if (continuationAttributes) {
const continuationRuleName = this.getContinuationRuleName(request);
this.logger.log(`Cleaning up CWE rule ${continuationRuleName}`);
try {
await this.cwEvents
.removeTargets({
Rule: continuationRuleName,
Ids: [CustomResource.CW_EVENTS_TARGET_ID],
})
.promise();
await this.cwEvents
.deleteRule({
Name: continuationRuleName,
})
.promise();
}
catch (err) {
// Best effort, didn't work, bad luck.
// Given that the rule is a single date, this should merely produce garbage in CW Events, but not produce
// any other side-effects.
this.logger.warn(`Cannot remove continuation rule ${continuationRuleName}: ${err.message}`);
}
}
}
return {
status,
statusReason,
response: {
physicalResourceId: responsePhysicalResourceId,
attributes: response === null || response === void 0 ? void 0 : response.attributes,
},
};
}
getContinuationRuleName(request) {
// Rule name, can be at most 64 characters and has a limited range of valid characters
// We also know the rule must be unique for this request, so we try to cram as much as we can
// into it.
// StackId is something like "arn:aws:cloudformation:us-west-2:123456789012:stack/stack-name/guid", for our purposes
// we care only about the stack name.
const [, stackName] = request.StackId.split(/:/)[5].split(/\//);
const safeRequestId = crc_1.crc32(request.RequestId).toString(16);
const continuationRuleName = `Continuation-${stackName}-${request.LogicalResourceId}`;
return `${continuationRuleName.slice(0, 55)}-${safeRequestId}`;
}
}
exports.CustomResource = CustomResource;
CustomResource.CW_EVENTS_TARGET_ID = 'CustomResource';
//# sourceMappingURL=custom-resource.js.map

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

import { CognitoIdentityServiceProvider } from 'aws-sdk';
import { SchemaType } from '@collaborne/json-schema-to-type';
import { CustomResource, Response } from '../custom-resource';
import { Logger } from '../logger';
import { DomainDescriptionType } from 'aws-sdk/clients/cognitoidentityserviceprovider';
declare const SCHEMA: {

@@ -14,3 +14,3 @@ type: "object";

};
declare type ResourceAttributes = Pick<DomainDescriptionType, 'CloudFrontDistribution'>;
declare type ResourceAttributes = Pick<CognitoIdentityServiceProvider.DomainDescriptionType, 'CloudFrontDistribution'>;
export declare class UserPoolDomainAttributes extends CustomResource<ResourceAttributes, typeof SCHEMA> {

@@ -17,0 +17,0 @@ private cognitoIdp;

{
"name": "@collaborne/custom-cloudformation-resources",
"version": "0.4.0",
"version": "0.5.0",
"description": "Custom CloudFormation resources",

@@ -46,3 +46,7 @@ "main": "dist/index.js",

"node": ">=14.14.0"
},
"dependencies": {
"@types/crc": "^3.4.0",
"crc": "^3.8.0"
}
}

@@ -5,3 +5,7 @@ import { ACM, Route53 } from 'aws-sdk';

import { CustomResource, Response } from '../custom-resource';
import {
ContinuationRequired,
CustomResource,
Response,
} from '../custom-resource';
import { Logger } from '../logger';

@@ -106,3 +110,4 @@ import { isDefined } from '../utils';

ResourceAttributes,
typeof SCHEMA
typeof SCHEMA,
Response<ResourceAttributes>
> {

@@ -119,8 +124,19 @@ private acm = new ACM({ region: 'us-east-1' });

params: SchemaType<typeof SCHEMA>,
): Promise<Response<ResourceAttributes>> {
const attributes = await this.createCertificate(params);
return {
physicalResourceId: `${physicalResourceId}/${attributes.CertificateId}`,
attributes,
};
continuationAttributes: Response<ResourceAttributes>,
): Promise<
| Response<ResourceAttributes>
| ContinuationRequired<Response<ResourceAttributes>>
> {
let response: Response<ResourceAttributes>;
if (continuationAttributes) {
response = continuationAttributes;
} else {
const attributes = await this.createCertificate(params);
response = {
physicalResourceId: `${physicalResourceId}/${attributes.CertificateId}`,
attributes,
};
}
return this.maybeContinuation(response);
}

@@ -166,3 +182,11 @@

oldParams: unknown,
): Promise<Response<ResourceAttributes>> {
continuationAttributes: Response<ResourceAttributes>,
): Promise<
| Response<ResourceAttributes>
| ContinuationRequired<Response<ResourceAttributes>>
> {
if (continuationAttributes) {
return this.maybeContinuation(continuationAttributes);
}
const {

@@ -294,6 +318,6 @@ CertificateTransparencyLoggingPreference: ctLoggingPreference,

);
return {
return this.maybeContinuation({
physicalResourceId: newPhysicalResourceId,
attributes: newAttributes,
};
});
}

@@ -510,8 +534,2 @@ }

// In theory we could proceed, as we have the ARN -- but this will just lead to failures downstream
// as the certificate isn't necessarily issued yet.
await this.acm
.waitFor('certificateValidated', { CertificateArn: certificateArn })
.promise();
return {

@@ -555,2 +573,39 @@ Arn: certificateArn,

}
protected async maybeContinuation(
response: Response<ResourceAttributes>,
): Promise<
| Response<ResourceAttributes>
| ContinuationRequired<Response<ResourceAttributes>>
> {
// Initially wee have the id in the response, so we simply check now whether the certificate is valid.
// If it is, great, return the response. Otherwise stuff the whole response into continuationAttributes, and
// try again in a minute or two.
const certificateArn = response.attributes?.Arn;
if (!certificateArn) {
throw new Error('Missing certificate ARN');
}
const { Certificate: certificate } = await this.acm
.describeCertificate({ CertificateArn: certificateArn })
.promise();
if (!certificate) {
throw new Error(`Cannot find certificate ${certificateArn}`);
}
if (certificate.Status === 'ISSUED') {
this.logger.log('Certificate is ISSUED, stopping continuations');
return response;
} else if (certificate.Status === 'PENDING_VALIDATION') {
this.logger.log(
`Certificate is PENDING_VALIDATION, requesting continuation`,
);
return {
continuationAfter: Number(process.env.RETRY_INTERVAL_SECONDS ?? '300'),
continuationAttributes: response,
};
} else {
throw new Error(`Certificate is in invalid status ${certificate.Status}`);
}
}
}

@@ -0,1 +1,4 @@

import { CloudWatchEvents } from 'aws-sdk';
import { crc32 } from 'crc';
import type { ObjectSchema, SchemaType } from '@collaborne/json-schema-to-type';

@@ -15,6 +18,42 @@

/**
* Response returned by the resource methods to indicate that long-running continuation is needed
*/
export interface ContinuationRequired<ContinuationAttributes extends unknown> {
/** Number of seconds after which to retry the function */
continuationAfter: number;
/** Additional properties that should be provided in the continuation invocation */
continuationAttributes: ContinuationAttributes;
}
function isContinuationRequired<RA, CA>(
response?: Response<RA> | ContinuationRequired<CA>,
): response is ContinuationRequired<CA> {
return typeof response === 'object' && 'continuationAfter' in response;
}
type ContinuedCustomResourceRequest<ContinuationAttributes> =
CustomResourceRequest & {
ContinuationAttributes: ContinuationAttributes;
};
function isContinuedCustomResourceRequest<ContinuationAttributes>(
request:
| CustomResourceRequest
| ContinuedCustomResourceRequest<ContinuationAttributes>,
): request is ContinuedCustomResourceRequest<ContinuationAttributes> {
return (
'ContinuationAttributes' in request &&
typeof request.ContinuationAttributes === 'object'
);
}
export abstract class CustomResource<
ResourceAttributes extends unknown,
S extends ObjectSchema,
ContinuationAttributes extends unknown = unknown,
> {
private static CW_EVENTS_TARGET_ID = 'CustomResource';
private cwEvents = new CloudWatchEvents({ apiVersion: '2015-10-07' });
private requestQueue: (() => Promise<void>)[] = [];

@@ -31,3 +70,6 @@

params: SchemaType<S>,
): Promise<Response<ResourceAttributes>>;
continuationAttributes?: ContinuationAttributes,
): Promise<
Response<ResourceAttributes> | ContinuationRequired<ContinuationAttributes>
>;

@@ -37,3 +79,6 @@ public abstract deleteResource(

params: SchemaType<S>,
): Promise<Response<ResourceAttributes>>;
continuationAttributes?: ContinuationAttributes,
): Promise<
Response<ResourceAttributes> | ContinuationRequired<ContinuationAttributes>
>;

@@ -44,6 +89,11 @@ public abstract updateResource(

oldParams: unknown,
): Promise<Response<ResourceAttributes>>;
continuationAttributes?: ContinuationAttributes,
): Promise<
Response<ResourceAttributes> | ContinuationRequired<ContinuationAttributes>
>;
public handleRequest(
request: CustomResourceRequest,
request:
| CustomResourceRequest
| ContinuedCustomResourceRequest<ContinuationAttributes>,
): Promise<ResponseStatus> {

@@ -81,6 +131,8 @@ let resolveRequest: (response: ResponseStatus) => void;

protected async processRequest(request: CustomResourceRequest): Promise<{
protected async processRequest(
request:
| CustomResourceRequest
| ContinuedCustomResourceRequest<ContinuationAttributes>,
): Promise<{
status: ResponseStatus;
statusReason?: string;
response: Response<ResourceAttributes>;
}> {

@@ -94,5 +146,13 @@ // TODO: validate the parameters to conform to the schema

let continuationAttributes: ContinuationAttributes | undefined;
if (isContinuedCustomResourceRequest(request)) {
this.logger.log('Request is a continuation of an earlier request');
continuationAttributes = request.ContinuationAttributes;
}
let status: ResponseStatus = 'FAILED';
let statusReason: string | undefined;
let response: Response<ResourceAttributes> | undefined;
let response:
| Response<ResourceAttributes>
| ContinuationRequired<ContinuationAttributes>
| undefined;
try {

@@ -106,2 +166,3 @@ const { ServiceToken: _ignoredServiceToken, ...properties } =

properties as SchemaType<S>,
continuationAttributes as ContinuationAttributes,
);

@@ -113,2 +174,3 @@ break;

properties as SchemaType<S>,
continuationAttributes as ContinuationAttributes,
);

@@ -126,2 +188,3 @@ break;

oldProperties,
continuationAttributes as ContinuationAttributes,
);

@@ -131,2 +194,3 @@ }

}
status = 'SUCCESS';

@@ -140,20 +204,119 @@ } catch (err) {

const responsePhysicalResourceId =
response?.physicalResourceId || physicalResourceId;
await sendResponse(
request,
status,
statusReason,
responsePhysicalResourceId,
response?.attributes,
);
if (isContinuationRequired(response)) {
// Schedule the invocation of the function again, with the additional attributes from
// the response
const { continuationAfter, continuationAttributes } = response;
const continuationRuleName = this.getContinuationRuleName(request);
// XXX: Magic! Needs to be documented that this can be set to an ARN of the role to use.
const ruleRoleArn = process.env.CW_EVENTS_CONTINUATION_RULE_ROLE_ARN;
// Ideally we want to put the target for the continuation first so that we don't miss
// our own goal, but CWE doesn't work this way.
// What does work however is to create the rule with a schedule expression pointing to a
// day in the past, keep it "DISABLED", then add the target, and then update the rule to
// enable it with a suitably-in-the-future expression.
// Note that if the rule already exists this will similarly first disable it; as the rule name
// and target ID are constant over the life-time of the rule this should all be idempotent.
const prepPutRuleParams: CloudWatchEvents.PutRuleRequest = {
Name: continuationRuleName,
RoleArn: ruleRoleArn,
ScheduleExpression: `cron(30 7 19 6 ? 2018)`,
State: 'DISABLED',
};
await this.cwEvents.putRule(prepPutRuleParams).promise();
const putTargetsParams: CloudWatchEvents.PutTargetsRequest = {
Rule: continuationRuleName,
Targets: [
{
Arn: request.ServiceToken,
Id: CustomResource.CW_EVENTS_TARGET_ID,
Input: JSON.stringify({
...request,
ContinuationAttributes: continuationAttributes,
}),
RoleArn: process.env.CW_EVENTS_CONTINUATION_TARGET_ROLE_ARN,
},
],
};
await this.cwEvents.putTargets(putTargetsParams).promise();
const now = new Date();
const when = new Date(now.getTime() + continuationAfter * 1000);
// Round up to the next full minute, as we won't be getting scheduling if the time isn't in the future.
const cronExpression = `${Math.max(
now.getMinutes() + 1,
when.getMinutes(),
)} ${when.getHours()} ${when.getDate()} ${
when.getMonth() + 1
} ? ${when.getFullYear()}`;
this.logger.log(
`Scheduling continuation using CWE rule ${continuationRuleName} after ${continuationAfter}s (at ${cronExpression}) `,
);
const schedulePutRuleParams: CloudWatchEvents.PutRuleRequest = {
Name: continuationRuleName,
RoleArn: ruleRoleArn,
ScheduleExpression: `cron(${cronExpression})`,
State: 'ENABLED',
};
await this.cwEvents.putRule(schedulePutRuleParams).promise();
} else {
// A "definite" status, so write that into the response document
const responsePhysicalResourceId =
response?.physicalResourceId || physicalResourceId;
await sendResponse(
request,
status,
statusReason,
responsePhysicalResourceId,
response?.attributes,
);
// If there are continuation attributes in the request, we know that this was a continuation
// and therefore we now want to remove the rule.
if (continuationAttributes) {
const continuationRuleName = this.getContinuationRuleName(request);
this.logger.log(`Cleaning up CWE rule ${continuationRuleName}`);
try {
await this.cwEvents
.removeTargets({
Rule: continuationRuleName,
Ids: [CustomResource.CW_EVENTS_TARGET_ID],
})
.promise();
await this.cwEvents
.deleteRule({
Name: continuationRuleName,
})
.promise();
} catch (err) {
// Best effort, didn't work, bad luck.
// Given that the rule is a single date, this should merely produce garbage in CW Events, but not produce
// any other side-effects.
this.logger.warn(
`Cannot remove continuation rule ${continuationRuleName}: ${err.message}`,
);
}
}
}
return {
status,
statusReason,
response: {
physicalResourceId: responsePhysicalResourceId,
attributes: response?.attributes,
},
};
}
private getContinuationRuleName(request: CustomResourceRequest): string {
// Rule name, can be at most 64 characters and has a limited range of valid characters
// We also know the rule must be unique for this request, so we try to cram as much as we can
// into it.
// StackId is something like "arn:aws:cloudformation:us-west-2:123456789012:stack/stack-name/guid", for our purposes
// we care only about the stack name.
const [, stackName] = request.StackId.split(/:/)[5].split(/\//);
const safeRequestId = crc32(request.RequestId).toString(16);
const continuationRuleName = `Continuation-${stackName}-${request.LogicalResourceId}`;
return `${continuationRuleName.slice(0, 55)}-${safeRequestId}`;
}
}

@@ -7,3 +7,2 @@ import { CognitoIdentityServiceProvider } from 'aws-sdk';

import { Logger } from '../logger';
import { DomainDescriptionType } from 'aws-sdk/clients/cognitoidentityserviceprovider';

@@ -20,3 +19,6 @@ const SCHEMA = {

type ResourceAttributes = Pick<DomainDescriptionType, 'CloudFrontDistribution'>;
type ResourceAttributes = Pick<
CognitoIdentityServiceProvider.DomainDescriptionType,
'CloudFrontDistribution'
>;

@@ -23,0 +25,0 @@ export class UserPoolDomainAttributes extends CustomResource<

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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