Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions packages/@aws-cdk/aws-glue-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,9 @@ A `Connection` allows Glue jobs, crawlers and development endpoints to access
certain types of data stores.

* **Secrets Management**
You must specify JDBC connection credentials in Secrets Manager and
provide the Secrets Manager Key name as a property to the job connection.
Manage JDBC connection credentials in Secrets Manager and pass the secret
to the connection via the `secret` property (see the example below), rather
than embedding credentials in `properties`.

* **Networking - the CDK determines the best fit subnet for Glue connection
configuration**
Expand All @@ -464,7 +465,7 @@ new glue.Connection(this, 'MyConnection', {
});
```

For RDS `Connection` by JDBC, it is recommended to manage credentials using AWS Secrets Manager. To use Secret, specify `SECRET_ID` in `properties` like the following code. Note that in this case, the subnet must have a route to the AWS Secrets Manager VPC endpoint or to the AWS Secrets Manager endpoint through a NAT gateway.
For RDS `Connection` by JDBC, it is recommended to manage credentials using AWS Secrets Manager. Pass the secret via the `secret` property: Glue reads the credentials at runtime through the connection's `SECRET_ID`, so the secret value never enters the template. Note that in this case, the subnet must have a route to the AWS Secrets Manager VPC endpoint or to the AWS Secrets Manager endpoint through a NAT gateway.

```ts
declare const securityGroup: ec2.SecurityGroup;
Expand All @@ -474,18 +475,18 @@ new glue.Connection(this, "RdsConnection", {
type: glue.ConnectionType.JDBC,
securityGroups: [securityGroup],
subnet,
secret: db.secret,
properties: {
JDBC_CONNECTION_URL: `jdbc:mysql://${db.clusterEndpoint.socketAddress}/databasename`,
JDBC_ENFORCE_SSL: "false",
SECRET_ID: db.secret!.secretName,
},
});
```

Connection `properties` are emitted verbatim into the CloudFormation template, so
any credential placed there in plaintext is stored in plaintext in the template,
`cdk.out`, and source control. Reference a Secrets Manager secret through
`SECRET_ID` (as above) instead. If a property key looks like a credential (for
Prefer the `secret` property over placing credentials in `properties`. Connection
`properties` are emitted verbatim into the CloudFormation template, so any
credential placed there in plaintext is stored in plaintext in the template,
`cdk.out`, and source control. If a property key looks like a credential (for
example `PASSWORD`, `SECRET`, or `TOKEN`) and holds a plaintext literal, the
construct emits a synthesis-time warning.

Expand Down
31 changes: 27 additions & 4 deletions packages/@aws-cdk/aws-glue-alpha/lib/connection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type * as ec2 from 'aws-cdk-lib/aws-ec2';
import { CfnConnection } from 'aws-cdk-lib/aws-glue';
import type { ISecretRef } from 'aws-cdk-lib/aws-secretsmanager';
import * as cdk from 'aws-cdk-lib/core';
import { memoizedGetter } from 'aws-cdk-lib/core/lib/helpers-internal';
import { memoizedGetter, lit } from 'aws-cdk-lib/core/lib/helpers-internal';
import { addConstructMetadata, MethodMetadata } from 'aws-cdk-lib/core/lib/metadata-resource';
import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable';
import type * as constructs from 'constructs';
Expand Down Expand Up @@ -275,6 +276,18 @@ export interface ConnectionOptions {
*/
readonly properties?: { [key: string]: string };

/**
* A reference to a Secrets Manager secret holding the credentials for this connection.
*
* The secret is referenced through the connection's `SECRET_ID` property, so
* Glue reads the credentials at runtime and the secret value never appears in
* the synthesized template. Prefer this over placing credentials directly in
* `properties`. Accepts any `secretsmanager.ISecret`.
*
* @default - no secret; any credentials must be supplied via `properties`
*/
readonly secret?: ISecretRef;

/**
* A list of criteria that can be used in selecting this connection.
* This is useful for filtering the results of https://awscli.amazonaws.com/v2/documentation/api/latest/reference/glue/get-connections.html
Expand Down Expand Up @@ -365,6 +378,11 @@ export class Connection extends cdk.Resource implements IConnection {

this.properties = props.properties || {};

if (props.secret !== undefined && this.properties.SECRET_ID !== undefined) {
throw new cdk.ValidationError(lit`ConnectionSecretConflict`, 'cannot set both `secret` and a `SECRET_ID` connection property', this);
}
const secretId = props.secret?.secretRef.secretId;

const physicalConnectionRequirements = props.subnet || props.securityGroups ? {
availabilityZone: props.subnet ? props.subnet.availabilityZone : undefined,
subnetId: props.subnet ? props.subnet.subnetId : undefined,
Expand All @@ -377,14 +395,19 @@ export class Connection extends cdk.Resource implements IConnection {
connectionProperties: cdk.Lazy.any({
produce: () => {
// Inspect the final property set at synthesis time so properties
// added via `addProperty` are covered as well.
// added via `addProperty` are covered as well. The `SECRET_ID`
// injected from `secret` is a reference, not a credential, so it is
// merged in after this scan.
warnOnPlaintextSecrets(
this,
this.properties,
'@aws-cdk/aws-glue-alpha:plaintextConnectionSecret',
'Reference a Secrets Manager secret through the connection\'s `SECRET_ID` property instead.',
'Pass a Secrets Manager secret via the connection\'s `secret` property instead.',
);
return Object.keys(this.properties).length > 0 ? this.properties : undefined;
const properties = secretId !== undefined
? { ...this.properties, SECRET_ID: secretId }
: this.properties;
return Object.keys(properties).length > 0 ? properties : undefined;
},
}),
connectionType: props.type.name,
Expand Down
51 changes: 50 additions & 1 deletion packages/@aws-cdk/aws-glue-alpha/test/connection.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as cdk from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import { Annotations, Match, Template } from 'aws-cdk-lib/assertions';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import * as glue from '../lib';

test('a connection with connection properties', () => {
Expand Down Expand Up @@ -135,6 +136,54 @@ test('addProperty', () => {
});
});

test('a secret is wired to the SECRET_ID connection property', () => {
const stack = new cdk.Stack();
const secret = new secretsmanager.Secret(stack, 'Secret');

new glue.Connection(stack, 'Connection', {
type: glue.ConnectionType.JDBC,
properties: { JDBC_CONNECTION_URL: 'jdbc:server://server:443/connection' },
secret,
});

Template.fromStack(stack).hasResourceProperties('AWS::Glue::Connection', {
ConnectionInput: {
ConnectionProperties: {
JDBC_CONNECTION_URL: 'jdbc:server://server:443/connection',
SECRET_ID: stack.resolve(secret.secretRef.secretId),
},
},
});
});

test('setting both `secret` and a SECRET_ID property throws', () => {
const stack = new cdk.Stack();
const secret = new secretsmanager.Secret(stack, 'Secret');

expect(() => new glue.Connection(stack, 'Connection', {
type: glue.ConnectionType.JDBC,
secret,
properties: { SECRET_ID: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:other' },
})).toThrow('cannot set both `secret` and a `SECRET_ID` connection property');
});

test('referencing a secret does not trigger the plaintext-secret warning', () => {
const stack = new cdk.Stack();
// An imported secret has a literal ARN; the SECRET_ID it populates is a
// reference, not a credential, so it must not be flagged.
const secret = secretsmanager.Secret.fromSecretCompleteArn(
stack, 'Secret', 'arn:aws:secretsmanager:us-east-1:123456789012:secret:creds-AbCdEf');

new glue.Connection(stack, 'Connection', {
type: glue.ConnectionType.JDBC,
secret,
});

Annotations.fromStack(stack).hasNoWarning(
'/Default/Connection',
Match.stringLikeRegexp('.*plaintext secret.*'));
});

test('fromConnectionName', () => {
const connectionName = 'name';
const stack = new cdk.Stack();
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading