Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Aug 11, 2020
2 parents 2ac50a2 + 1fe9684 commit 030da8d
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 4 deletions.
14 changes: 14 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ The following widgets are available:
- `AlarmWidget` -- shows the graph and alarm line for a single alarm.
- `SingleValueWidget` -- shows the current value of a set of metrics.
- `TextWidget` -- shows some static Markdown.
- `AlarmStatusWidget` -- shows the status of your alarms in a grid view.

### Graph widget

Expand Down Expand Up @@ -319,6 +320,19 @@ dashboard.addWidgets(new TextWidget({
}));
```

### Alarm Status widget

An alarm status widget displays instantly the status of any type of alarms and gives the
ability to aggregate one or more alarms together in a small surface.

```ts
dashboard.addWidgets(
new AlarmStatusWidget({
alarms: [errorAlarm],
})
);
```

### Query results widget

A `LogQueryWidget` shows the results of a query from Logs Insights:
Expand Down
63 changes: 63 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/lib/alarm-status-widget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { IAlarm } from './alarm-base';
import { ConcreteWidget } from './widget';

/**
* Properties for an Alarm Status Widget
*/
export interface AlarmStatusWidgetProps {
/**
* CloudWatch Alarms to show in widget
*/
readonly alarms: IAlarm[];
/**
* The title of the widget
*
* @default 'Alarm Status'
*/
readonly title?: string;
/**
* Width of the widget, in a grid of 24 units wide
*
* @default 6
*/
readonly width?: number;
/**
* Height of the widget
*
* @default 3
*/
readonly height?: number;
}

/**
* A dashboard widget that displays alarms in a grid view
*/
export class AlarmStatusWidget extends ConcreteWidget {
private readonly props: AlarmStatusWidgetProps;

constructor(props: AlarmStatusWidgetProps) {
super(props.width || 6, props.height || 3);
this.props = props;
}

public position(x: number, y: number): void {
this.x = x;
this.y = y;
}

public toJson(): any[] {
return [
{
type: 'alarm',
width: this.width,
height: this.height,
x: this.x,
y: this.y,
properties: {
title: this.props.title ? this.props.title : 'Alarm Status',
alarms: this.props.alarms.map((alarm) => alarm.alarmArn),
},
},
];
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-cloudwatch/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './metric-types';
export * from './log-query';
export * from './text';
export * from './widget';
export * from './alarm-status-widget';

// AWS::CloudWatch CloudFormation Resources:
export * from './cloudwatch.generated';
35 changes: 35 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/test/test.alarm-status-widget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Stack } from '@aws-cdk/core';
import { Test } from 'nodeunit';
import { Metric, Alarm, AlarmStatusWidget } from '../lib';
export = {
'alarm status widget'(test: Test) {
// GIVEN
const stack = new Stack();
const metric = new Metric({ namespace: 'CDK', metricName: 'Test' });
const alarm = new Alarm(stack, 'Alarm', {
metric,
threshold: 1,
evaluationPeriods: 1,
});

// WHEN
const widget = new AlarmStatusWidget({
alarms: [alarm],
});

// THEN
test.deepEqual(stack.resolve(widget.toJson()), [
{
type: 'alarm',
width: 6,
height: 3,
properties: {
title: 'Alarm Status',
alarms: [{ 'Fn::GetAtt': ['Alarm7103F465', 'Arn'] }],
},
},
]);

test.done();
},
};
37 changes: 36 additions & 1 deletion packages/@aws-cdk/aws-globalaccelerator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

## Introduction

AWS Global Accelerator is a service that improves the availability and performance of your applications with local or global users. It provides static IP addresses that act as a fixed entry point to your application endpoints in a single or multiple AWS Regions, such as your Application Load Balancers, Network Load Balancers or Amazon EC2 instances.
AWS Global Accelerator (AGA) is a service that improves the availability and performance of your applications with local or global users. It provides static IP addresses that act as a fixed entry point to your application endpoints in a single or multiple AWS Regions, such as your Application Load Balancers, Network Load Balancers or Amazon EC2 instances.

This module supports features under [AWS Global Accelerator](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_GlobalAccelerator.html) that allows users set up resources using the `@aws-cdk/aws-globalaccelerator` module.

Expand Down Expand Up @@ -93,3 +93,38 @@ endpointGroup.addElasticIpAddress('EipEndpoint', eip);
endpointGroup.addEc2Instance('InstanceEndpoint', instances[0]);
endpointGroup.addEndpoint('InstanceEndpoint2', instances[1].instanceId);
```

## Accelerator Security Groups

When using certain AGA features (client IP address preservation), AGA creates elastic network interfaces (ENI) in your AWS account which are
associated with a Security Group, and which are reused for all AGAs associated with that VPC. Per the
[best practices](https://docs.aws.amazon.com/global-accelerator/latest/dg/best-practices-aga.html) page, AGA creates a specific security group
called `GlobalAccelerator` for each VPC it has an ENI in. You can use the security group created by AGA as a source group in other security
groups, such as those for EC2 instances or Elastic Load Balancers, in order to implement least-privilege security group rules.

CloudFormation doesn't support referencing the security group created by AGA. CDK has a library that enables you to reference the AGA security group
for a VPC using an AwsCustomResource.

```
const vpc = new Vpc(stack, 'VPC', {});
const alb = new elbv2.ApplicationLoadBalancer(stack, 'ALB', { vpc, internetFacing: false });
const accelerator = new ga.Accelerator(stack, 'Accelerator');
const listener = new ga.Listener(stack, 'Listener', {
accelerator,
portRanges: [
{
fromPort: 443,
toPort: 443,
},
],
});
const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener });
endpointGroup.addLoadBalancer('AlbEndpoint', alb);
// Remember that there is only one AGA security group per VPC.
// This code will fail at CloudFormation deployment time if you do not have an AGA
const agaSg = ga.AcceleratorSecurityGroup.fromVpc(stack, 'GlobalAcceleratorSG', vpc);
// Allow connections from the AGA to the ALB
alb.connections.allowFrom(agaSg, Port.tcp(443));
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ISecurityGroup, SecurityGroup, IVpc } from '@aws-cdk/aws-ec2';
import { Construct } from '@aws-cdk/core';
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId} from '@aws-cdk/custom-resources';
import { EndpointGroup } from '../lib';

/**
* The security group used by a Global Accelerator to send traffic to resources in a VPC.
*/
export class AcceleratorSecurityGroup {
/**
* Lookup the Global Accelerator security group at CloudFormation deployment time.
*
* As of this writing, Global Accelerators (AGA) create a single security group per VPC. AGA security groups are shared
* by all AGAs in an account. Additionally, there is no CloudFormation mechanism to reference the AGA security groups.
*
* This makes creating security group rules which allow traffic from an AGA complicated in CDK. This lookup will identify
* the AGA security group for a given VPC at CloudFormation deployment time, and lets you create rules for traffic from AGA
* to other resources created by CDK.
*/
public static fromVpc(scope: Construct, id: string, vpc: IVpc, endpointGroup: EndpointGroup): ISecurityGroup {

// The security group name is always 'GlobalAccelerator'
const globalAcceleratorSGName = 'GlobalAccelerator';

// How to reference the security group name in the response from EC2
const ec2ResponseSGIdField = 'SecurityGroups.0.GroupId';

// The AWS Custom Resource that make a call to EC2 to get the security group ID, for the given VPC
const lookupAcceleratorSGCustomResource = new AwsCustomResource(scope, id + 'CustomResource', {
onCreate: {
service: 'EC2',
action: 'describeSecurityGroups',
parameters: {
Filters: [
{
Name: 'group-name',
Values: [
globalAcceleratorSGName,
],
},
{
Name: 'vpc-id',
Values: [
vpc.vpcId,
],
},
],
},
// We get back a list of responses, but the list should be of length 0 or 1
// Getting no response means no resources have been linked to the AGA
physicalResourceId: PhysicalResourceId.fromResponse(ec2ResponseSGIdField),
},
policy: AwsCustomResourcePolicy.fromSdkCalls({
resources: AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});

// Look up the security group ID
const sg = SecurityGroup.fromSecurityGroupId(scope,
id,
lookupAcceleratorSGCustomResource.getResponseField(ec2ResponseSGIdField));
// We add a dependency on the endpoint group, guaranteeing that CloudFormation won't
// try and look up the SG before AGA creates it. The SG is created when a VPC resource
// is associated with an AGA
lookupAcceleratorSGCustomResource.node.addDependency(endpointGroup);
return sg;
}

private constructor() {}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-globalaccelerator/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// AWS::GlobalAccelerator CloudFormation Resources:
export * from './globalaccelerator.generated';
export * from './accelerator';
export * from './accelerator-security-group';
export * from './listener';
export * from './endpoint-group';
7 changes: 5 additions & 2 deletions packages/@aws-cdk/aws-globalaccelerator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,23 @@
"license": "Apache-2.0",
"devDependencies": {
"@aws-cdk/assert": "0.0.0",
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/aws-elasticloadbalancingv2": "0.0.0",
"cdk-integ-tools": "0.0.0",
"cdk-build-tools": "0.0.0",
"cfn2ts": "0.0.0",
"pkglint": "0.0.0"
},
"dependencies": {
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/custom-resources": "0.0.0",
"constructs": "^3.0.2"
},
"peerDependencies": {
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.0.2"
"constructs": "^3.0.2",
"@aws-cdk/custom-resources": "0.0.0"
},
"engines": {
"node": ">= 10.13.0 <13 || >=13.7.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { expect, haveResource, ResourcePart } from '@aws-cdk/assert';
import { Port } from '@aws-cdk/aws-ec2';
import * as ga from '../lib';
import { testFixture, testFixtureAlb } from './util';

test('custom resource exists', () => {
// GIVEN
const { stack, vpc } = testFixture();
const accelerator = new ga.Accelerator(stack, 'Accelerator');
const listener = new ga.Listener(stack, 'Listener', {
accelerator,
portRanges: [
{
fromPort: 443,
toPort: 443,
},
],
});
const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener });

// WHEN
ga.AcceleratorSecurityGroup.fromVpc(stack, 'GlobalAcceleratorSG', vpc, endpointGroup);

// THEN
expect(stack).to(haveResource('Custom::AWS', {
Properties: {
ServiceToken: {
'Fn::GetAtt': [
'AWS679f53fac002430cb0da5b7982bd22872D164C4C',
'Arn',
],
},
Create: {
action: 'describeSecurityGroups',
service: 'EC2',
parameters: {
Filters: [
{
Name: 'group-name',
Values: [
'GlobalAccelerator',
],
},
{
Name: 'vpc-id',
Values: [
{
Ref: 'VPCB9E5F0B4',
},
],
},
],
},
physicalResourceId: {
responsePath: 'SecurityGroups.0.GroupId',
},
},
},
DependsOn: [
'GroupC77FDACD',
],
}, ResourcePart.CompleteDefinition));
});

test('can create security group rule', () => {
// GIVEN
const { stack, alb, vpc } = testFixtureAlb();
const accelerator = new ga.Accelerator(stack, 'Accelerator');
const listener = new ga.Listener(stack, 'Listener', {
accelerator,
portRanges: [
{
fromPort: 443,
toPort: 443,
},
],
});
const endpointGroup = new ga.EndpointGroup(stack, 'Group', { listener });
endpointGroup.addLoadBalancer('endpoint', alb);

// WHEN
const sg = ga.AcceleratorSecurityGroup.fromVpc(stack, 'GlobalAcceleratorSG', vpc, endpointGroup);
alb.connections.allowFrom(sg, Port.tcp(443));

// THEN
expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', {
IpProtocol: 'tcp',
FromPort: 443,
GroupId: {
'Fn::GetAtt': [
'ALBSecurityGroup8B8624F8',
'GroupId',
],
},
SourceSecurityGroupId: {
'Fn::GetAtt': [
'GlobalAcceleratorSGCustomResourceC1DB5287',
'SecurityGroups.0.GroupId',
],
},
ToPort: 443,
}));
});
Loading

0 comments on commit 030da8d

Please sign in to comment.