@aws-cdk/aws-msk-alpha
Version:
The CDK Construct Library for AWS::MSK
700 lines • 96.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
var useValue = arguments.length > 2;
for (var i = 0; i < initializers.length; i++) {
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
}
return useValue ? value : void 0;
};
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
var _, done = false;
for (var i = decorators.length - 1; i >= 0; i--) {
var context = {};
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
if (kind === "accessor") {
if (result === void 0) continue;
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
if (_ = accept(result.get)) descriptor.get = _;
if (_ = accept(result.set)) descriptor.set = _;
if (_ = accept(result.init)) initializers.unshift(_);
}
else if (_ = accept(result)) {
if (kind === "field") initializers.unshift(_);
else descriptor[key] = _;
}
}
if (target) Object.defineProperty(target, contextIn.name, descriptor);
done = true;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cluster = exports.ClientAuthentication = exports.ClientBrokerEncryption = exports.ClusterMonitoringLevel = exports.BrokerType = exports.StorageMode = exports.ClusterBase = void 0;
const jsiiDeprecationWarnings = require("../.warnings.jsii.js");
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
const ec2 = __importStar(require("aws-cdk-lib/aws-ec2"));
const iam = __importStar(require("aws-cdk-lib/aws-iam"));
const kms = __importStar(require("aws-cdk-lib/aws-kms"));
const aws_msk_1 = require("aws-cdk-lib/aws-msk");
const secretsmanager = __importStar(require("aws-cdk-lib/aws-secretsmanager"));
const core = __importStar(require("aws-cdk-lib/core"));
const core_1 = require("aws-cdk-lib/core");
const helpers_internal_1 = require("aws-cdk-lib/core/lib/helpers-internal");
const metadata_resource_1 = require("aws-cdk-lib/core/lib/metadata-resource");
const prop_injectable_1 = require("aws-cdk-lib/core/lib/prop-injectable");
const cr = __importStar(require("aws-cdk-lib/custom-resources"));
const cx_api_1 = require("aws-cdk-lib/cx-api");
const uniqueid_1 = require("constructs/lib/private/uniqueid");
/**
* A new or imported MSK Cluster.
*/
class ClusterBase extends core.Resource {
static [JSII_RTTI_SYMBOL_1] = { fqn: "@aws-cdk/aws-msk-alpha.ClusterBase", version: "2.260.0-alpha.0" };
/** @internal */
_connections;
/** Manages connections for the cluster */
get connections() {
if (!this._connections) {
throw new core.ValidationError((0, helpers_internal_1.lit) `ImportedClusterSecurityGroups`, 'An imported Cluster cannot manage its security groups', this);
}
return this._connections;
}
}
exports.ClusterBase = ClusterBase;
/**
* The storage mode for the cluster brokers.
*/
var StorageMode;
(function (StorageMode) {
/**
* Local storage mode utilizes network attached EBS storage.
*/
StorageMode["LOCAL"] = "LOCAL";
/**
* Tiered storage mode utilizes EBS storage and Tiered storage.
*/
StorageMode["TIERED"] = "TIERED";
})(StorageMode || (exports.StorageMode = StorageMode = {}));
/**
* The broker type for the cluster.
*/
var BrokerType;
(function (BrokerType) {
/**
* Standard brokers provide high-availability guarantees.
*/
BrokerType["STANDARD"] = "STANDARD";
/**
* Express brokers are a low-cost option for development, testing, and workloads that don't require the high availability guarantees of standard MSK cluster.
*/
BrokerType["EXPRESS"] = "EXPRESS";
})(BrokerType || (exports.BrokerType = BrokerType = {}));
/**
* The level of monitoring for the MSK cluster
*
* @see https://docs.aws.amazon.com/msk/latest/developerguide/monitoring.html#metrics-details
*/
var ClusterMonitoringLevel;
(function (ClusterMonitoringLevel) {
/**
* Default metrics are the essential metrics to monitor.
*/
ClusterMonitoringLevel["DEFAULT"] = "DEFAULT";
/**
* Per Broker metrics give you metrics at the broker level.
*/
ClusterMonitoringLevel["PER_BROKER"] = "PER_BROKER";
/**
* Per Topic Per Broker metrics help you understand volume at the topic level.
*/
ClusterMonitoringLevel["PER_TOPIC_PER_BROKER"] = "PER_TOPIC_PER_BROKER";
/**
* Per Topic Per Partition metrics help you understand consumer group lag at the topic partition level.
*/
ClusterMonitoringLevel["PER_TOPIC_PER_PARTITION"] = "PER_TOPIC_PER_PARTITION";
})(ClusterMonitoringLevel || (exports.ClusterMonitoringLevel = ClusterMonitoringLevel = {}));
/**
* Indicates the encryption setting for data in transit between clients and brokers.
*/
var ClientBrokerEncryption;
(function (ClientBrokerEncryption) {
/**
* TLS means that client-broker communication is enabled with TLS only.
*/
ClientBrokerEncryption["TLS"] = "TLS";
/**
* TLS_PLAINTEXT means that client-broker communication is enabled for both TLS-encrypted, as well as plaintext data.
*/
ClientBrokerEncryption["TLS_PLAINTEXT"] = "TLS_PLAINTEXT";
/**
* PLAINTEXT means that client-broker communication is enabled in plaintext only.
*/
ClientBrokerEncryption["PLAINTEXT"] = "PLAINTEXT";
})(ClientBrokerEncryption || (exports.ClientBrokerEncryption = ClientBrokerEncryption = {}));
/**
* Configuration properties for client authentication.
*/
class ClientAuthentication {
saslProps;
tlsProps;
static [JSII_RTTI_SYMBOL_1] = { fqn: "@aws-cdk/aws-msk-alpha.ClientAuthentication", version: "2.260.0-alpha.0" };
/**
* SASL authentication
*/
static sasl(props) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_msk_alpha_SaslAuthProps(props);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.sasl);
}
throw error;
}
return new ClientAuthentication(props, undefined);
}
/**
* TLS authentication
*/
static tls(props) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_msk_alpha_TlsAuthProps(props);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.tls);
}
throw error;
}
return new ClientAuthentication(undefined, props);
}
/**
* SASL + TLS authentication
*/
static saslTls(saslTlsProps) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_msk_alpha_SaslTlsAuthProps(saslTlsProps);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.saslTls);
}
throw error;
}
return new ClientAuthentication(saslTlsProps, saslTlsProps);
}
/**
* @param saslProps - properties for SASL authentication
* @param tlsProps - properties for TLS authentication
*/
constructor(saslProps, tlsProps) {
this.saslProps = saslProps;
this.tlsProps = tlsProps;
}
}
exports.ClientAuthentication = ClientAuthentication;
/**
* Create a MSK Cluster.
*
* @resource AWS::MSK::Cluster
*/
let Cluster = (() => {
let _classDecorators = [prop_injectable_1.propertyInjectable];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
let _classSuper = ClusterBase;
let _instanceExtraInitializers = [];
let _get_clusterName_decorators;
let _get_clusterArn_decorators;
let _addUser_decorators;
var Cluster = class extends _classSuper {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
_get_clusterName_decorators = [helpers_internal_1.memoizedGetter];
_get_clusterArn_decorators = [helpers_internal_1.memoizedGetter];
_addUser_decorators = [(0, metadata_resource_1.MethodMetadata)()];
__esDecorate(this, null, _get_clusterName_decorators, { kind: "getter", name: "clusterName", static: false, private: false, access: { has: obj => "clusterName" in obj, get: obj => obj.clusterName }, metadata: _metadata }, null, _instanceExtraInitializers);
__esDecorate(this, null, _get_clusterArn_decorators, { kind: "getter", name: "clusterArn", static: false, private: false, access: { has: obj => "clusterArn" in obj, get: obj => obj.clusterArn }, metadata: _metadata }, null, _instanceExtraInitializers);
__esDecorate(this, null, _addUser_decorators, { kind: "method", name: "addUser", static: false, private: false, access: { has: obj => "addUser" in obj, get: obj => obj.addUser }, metadata: _metadata }, null, _instanceExtraInitializers);
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
Cluster = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
}
static [JSII_RTTI_SYMBOL_1] = { fqn: "@aws-cdk/aws-msk-alpha.Cluster", version: "2.260.0-alpha.0" };
/** Uniquely identifies this class. */
static PROPERTY_INJECTION_ID = '@aws-cdk.aws-msk-alpha.Cluster';
/**
* Reference an existing cluster, defined outside of the CDK code, by name.
*/
static fromClusterArn(scope, id, clusterArn) {
class Import extends ClusterBase {
clusterArn = clusterArn;
clusterName = core.Fn.select(1, core.Fn.split('/', clusterArn)); // ['arn:partition:kafka:region:account-id', clusterName, clusterId]
}
return new Import(scope, id);
}
/** Key used to encrypt SASL/SCRAM users */
saslScramAuthenticationKey = __runInitializers(this, _instanceExtraInitializers);
resource;
_clusterDescription;
_clusterBootstrapBrokers;
constructor(scope, id, props) {
super(scope, id, { physicalName: props.clusterName });
try {
jsiiDeprecationWarnings._aws_cdk_aws_msk_alpha_ClusterProps(props);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, Cluster);
}
throw error;
}
// Enhanced CDK Analytics Telemetry
(0, metadata_resource_1.addConstructMetadata)(this, props);
const subnetSelection = props.vpc.selectSubnets(props.vpcSubnets);
this._connections = new ec2.Connections({
securityGroups: props.securityGroups ?? [
new ec2.SecurityGroup(this, 'SecurityGroup', {
description: 'MSK security group',
vpc: props.vpc,
}),
],
});
if (subnetSelection.subnets.length < 2) {
throw new core.ValidationError((0, helpers_internal_1.lit) `InsufficientSubnets`, `Cluster requires at least 2 subnets, got ${subnetSelection.subnets.length}`, this);
}
if (props.encryptionInTransit?.clientBroker === ClientBrokerEncryption.PLAINTEXT && props.clientAuthentication) {
throw new core.ValidationError((0, helpers_internal_1.lit) `ClientAuthRequiresTls`, 'To enable client authentication, you must enabled TLS-encrypted traffic between clients and brokers.', this);
}
else if (props.encryptionInTransit?.clientBroker ===
ClientBrokerEncryption.TLS_PLAINTEXT &&
(props.clientAuthentication?.saslProps?.scram ||
props.clientAuthentication?.saslProps?.iam)) {
throw new core.ValidationError((0, helpers_internal_1.lit) `SaslAuthRequiresTlsOnly`, 'To enable SASL/SCRAM or IAM authentication, you must only allow TLS-encrypted traffic between clients and brokers.', this);
}
const volumeSize = props.ebsStorageInfo?.volumeSize ?? 1000;
// Minimum: 1 GiB, maximum: 16384 GiB
if (volumeSize < 1 || volumeSize > 16384) {
throw new core.ValidationError((0, helpers_internal_1.lit) `InvalidEbsVolumeSize`, 'EBS volume size should be in the range 1-16384', this);
}
const isExpress = props.brokerType === BrokerType.EXPRESS;
if (isExpress) {
// Validate Kafka version compatibility
// express broker documentation for supported versions https://docs.aws.amazon.com/msk/latest/developerguide/msk-broker-types-express.html#msk-broker-types-express-notes
const supportedVersions = ['3.6', '3.8', '3.9'];
const kafkaVersionString = props.kafkaVersion.version;
const isCompatibleVersion = supportedVersions.some(version => kafkaVersionString.includes(version));
if (!isCompatibleVersion) {
throw new core.ValidationError((0, helpers_internal_1.lit) `ExpressBrokerIncompatibleVersion`, `Express brokers are only supported with Apache Kafka ${supportedVersions.join(', ')}, got ${kafkaVersionString}`, this);
}
if (!props.instanceType) {
throw new core.ValidationError((0, helpers_internal_1.lit) `ExpressBrokerRequiresInstanceType`, '`instanceType` must also be specified when `brokerType` is `BrokerType.EXPRESS`.', this);
}
if (props.ebsStorageInfo) {
throw new core.ValidationError((0, helpers_internal_1.lit) `ExpressBrokerNoEbsStorage`, '`ebsStorageInfo` is not supported when `brokerType` is `BrokerType.EXPRESS`.', this);
}
if (props.storageMode) {
throw new core.ValidationError((0, helpers_internal_1.lit) `ExpressBrokerNoStorageMode`, '`storageMode` is not supported when `brokerType` is `BrokerType.EXPRESS`.', this);
}
if (props.logging) {
throw new core.ValidationError((0, helpers_internal_1.lit) `ExpressBrokerNoLogging`, '`logging` is not supported when `brokerType` is `BrokerType.EXPRESS`.', this);
}
if (subnetSelection.subnets.length < 3) {
throw new core.ValidationError((0, helpers_internal_1.lit) `ExpressClusterInsufficientSubnets`, `Express cluster requires at least 3 subnets, got ${subnetSelection.subnets.length}`, this);
}
}
const instanceType = props.instanceType
? this.mskInstanceType(props.instanceType, isExpress)
: this.mskInstanceType(ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE));
if (props.storageMode && props.storageMode === StorageMode.TIERED) {
if (!props.kafkaVersion.isTieredStorageCompatible()) {
throw new core.ValidationError((0, helpers_internal_1.lit) `TieredStorageIncompatibleVersion`, `To deploy a tiered cluster you must select a compatible Kafka version, got ${props.kafkaVersion.version}`, this);
}
if (instanceType === this.mskInstanceType(ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL))) {
throw new core.ValidationError((0, helpers_internal_1.lit) `TieredStorageIncompatibleInstanceType`, 'Tiered storage doesn\'t support broker type t3.small', this);
}
}
const encryptionAtRest = props.ebsStorageInfo?.encryptionKey
? {
dataVolumeKmsKeyId: props.ebsStorageInfo.encryptionKey.keyRef.keyId,
}
: undefined; // MSK will create the managed key
const encryptionInTransit = {
clientBroker: props.encryptionInTransit?.clientBroker ??
ClientBrokerEncryption.TLS,
inCluster: props.encryptionInTransit?.enableInCluster ?? true,
};
const openMonitoring = props.monitoring?.enablePrometheusJmxExporter ||
props.monitoring?.enablePrometheusNodeExporter
? {
prometheus: {
jmxExporter: props.monitoring?.enablePrometheusJmxExporter
? { enabledInBroker: true }
: undefined,
nodeExporter: props.monitoring
?.enablePrometheusNodeExporter
? { enabledInBroker: true }
: undefined,
},
}
: undefined;
const loggingBucket = props.logging?.s3?.bucket;
if (loggingBucket && core_1.FeatureFlags.of(this).isEnabled(cx_api_1.S3_CREATE_DEFAULT_LOGGING_POLICY)) {
const stack = core.Stack.of(this);
loggingBucket.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [
new iam.ServicePrincipal('delivery.logs.amazonaws.com'),
],
resources: [
loggingBucket.arnForObjects(`AWSLogs/${stack.account}/*`),
],
actions: ['s3:PutObject'],
conditions: {
StringEquals: {
's3:x-amz-acl': 'bucket-owner-full-control',
'aws:SourceAccount': stack.account,
},
ArnLike: {
'aws:SourceArn': stack.formatArn({
service: 'logs',
resource: '*',
}),
},
},
}));
loggingBucket.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [
new iam.ServicePrincipal('delivery.logs.amazonaws.com'),
],
resources: [loggingBucket.bucketArn],
actions: [
's3:GetBucketAcl',
's3:ListBucket',
],
conditions: {
StringEquals: {
'aws:SourceAccount': stack.account,
},
ArnLike: {
'aws:SourceArn': stack.formatArn({
service: 'logs',
resource: '*',
}),
},
},
}));
}
const loggingInfo = isExpress ? undefined : {
brokerLogs: {
cloudWatchLogs: {
enabled: props.logging?.cloudwatchLogGroup !== undefined,
logGroup: props.logging?.cloudwatchLogGroup?.logGroupName,
},
firehose: {
enabled: props.logging?.firehoseDeliveryStreamName !==
undefined,
deliveryStream: props.logging?.firehoseDeliveryStreamName,
},
s3: {
enabled: loggingBucket !== undefined,
bucket: loggingBucket?.bucketName,
prefix: props.logging?.s3?.prefix,
},
},
};
if (props.clientAuthentication?.saslProps?.scram && props.clientAuthentication?.saslProps?.key === undefined) {
this.saslScramAuthenticationKey = new kms.Key(this, 'SASLKey', {
description: 'Used for encrypting MSK secrets for SASL/SCRAM authentication.',
alias: `msk/${props.clusterName}/sasl/scram`,
});
// https://docs.aws.amazon.com/kms/latest/developerguide/services-secrets-manager.html#asm-policies
this.saslScramAuthenticationKey.addToResourcePolicy(new iam.PolicyStatement({
sid: 'Allow access through AWS Secrets Manager for all principals in the account that are authorized to use AWS Secrets Manager',
principals: [new iam.AnyPrincipal()],
actions: [
'kms:Encrypt',
'kms:Decrypt',
'kms:ReEncrypt*',
'kms:GenerateDataKey*',
'kms:CreateGrant',
'kms:DescribeKey',
],
resources: ['*'],
conditions: {
StringEquals: {
'kms:ViaService': `secretsmanager.${core.Stack.of(this).region}.amazonaws.com`,
'kms:CallerAccount': core.Stack.of(this).account,
},
},
}));
}
let clientAuthentication;
if (props.clientAuthentication) {
const { saslProps, tlsProps } = props.clientAuthentication;
clientAuthentication = {
sasl: saslProps ? {
iam: saslProps.iam ? { enabled: true } : undefined,
scram: saslProps.scram ? { enabled: true } : undefined,
} : undefined,
tls: tlsProps?.certificateAuthorities ? {
certificateAuthorityArnList: tlsProps.certificateAuthorities?.map((ca) => ca.certificateAuthorityArn),
} : undefined,
};
}
const resource = new aws_msk_1.CfnCluster(this, 'Resource', {
clusterName: props.clusterName,
kafkaVersion: props.kafkaVersion.version,
numberOfBrokerNodes: props.numberOfBrokerNodes !== undefined ?
subnetSelection.availabilityZones.length * props.numberOfBrokerNodes : subnetSelection.availabilityZones.length,
brokerNodeGroupInfo: {
instanceType,
clientSubnets: subnetSelection.subnetIds,
securityGroups: this.connections.securityGroups.map((group) => group.securityGroupId),
storageInfo: isExpress ? undefined : {
ebsStorageInfo: {
volumeSize: volumeSize,
},
},
},
encryptionInfo: {
encryptionAtRest,
encryptionInTransit,
},
configurationInfo: props.configurationInfo,
enhancedMonitoring: props.monitoring?.clusterMonitoringLevel,
openMonitoring: openMonitoring,
storageMode: isExpress ? undefined : props.storageMode,
loggingInfo: loggingInfo,
clientAuthentication,
});
this.resource = resource;
resource.applyRemovalPolicy(props.removalPolicy, {
default: core.RemovalPolicy.RETAIN,
});
}
get clusterName() {
return this.getResourceNameAttribute(core.Fn.select(1, core.Fn.split('/', this.resource.ref)));
}
get clusterArn() {
return this.resource.ref;
}
mskInstanceType(instanceType, express) {
const prefix = express ? 'express.' : 'kafka.';
return `${prefix}${instanceType.toString()}`;
}
/**
* Get the ZooKeeper Connection string
*
* Uses a Custom Resource to make an API call to `describeCluster` using the Javascript SDK
*
* @param responseField Field to return from API call. eg. ZookeeperConnectString, ZookeeperConnectStringTls
* @returns - The connection string to use to connect to the Apache ZooKeeper cluster.
*/
_zookeeperConnectionString(responseField) {
if (!this._clusterDescription) {
this._clusterDescription = new cr.AwsCustomResource(this, 'ZookeeperConnect', {
onUpdate: {
service: 'Kafka',
action: 'describeCluster',
parameters: {
ClusterArn: this.clusterArn,
},
physicalResourceId: cr.PhysicalResourceId.of('ZooKeeperConnectionString'),
// Limit the output of describeCluster that is otherwise too large
outputPaths: [
'ClusterInfo.ZookeeperConnectString',
'ClusterInfo.ZookeeperConnectStringTls',
],
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: [this.clusterArn],
}),
installLatestAwsSdk: false,
});
}
return this._clusterDescription.getResponseField(`ClusterInfo.${responseField}`);
}
/**
* Get the ZooKeeper Connection string
*
* Uses a Custom Resource to make an API call to `describeCluster` using the Javascript SDK
*
* @returns - The connection string to use to connect to the Apache ZooKeeper cluster.
*/
get zookeeperConnectionString() {
return this._zookeeperConnectionString('ZookeeperConnectString');
}
/**
* Get the ZooKeeper Connection string for a TLS enabled cluster
*
* Uses a Custom Resource to make an API call to `describeCluster` using the Javascript SDK
*
* @returns - The connection string to use to connect to zookeeper cluster on TLS port.
*/
get zookeeperConnectionStringTls() {
return this._zookeeperConnectionString('ZookeeperConnectStringTls');
}
/**
* Get the list of brokers that a client application can use to bootstrap
*
* Uses a Custom Resource to make an API call to `getBootstrapBrokers` using the Javascript SDK
*
* @param responseField Field to return from API call. eg. BootstrapBrokerStringSaslScram, BootstrapBrokerString
* @returns - A string containing one or more hostname:port pairs.
*/
_bootstrapBrokers(responseField) {
if (!this._clusterBootstrapBrokers) {
this._clusterBootstrapBrokers = new cr.AwsCustomResource(this, `BootstrapBrokers${responseField}`, {
onUpdate: {
service: 'Kafka',
action: 'getBootstrapBrokers',
parameters: {
ClusterArn: this.clusterArn,
},
physicalResourceId: cr.PhysicalResourceId.of('BootstrapBrokers'),
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: [this.clusterArn],
}),
// APIs are available in 2.1055.0
installLatestAwsSdk: false,
});
}
return this._clusterBootstrapBrokers.getResponseField(responseField);
}
/**
* Get the list of brokers that a client application can use to bootstrap
*
* Uses a Custom Resource to make an API call to `getBootstrapBrokers` using the Javascript SDK
*
* @returns - A string containing one or more hostname:port pairs.
*/
get bootstrapBrokers() {
return this._bootstrapBrokers('BootstrapBrokerString');
}
/**
* Get the list of brokers that a TLS authenticated client application can use to bootstrap
*
* Uses a Custom Resource to make an API call to `getBootstrapBrokers` using the Javascript SDK
*
* @returns - A string containing one or more DNS names (or IP) and TLS port pairs.
*/
get bootstrapBrokersTls() {
return this._bootstrapBrokers('BootstrapBrokerStringTls');
}
/**
* Get the list of brokers that a SASL/SCRAM authenticated client application can use to bootstrap
*
* Uses a Custom Resource to make an API call to `getBootstrapBrokers` using the Javascript SDK
*
* @returns - A string containing one or more dns name (or IP) and SASL SCRAM port pairs.
*/
get bootstrapBrokersSaslScram() {
return this._bootstrapBrokers('BootstrapBrokerStringSaslScram');
}
/**
* Get the list of brokers that a SASL/IAM authenticated client application can use to bootstrap
*
* Uses a Custom Resource to make an API call to `getBootstrapBrokers` using the Javascript SDK
*
* @returns - A string containing one or more DNS names (or IP) and TLS port pairs.
*/
get bootstrapBrokersSaslIam() {
return this._bootstrapBrokers('BootstrapBrokerStringSaslIam');
}
/**
* A list of usersnames to register with the cluster. The password will automatically be generated using Secrets
* Manager and the { username, password } JSON object stored in Secrets Manager as `AmazonMSK_username`.
*
* Must be using the SASL/SCRAM authentication mechanism.
*
* @param usernames - username(s) to register with the cluster
*/
addUser(...usernames) {
if (this.saslScramAuthenticationKey) {
const MSK_SECRET_PREFIX = 'AmazonMSK_'; // Required
const secrets = usernames.map((username) => new secretsmanager.Secret(this, `KafkaUser${username}`, {
secretName: `${MSK_SECRET_PREFIX}${this.clusterName}_${username}`,
generateSecretString: {
secretStringTemplate: JSON.stringify({ username }),
generateStringKey: 'password',
},
encryptionKey: this.saslScramAuthenticationKey,
}));
new cr.AwsCustomResource(this, `BatchAssociateScramSecrets${(0, uniqueid_1.addressOf)(usernames)}`, {
onUpdate: {
service: 'Kafka',
action: 'batchAssociateScramSecret',
parameters: {
ClusterArn: this.clusterArn,
SecretArnList: secrets.map((secret) => secret.secretArn),
},
physicalResourceId: cr.PhysicalResourceId.of('CreateUsers'),
},
policy: cr.AwsCustomResourcePolicy.fromStatements([
new iam.PolicyStatement({
actions: ['kms:CreateGrant'],
resources: [this.saslScramAuthenticationKey?.keyArn],
}),
new iam.PolicyStatement({
actions: ['kafka:BatchAssociateScramSecret'],
resources: [this.clusterArn],
}),
]),
installLatestAwsSdk: false,
});
}
else {
throw new core.ValidationError((0, helpers_internal_1.lit) `MissingAuthenticationKmsKey`, 'Cannot create users if an authentication KMS key has not been created/provided.', this);
}
}
static {
__runInitializers(_classThis, _classExtraInitializers);
}
};
return Cluster = _classThis;
})();
exports.Cluster = Cluster;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2x1c3Rlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImNsdXN0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQ0EseURBQTJDO0FBQzNDLHlEQUEyQztBQUMzQyx5REFBMkM7QUFFM0MsaURBQWlEO0FBRWpELCtFQUFpRTtBQUNqRSx1REFBeUM7QUFDekMsMkNBQWdEO0FBQ2hELDRFQUE0RTtBQUM1RSw4RUFBOEY7QUFDOUYsMEVBQTBFO0FBQzFFLGlFQUFtRDtBQUNuRCwrQ0FBc0U7QUFFdEUsOERBQTREO0FBc0I1RDs7R0FFRztBQUNILE1BQXNCLFdBQVksU0FBUSxJQUFJLENBQUMsUUFBUTs7SUFHckQsZ0JBQWdCO0lBQ04sWUFBWSxDQUE4QjtJQUVwRCwwQ0FBMEM7SUFDMUMsSUFBVyxXQUFXO1FBQ3BCLElBQUksQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDdkIsTUFBTSxJQUFJLElBQUksQ0FBQyxlQUFlLENBQUMsSUFBQSxzQkFBRyxFQUFBLCtCQUErQixFQUFFLHVEQUF1RCxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3BJLENBQUM7UUFDRCxPQUFPLElBQUksQ0FBQyxZQUFZLENBQUM7S0FDMUI7O0FBWkgsa0NBYUM7QUE4SUQ7O0dBRUc7QUFDSCxJQUFZLFdBVVg7QUFWRCxXQUFZLFdBQVc7SUFDckI7O09BRUc7SUFDSCw4QkFBZSxDQUFBO0lBRWY7O09BRUc7SUFDSCxnQ0FBaUIsQ0FBQTtBQUNuQixDQUFDLEVBVlcsV0FBVywyQkFBWCxXQUFXLFFBVXRCO0FBRUQ7O0dBRUc7QUFDSCxJQUFZLFVBVVg7QUFWRCxXQUFZLFVBQVU7SUFDcEI7O09BRUc7SUFDSCxtQ0FBcUIsQ0FBQTtJQUVyQjs7T0FFRztJQUNILGlDQUFtQixDQUFBO0FBQ3JCLENBQUMsRUFWVyxVQUFVLDBCQUFWLFVB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