Skip to content

Commit

Permalink
fix(route53): CrossAccountZoneDelegationRecord does not remove old NS…
Browse files Browse the repository at this point in the history
… records when the zoneName changes
  • Loading branch information
rangerthegood committed Aug 15, 2022
1 parent 90786d6 commit be8f5d0
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,24 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent

switch (event.RequestType) {
case 'Create':
return cfnEventHandler(resourceProps, false);
case 'Update':
// We handle updates in two parts, first attempt to delete the old NS record, second create
// the new resource.

// DELETE old Resources
try {
const oldResourceProps = event.OldResourceProperties as unknown as ResourceProperties;

await cfnEventHandler(oldResourceProps, true);
} catch (e) {
// it is possible that the user removed the old role manually before this call,
// additionally, it is possible that the old record set was manually deleted. We
// want to ignore if this errors and continue execution and attempt to create the
// new resource, and not block the user.
}

// UPSERT new Resources
return cfnEventHandler(resourceProps, false);
case 'Delete':
return cfnEventHandler(resourceProps, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,118 @@ test('calls listHostedZonesByName to get zoneId if ParentZoneId is not provided'
});
});

test('calls change resource record set with Delete and Upsert for Update event', async () => {
// GIVEN
mockStsClient.promise.mockResolvedValue({
Credentials: { AccessKeyId: 'K', SecretAccessKey: 'S', SessionToken: 'T' },
});
mockRoute53Client.promise.mockResolvedValueOnce({});

// WHEN
const event = getCfnEvent({
RequestType: 'Update',
OldResourceProperties: {
ServiceToken: 'Foo',
AssumeRoleArn: 'roleArn2',
ParentZoneId: '1',
DelegatedZoneName: 'recordName',
DelegatedZoneNameServers: ['oldone', 'oldtwo'],
TTL: 172800,
},
});
await invokeHandler(event);

// THEN
// The old and new records can be called with a different roleArn
expect(mockStsClient.assumeRole).toHaveBeenCalledTimes(2);

expect(mockRoute53Client.changeResourceRecordSets).toHaveBeenCalledTimes(2);

// check first call to delete old NS record
expect(mockRoute53Client.changeResourceRecordSets).toHaveBeenNthCalledWith(1, {
HostedZoneId: '1',
ChangeBatch: {
Changes: [
{
Action: 'DELETE',
ResourceRecordSet: {
Name: 'recordName',
Type: 'NS',
TTL: 172800,
ResourceRecords: [{ Value: 'oldone' }, { Value: 'oldtwo' }],
},
},
],
},
});

// check second call to create new NS record
expect(mockRoute53Client.changeResourceRecordSets).toHaveBeenNthCalledWith(2, {
HostedZoneId: '1',
ChangeBatch: {
Changes: [
{
Action: 'UPSERT',
ResourceRecordSet: {
Name: 'recordName',
Type: 'NS',
TTL: 172800,
ResourceRecords: [{ Value: 'one' }, { Value: 'two' }],
},
},
],
},
});
});

test('calls change resource record set with DELETE for Update event with bad old roleArn', async () => {
// GIVEN
mockStsClient.promise.mockResolvedValueOnce({
Credentials: undefined,
});
mockStsClient.promise.mockResolvedValue({
Credentials: { AccessKeyId: 'K', SecretAccessKey: 'S', SessionToken: 'T' },
});
mockRoute53Client.promise.mockResolvedValueOnce({});

// WHEN
const event = getCfnEvent({
RequestType: 'Update',
OldResourceProperties: {
ServiceToken: 'Foo',
AssumeRoleArn: 'badRoleArn',
ParentZoneId: '1',
DelegatedZoneName: 'recordName',
DelegatedZoneNameServers: ['oldone', 'oldtwo'],
TTL: 172800,
},
});
await invokeHandler(event);

// THEN
// The old and new records can be called with a different roleArn
expect(mockStsClient.assumeRole).toHaveBeenCalledTimes(2);

expect(mockRoute53Client.changeResourceRecordSets).toHaveBeenCalledTimes(1);

// check for second call when we UPSET the new NS record
expect(mockRoute53Client.changeResourceRecordSets).toHaveBeenCalledWith({
HostedZoneId: '1',
ChangeBatch: {
Changes: [{
Action: 'UPSERT',
ResourceRecordSet: {
Name: 'recordName',
Type: 'NS',
TTL: 172800,
ResourceRecords: [{ Value: 'one' }, { Value: 'two' }],
},
}],
},
});
});


test('throws if more than one HostedZones are returnd for the provided ParentHostedZone', async () => {
// GIVEN
const parentZoneName = 'some.zone';
Expand Down

0 comments on commit be8f5d0

Please sign in to comment.