Skip to content

Commit

Permalink
feat(ecs): Adding support for secretOptions in Firelens log driver (#…
Browse files Browse the repository at this point in the history
…15351)

This PR adds support for specifying secrets for the Firelens log driver. It adds the `secretOptions` property to the `LogConfiguration` of log drivers.

Fixes [#8174](#8174)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
upparekh authored Jun 29, 2021
1 parent 1f5360e commit c3096ea
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 4 deletions.
19 changes: 19 additions & 0 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,25 @@ taskDefinition.addContainer('TheContainer', {
});
```

To pass secrets to the log configuration, use the `secretOptions` property of the log configuration. The task execution role is automatically granted read permissions on the secrets/parameters.

```ts
const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef');
taskDefinition.addContainer('TheContainer', {
image: ecs.ContainerImage.fromRegistry('example-image'),
memoryLimitMiB: 256,
logging: ecs.LogDrivers.firelens({
options: {
// ... log driver options here ...
},
secretOptions: { // Retrieved from AWS Secrets Manager or AWS Systems Manager Parameter Store
apikey: ecs.Secret.fromSecretsManager(secret),
host: ecs.Secret.fromSsmParameter(parameter),
},
})
});
```

### Generic Log Driver

A generic log driver object exists to provide a lower level abstraction of the log driver configuration.
Expand Down
18 changes: 16 additions & 2 deletions packages/@aws-cdk/aws-ecs/lib/log-drivers/firelens-log-driver.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ContainerDefinition } from '../container-definition';
import { ContainerDefinition, Secret } from '../container-definition';
import { BaseLogDriverProps } from './base-log-driver';
import { LogDriver, LogDriverConfig } from './log-driver';
import { removeEmpty } from './utils';
import { removeEmpty, renderLogDriverSecretOptions } from './utils';

// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch.
// eslint-disable-next-line
Expand All @@ -16,6 +16,12 @@ export interface FireLensLogDriverProps extends BaseLogDriverProps {
* @default - the log driver options
*/
readonly options?: { [key: string]: string };

/**
* The secrets to pass to the log configuration.
* @default - No secret options provided.
*/
readonly secretOptions?: { [key: string]: Secret };
}

/**
Expand All @@ -29,6 +35,12 @@ export class FireLensLogDriver extends LogDriver {
*/
private options?: { [key: string]: string };

/**
* The secrets to pass to the log configuration.
* @default - No secret options provided.
*/
private secretOptions?: { [key: string]: Secret };

/**
* Constructs a new instance of the FireLensLogDriver class.
* @param props the awsfirelens log driver configuration options.
Expand All @@ -37,6 +49,7 @@ export class FireLensLogDriver extends LogDriver {
super();

this.options = props.options;
this.secretOptions = props.secretOptions;
}

/**
Expand All @@ -46,6 +59,7 @@ export class FireLensLogDriver extends LogDriver {
return {
logDriver: 'awsfirelens',
...(this.options && { options: removeEmpty(this.options) }),
secretOptions: this.secretOptions && renderLogDriverSecretOptions(this.secretOptions, _containerDefinition.taskDefinition),
};
}
}
18 changes: 16 additions & 2 deletions packages/@aws-cdk/aws-ecs/lib/log-drivers/generic-log-driver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ContainerDefinition } from '../container-definition';
import { ContainerDefinition, Secret } from '../container-definition';
import { LogDriver, LogDriverConfig } from '../index';
import { removeEmpty } from './utils';
import { removeEmpty, renderLogDriverSecretOptions } from './utils';

// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch.
// eslint-disable-next-line
Expand All @@ -25,8 +25,15 @@ export interface GenericLogDriverProps {

/**
* The configuration options to send to the log driver.
* @default - the log driver options.
*/
readonly options?: { [key: string]: string };

/**
* The secrets to pass to the log configuration.
* @default - no secret options provided.
*/
readonly secretOptions?: { [key: string]: Secret };
}

/**
Expand All @@ -49,6 +56,11 @@ export class GenericLogDriver extends LogDriver {
*/
private options: { [key: string]: string };

/**
* The secrets to pass to the log configuration.
*/
private secretOptions?: { [key: string]: Secret };

/**
* Constructs a new instance of the GenericLogDriver class.
*
Expand All @@ -59,6 +71,7 @@ export class GenericLogDriver extends LogDriver {

this.logDriver = props.logDriver;
this.options = props.options || {};
this.secretOptions = props.secretOptions;
}

/**
Expand All @@ -68,6 +81,7 @@ export class GenericLogDriver extends LogDriver {
return {
logDriver: this.logDriver,
options: removeEmpty(this.options),
secretOptions: this.secretOptions && renderLogDriverSecretOptions(this.secretOptions, _containerDefinition.taskDefinition),
};
}
}
7 changes: 7 additions & 0 deletions packages/@aws-cdk/aws-ecs/lib/log-drivers/log-driver.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ContainerDefinition } from '../container-definition';
import { CfnTaskDefinition } from '../ecs.generated';
import { AwsLogDriver, AwsLogDriverProps } from './aws-log-driver';

// v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch.
Expand Down Expand Up @@ -48,4 +49,10 @@ export interface LogDriverConfig {
* The configuration options to send to the log driver.
*/
readonly options?: { [key: string]: string };

/**
* The secrets to pass to the log configuration.
* @default - No secret options provided.
*/
readonly secretOptions?: CfnTaskDefinition.SecretProperty[];
}
16 changes: 16 additions & 0 deletions packages/@aws-cdk/aws-ecs/lib/log-drivers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Duration, SecretValue, Token } from '@aws-cdk/core';
import { TaskDefinition } from '../base/task-definition';
import { Secret } from '../container-definition';
import { CfnTaskDefinition } from '../ecs.generated';
import { BaseLogDriverProps } from './base-log-driver';

/**
Expand Down Expand Up @@ -55,3 +58,16 @@ export function renderCommonLogDriverOptions(opts: BaseLogDriverProps) {
export function joinWithCommas(xs?: string[]): string | undefined {
return xs && xs.join(',');
}

export function renderLogDriverSecretOptions(secretValue: { [key: string]: Secret }, taskDefinition: TaskDefinition):
CfnTaskDefinition.SecretProperty[] {
const secrets = [];
for (const [name, secret] of Object.entries(secretValue)) {
secret.grantRead(taskDefinition.obtainExecutionRole());
secrets.push({
name,
valueFrom: secret.arn,
});
}
return secrets;
}
135 changes: 135 additions & 0 deletions packages/@aws-cdk/aws-ecs/test/firelens-log-driver.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { expect, haveResourceLike } from '@aws-cdk/assert-internal';
import * as secretsmanager from '@aws-cdk/aws-secretsmanager';
import * as ssm from '@aws-cdk/aws-ssm';
import * as cdk from '@aws-cdk/core';
import { nodeunitShim, Test } from 'nodeunit-shim';
import * as ecs from '../lib';
Expand Down Expand Up @@ -42,6 +44,139 @@ nodeunitShim({
test.done();
},

'create a firelens log driver with secret options'(test: Test) {
const secret = new secretsmanager.Secret(stack, 'Secret');
const parameter = ssm.StringParameter.fromSecureStringParameterAttributes(stack, 'Parameter', {
parameterName: '/host',
version: 1,
});

// WHEN
td.addContainer('Container', {
image,
logging: ecs.LogDrivers.firelens({
options: {
Name: 'datadog',
TLS: 'on',
dd_service: 'my-httpd-service',
dd_source: 'httpd',
dd_tags: 'project:example',
provider: 'ecs',
},
secretOptions: {
apikey: ecs.Secret.fromSecretsManager(secret),
Host: ecs.Secret.fromSsmParameter(parameter),
},
}),
memoryLimitMiB: 128,
});

// THEN
expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', {
ContainerDefinitions: [
{
LogConfiguration: {
LogDriver: 'awsfirelens',
Options: {
Name: 'datadog',
TLS: 'on',
dd_service: 'my-httpd-service',
dd_source: 'httpd',
dd_tags: 'project:example',
provider: 'ecs',
},
SecretOptions: [
{
Name: 'apikey',
ValueFrom: {
Ref: 'SecretA720EF05',
},
},
{
Name: 'Host',
ValueFrom: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':ssm:',
{
Ref: 'AWS::Region',
},
':',
{
Ref: 'AWS::AccountId',
},
':parameter/host',
],
],
},
},
],
},
},
{
Essential: true,
FirelensConfiguration: {
Type: 'fluentbit',
},
},
],
}));

expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: [
'secretsmanager:GetSecretValue',
'secretsmanager:DescribeSecret',
],
Effect: 'Allow',
Resource: {
Ref: 'SecretA720EF05',
},
},
{
Action: [
'ssm:DescribeParameters',
'ssm:GetParameters',
'ssm:GetParameter',
'ssm:GetParameterHistory',
],
Effect: 'Allow',
Resource: {
'Fn::Join': [
'',
[
'arn:',
{
Ref: 'AWS::Partition',
},
':ssm:',
{
Ref: 'AWS::Region',
},
':',
{
Ref: 'AWS::AccountId',
},
':parameter/host',
],
],
},
},
],
Version: '2012-10-17',
},
}));

test.done();
},

'create a firelens log driver to route logs to CloudWatch Logs with Fluent Bit'(test: Test) {
// WHEN
td.addContainer('Container', {
Expand Down

0 comments on commit c3096ea

Please sign in to comment.