Skip to content

Commit

Permalink
Merge branch 'main' into TheRealAmazonKendra/lambda-layer-awscli-upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Aug 23, 2022
2 parents 7b80144 + 17dd132 commit 2a36a94
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 3 deletions.
11 changes: 11 additions & 0 deletions packages/@aws-cdk/aws-certificatemanager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,17 @@ new acm.PrivateCertificate(this, 'PrivateCertificate', {
});
```

## Requesting certificates without transparency logging

Transparency logging can be opted out of for AWS Certificate Manager certificates. See [opting out of certifiacte transparency logging](https://docs.aws.amazon.com/acm/latest/userguide/acm-bestpractices.html#best-practices-transparency) for limits.

```ts
new acm.Certificate(this, 'Certificate', {
domainName: 'test.example.com',
transparencyLoggingEnabled: false,
});
```

## Importing

If you want to import an existing certificate, you can do so from its ARN:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ let report = function (event, context, responseStatus, physicalResourceId, respo
* @param {map} tags Tags to add to the requested certificate
* @returns {string} Validated certificate ARN
*/
const requestCertificate = async function (requestId, domainName, subjectAlternativeNames, hostedZoneId, region, route53Endpoint, tags) {
const requestCertificate = async function (requestId, domainName, subjectAlternativeNames, certificateTransparencyLoggingPreference, hostedZoneId, region, route53Endpoint, tags) {
const crypto = require('crypto');
const acm = new aws.ACM({ region });
const route53 = route53Endpoint ? new aws.Route53({ endpoint: route53Endpoint }) : new aws.Route53();
Expand All @@ -92,6 +92,9 @@ const requestCertificate = async function (requestId, domainName, subjectAlterna
const reqCertResponse = await acm.requestCertificate({
DomainName: domainName,
SubjectAlternativeNames: subjectAlternativeNames,
Options: {
CertificateTransparencyLoggingPreference: certificateTransparencyLoggingPreference
},
IdempotencyToken: crypto.createHash('sha256').update(requestId).digest('hex').slice(0, 32),
ValidationMethod: 'DNS'
}).promise();
Expand Down Expand Up @@ -288,6 +291,7 @@ exports.certificateRequestHandler = async function (event, context) {
event.RequestId,
event.ResourceProperties.DomainName,
event.ResourceProperties.SubjectAlternativeNames,
event.ResourceProperties.CertificateTransparencyLoggingPreference,
event.ResourceProperties.HostedZoneId,
event.ResourceProperties.Region,
event.ResourceProperties.Route53Endpoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ describe('DNS Validated Certificate Handler', () => {
.expectResolve(() => {
sinon.assert.calledWith(requestCertificateFake, sinon.match({
DomainName: testDomainName,
ValidationMethod: 'DNS'
ValidationMethod: 'DNS',
Options: {
CertificateTransparencyLoggingPreference: undefined
}
}));
sinon.assert.calledWith(changeResourceRecordSetsFake, sinon.match({
ChangeBatch: {
Expand Down Expand Up @@ -731,6 +734,72 @@ describe('DNS Validated Certificate Handler', () => {
});
});

test('Create operation with `CertificateTransparencyLoggingPreference` requests a certificate with that preference set', () => {
const requestCertificateFake = sinon.fake.resolves({
CertificateArn: testCertificateArn,
});

const describeCertificateFake = sinon.stub();
describeCertificateFake.onFirstCall().resolves({
Certificate: {
CertificateArn: testCertificateArn
}
});
describeCertificateFake.resolves({
Certificate: {
CertificateArn: testCertificateArn,
DomainValidationOptions: [{
ValidationStatus: 'SUCCESS',
ResourceRecord: {
Name: testRRName,
Type: 'CNAME',
Value: testRRValue
}
}]
}
});

const addTagsToCertificateFake = sinon.fake.resolves({});

const changeResourceRecordSetsFake = sinon.fake.resolves({
ChangeInfo: {
Id: 'bogus'
}
});

AWS.mock('ACM', 'requestCertificate', requestCertificateFake);
AWS.mock('ACM', 'describeCertificate', describeCertificateFake);
AWS.mock('Route53', 'changeResourceRecordSets', changeResourceRecordSetsFake);
AWS.mock('ACM', 'addTagsToCertificate', addTagsToCertificateFake);

const request = nock(ResponseURL).put('/', body => {
return body.Status === 'SUCCESS';
}).reply(200);

return LambdaTester(handler.certificateRequestHandler)
.event({
RequestType: 'Create',
RequestId: testRequestId,
ResourceProperties: {
DomainName: testDomainName,
HostedZoneId: testHostedZoneId,
Region: 'us-east-1',
CertificateTransparencyLoggingPreference: 'DISABLED',
Tags: testTags
}
})
.expectResolve(() => {
sinon.assert.calledWith(requestCertificateFake, sinon.match({
DomainName: testDomainName,
ValidationMethod: 'DNS',
Options: {
CertificateTransparencyLoggingPreference: 'DISABLED'
}
}));
expect(request.isDone()).toBe(true);
});
});

test('Delete operation deletes the certificate', () => {
const describeCertificateFake = sinon.fake.resolves({
Certificate: {
Expand Down
21 changes: 21 additions & 0 deletions packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ export interface CertificateProps {
* @default CertificateValidation.fromEmail()
*/
readonly validation?: CertificateValidation;

/**
* Enable or disable transparency logging for this certificate
*
* Once a certificate has been logged, it cannot be removed from the log.
* Opting out at that point will have no effect. If you opt out of logging
* when you request a certificate and then choose later to opt back in,
* your certificate will not be logged until it is renewed.
* If you want the certificate to be logged immediately, we recommend that you issue a new one.
*
* @see https://docs.aws.amazon.com/acm/latest/userguide/acm-bestpractices.html#best-practices-transparency
*
* @default true
*/
readonly transparencyLoggingEnabled?: boolean;
}

/**
Expand Down Expand Up @@ -214,11 +229,17 @@ export class Certificate extends CertificateBase implements ICertificate {

const allDomainNames = [props.domainName].concat(props.subjectAlternativeNames || []);

let certificateTransparencyLoggingPreference: string | undefined;
if (props.transparencyLoggingEnabled !== undefined) {
certificateTransparencyLoggingPreference = props.transparencyLoggingEnabled ? 'ENABLED' : 'DISABLED';
}

const cert = new CfnCertificate(this, 'Resource', {
domainName: props.domainName,
subjectAlternativeNames: props.subjectAlternativeNames,
domainValidationOptions: renderDomainValidation(validation, allDomainNames),
validationMethod: validation.method,
certificateTransparencyLoggingPreference,
});

this.certificateArn = cert.ref;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ export class DnsValidatedCertificate extends CertificateBase implements ICertifi
this.hostedZoneId = props.hostedZone.hostedZoneId.replace(/^\/hostedzone\//, '');
this.tags = new cdk.TagManager(cdk.TagType.MAP, 'AWS::CertificateManager::Certificate');

let certificateTransparencyLoggingPreference: string | undefined;
if (props.transparencyLoggingEnabled !== undefined) {
certificateTransparencyLoggingPreference = props.transparencyLoggingEnabled ? 'ENABLED' : 'DISABLED';
}

const requestorFunction = new lambda.Function(this, 'CertificateRequestorFunction', {
code: lambda.Code.fromAsset(path.resolve(__dirname, '..', 'lambda-packages', 'dns_validated_certificate_handler', 'lib')),
handler: 'index.certificateRequestHandler',
Expand All @@ -121,6 +126,7 @@ export class DnsValidatedCertificate extends CertificateBase implements ICertifi
properties: {
DomainName: props.domainName,
SubjectAlternativeNames: cdk.Lazy.list({ produce: () => props.subjectAlternativeNames }, { omitEmpty: true }),
CertificateTransparencyLoggingPreference: certificateTransparencyLoggingPreference,
HostedZoneId: this.hostedZoneId,
Region: props.region,
Route53Endpoint: props.route53Endpoint,
Expand Down
42 changes: 42 additions & 0 deletions packages/@aws-cdk/aws-certificatemanager/test/certificate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,45 @@ test('CertificateValidation.fromDnsMultiZone', () => {
ValidationMethod: 'DNS',
});
});

describe('Transparency logging settings', () => {
test('leaves transparency logging untouched by default', () => {
const stack = new Stack();

new Certificate(stack, 'Certificate', {
domainName: 'test.example.com',
});

const certificateNodes = Template.fromStack(stack).findResources('AWS::CertificateManager::Certificate');
expect(certificateNodes.Certificate4E7ABB08).toBeDefined();
expect(certificateNodes.Certificate4E7ABB08.CertificateTransparencyLoggingPreference).toBeUndefined();
});

test('can enable transparency logging', () => {
const stack = new Stack();

new Certificate(stack, 'Certificate', {
domainName: 'test.example.com',
transparencyLoggingEnabled: true,
});

Template.fromStack(stack).hasResourceProperties('AWS::CertificateManager::Certificate', {
DomainName: 'test.example.com',
CertificateTransparencyLoggingPreference: 'ENABLED',
});
});

test('can disable transparency logging', () => {
const stack = new Stack();

new Certificate(stack, 'Certificate', {
domainName: 'test.example.com',
transparencyLoggingEnabled: false,
});

Template.fromStack(stack).hasResourceProperties('AWS::CertificateManager::Certificate', {
DomainName: 'test.example.com',
CertificateTransparencyLoggingPreference: 'DISABLED',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,31 @@ test('works with imported role', () => {
Role: 'arn:aws:iam::account-id:role/role-name',
});
});

test('test transparency logging settings is passed to the custom resource', () => {
const stack = new Stack();

const exampleDotComZone = new PublicHostedZone(stack, 'ExampleDotCom', {
zoneName: 'example.com',
});

new DnsValidatedCertificate(stack, 'Cert', {
domainName: 'example.com',
hostedZone: exampleDotComZone,
transparencyLoggingEnabled: false,
});

Template.fromStack(stack).hasResourceProperties('AWS::CloudFormation::CustomResource', {
ServiceToken: {
'Fn::GetAtt': [
'CertCertificateRequestorFunction98FDF273',
'Arn',
],
},
DomainName: 'example.com',
HostedZoneId: {
Ref: 'ExampleDotCom4D1B83AA',
},
CertificateTransparencyLoggingPreference: 'DISABLED',
});
});
12 changes: 12 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,18 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn

/**
* The DNS entries for the interface VPC endpoint.
* Each entry is a combination of the hosted zone ID and the DNS name.
* The entries are ordered as follows: regional public DNS, zonal public DNS, private DNS, and wildcard DNS.
* This order is not enforced for AWS Marketplace services.
*
* The following is an example. In the first entry, the hosted zone ID is Z1HUB23UULQXV
* and the DNS name is vpce-01abc23456de78f9g-12abccd3.ec2.us-east-1.vpce.amazonaws.com.
*
* ["Z1HUB23UULQXV:vpce-01abc23456de78f9g-12abccd3.ec2.us-east-1.vpce.amazonaws.com",
* "Z1HUB23UULQXV:vpce-01abc23456de78f9g-12abccd3-us-east-1a.ec2.us-east-1.vpce.amazonaws.com",
* "Z1C12344VYDITB0:ec2.us-east-1.amazonaws.com"]
*
* If you update the PrivateDnsEnabled or SubnetIds properties, the DNS entries in the list will change.
* @attribute
*/
public readonly vpcEndpointDnsEntries: string[];
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async function parseCommandLineArguments() {
return yargs
.env('CDK')
.usage('Usage: cdk -a <cdk-app> COMMAND')
.option('app', { type: 'string', alias: 'a', desc: 'REQUIRED: command-line for executing your app or a cloud assembly directory (e.g. "node bin/my-app.js")', requiresArg: true })
.option('app', { type: 'string', alias: 'a', desc: 'REQUIRED WHEN RUNNING APP: command-line for executing your app or a cloud assembly directory (e.g. "node bin/my-app.js"). Can also be specified in cdk.json or ~/.cdk.json', requiresArg: true })
.option('build', { type: 'string', desc: 'Command-line for a pre-synth build' })
.option('context', { type: 'array', alias: 'c', desc: 'Add contextual string parameter (KEY=VALUE)', nargs: 1, requiresArg: true })
.option('plugin', { type: 'array', alias: 'p', desc: 'Name or path of a node package that extend the CDK features. Can be specified multiple times', nargs: 1 })
Expand Down

0 comments on commit 2a36a94

Please sign in to comment.