@aws-cdk/aws-s3
Version:
The CDK Construct Library for AWS::S3
1,195 lines (1,194 loc) • 224 kB
JavaScript
"use strict";
var _a, _b, _c, _d;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReplaceKey = exports.BucketAccessControl = exports.EventType = exports.BucketEncryption = exports.Bucket = exports.ObjectOwnership = exports.InventoryObjectVersion = exports.InventoryFrequency = exports.InventoryFormat = exports.RedirectProtocol = exports.HttpMethods = exports.BlockPublicAccess = exports.BucketBase = void 0;
const jsiiDeprecationWarnings = require("../.warnings.jsii.js");
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
const os_1 = require("os");
const path = require("path");
const events = require("@aws-cdk/aws-events");
const iam = require("@aws-cdk/aws-iam");
const kms = require("@aws-cdk/aws-kms");
const core_1 = require("@aws-cdk/core");
const cxapi = require("@aws-cdk/cx-api");
const bucket_policy_1 = require("./bucket-policy");
const notifications_resource_1 = require("./notifications-resource");
const perms = require("./perms");
const s3_generated_1 = require("./s3.generated");
const util_1 = require("./util");
const AUTO_DELETE_OBJECTS_RESOURCE_TYPE = 'Custom::S3AutoDeleteObjects';
const AUTO_DELETE_OBJECTS_TAG = 'aws-cdk:auto-delete-objects';
/**
* Represents an S3 Bucket.
*
* Buckets can be either defined within this stack:
*
* new Bucket(this, 'MyBucket', { props });
*
* Or imported from an existing bucket:
*
* Bucket.import(this, 'MyImportedBucket', { bucketArn: ... });
*
* You can also export a bucket and import it into another stack:
*
* const ref = myBucket.export();
* Bucket.import(this, 'MyImportedBucket', ref);
*
*/
class BucketBase extends core_1.Resource {
constructor(scope, id, props = {}) {
super(scope, id, props);
}
/**
* Define a CloudWatch event that triggers when something happens to this repository
*
* Requires that there exists at least one CloudTrail Trail in your account
* that captures the event. This method will not create the Trail.
*
* @param id The id of the rule
* @param options Options for adding the rule
*/
onCloudTrailEvent(id, options = {}) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_OnCloudTrailBucketEventOptions(options);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.onCloudTrailEvent);
}
throw error;
}
const rule = new events.Rule(this, id, options);
rule.addTarget(options.target);
rule.addEventPattern({
source: ['aws.s3'],
detailType: ['AWS API Call via CloudTrail'],
detail: {
resources: {
ARN: options.paths?.map(p => this.arnForObjects(p)) ?? [this.bucketArn],
},
},
});
return rule;
}
/**
* Defines an AWS CloudWatch event that triggers when an object is uploaded
* to the specified paths (keys) in this bucket using the PutObject API call.
*
* Note that some tools like `aws s3 cp` will automatically use either
* PutObject or the multipart upload API depending on the file size,
* so using `onCloudTrailWriteObject` may be preferable.
*
* Requires that there exists at least one CloudTrail Trail in your account
* that captures the event. This method will not create the Trail.
*
* @param id The id of the rule
* @param options Options for adding the rule
*/
onCloudTrailPutObject(id, options = {}) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_OnCloudTrailBucketEventOptions(options);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.onCloudTrailPutObject);
}
throw error;
}
const rule = this.onCloudTrailEvent(id, options);
rule.addEventPattern({
detail: {
eventName: ['PutObject'],
},
});
return rule;
}
/**
* Defines an AWS CloudWatch event that triggers when an object at the
* specified paths (keys) in this bucket are written to. This includes
* the events PutObject, CopyObject, and CompleteMultipartUpload.
*
* Note that some tools like `aws s3 cp` will automatically use either
* PutObject or the multipart upload API depending on the file size,
* so using this method may be preferable to `onCloudTrailPutObject`.
*
* Requires that there exists at least one CloudTrail Trail in your account
* that captures the event. This method will not create the Trail.
*
* @param id The id of the rule
* @param options Options for adding the rule
*/
onCloudTrailWriteObject(id, options = {}) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_OnCloudTrailBucketEventOptions(options);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.onCloudTrailWriteObject);
}
throw error;
}
const rule = this.onCloudTrailEvent(id, options);
rule.addEventPattern({
detail: {
eventName: [
'CompleteMultipartUpload',
'CopyObject',
'PutObject',
],
requestParameters: {
bucketName: [this.bucketName],
key: options.paths,
},
},
});
return rule;
}
/**
* Adds a statement to the resource policy for a principal (i.e.
* account/role/service) to perform actions on this bucket and/or its
* contents. Use `bucketArn` and `arnForObjects(keys)` to obtain ARNs for
* this bucket or objects.
*
* Note that the policy statement may or may not be added to the policy.
* For example, when an `IBucket` is created from an existing bucket,
* it's not possible to tell whether the bucket already has a policy
* attached, let alone to re-use that policy to add more statements to it.
* So it's safest to do nothing in these cases.
*
* @param permission the policy statement to be added to the bucket's
* policy.
* @returns metadata about the execution of this method. If the policy
* was not added, the value of `statementAdded` will be `false`. You
* should always check this value to make sure that the operation was
* actually carried out. Otherwise, synthesis and deploy will terminate
* silently, which may be confusing.
*/
addToResourcePolicy(permission) {
if (!this.policy && this.autoCreatePolicy) {
this.policy = new bucket_policy_1.BucketPolicy(this, 'Policy', { bucket: this });
}
if (this.policy) {
this.policy.document.addStatements(permission);
return { statementAdded: true, policyDependable: this.policy };
}
return { statementAdded: false };
}
validate() {
const errors = super.validate();
errors.push(...this.policy?.document.validateForResourcePolicy() || []);
return errors;
}
/**
* The https URL of an S3 object. Specify `regional: false` at the options
* for non-regional URLs. For example:
*
* - `https://s3.us-west-1.amazonaws.com/onlybucket`
* - `https://s3.us-west-1.amazonaws.com/bucket/key`
* - `https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey`
*
* @param key The S3 key of the object. If not specified, the URL of the
* bucket is returned.
* @returns an ObjectS3Url token
*/
urlForObject(key) {
const stack = core_1.Stack.of(this);
const prefix = `https://s3.${this.env.region}.${stack.urlSuffix}/`;
if (typeof key !== 'string') {
return this.urlJoin(prefix, this.bucketName);
}
return this.urlJoin(prefix, this.bucketName, key);
}
/**
* The https Transfer Acceleration URL of an S3 object. Specify `dualStack: true` at the options
* for dual-stack endpoint (connect to the bucket over IPv6). For example:
*
* - `https://bucket.s3-accelerate.amazonaws.com`
* - `https://bucket.s3-accelerate.amazonaws.com/key`
*
* @param key The S3 key of the object. If not specified, the URL of the
* bucket is returned.
* @param options Options for generating URL.
* @returns an TransferAccelerationUrl token
*/
transferAccelerationUrlForObject(key, options) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_TransferAccelerationUrlOptions(options);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.transferAccelerationUrlForObject);
}
throw error;
}
const dualStack = options?.dualStack ? '.dualstack' : '';
const prefix = `https://${this.bucketName}.s3-accelerate${dualStack}.amazonaws.com/`;
if (typeof key !== 'string') {
return this.urlJoin(prefix);
}
return this.urlJoin(prefix, key);
}
/**
* The virtual hosted-style URL of an S3 object. Specify `regional: false` at
* the options for non-regional URL. For example:
*
* - `https://only-bucket.s3.us-west-1.amazonaws.com`
* - `https://bucket.s3.us-west-1.amazonaws.com/key`
* - `https://bucket.s3.amazonaws.com/key`
* - `https://china-bucket.s3.cn-north-1.amazonaws.com.cn/mykey`
*
* @param key The S3 key of the object. If not specified, the URL of the
* bucket is returned.
* @param options Options for generating URL.
* @returns an ObjectS3Url token
*/
virtualHostedUrlForObject(key, options) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_VirtualHostedStyleUrlOptions(options);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.virtualHostedUrlForObject);
}
throw error;
}
const domainName = options?.regional ?? true ? this.bucketRegionalDomainName : this.bucketDomainName;
const prefix = `https://${domainName}`;
if (typeof key !== 'string') {
return prefix;
}
return this.urlJoin(prefix, key);
}
/**
* The S3 URL of an S3 object. For example:
*
* - `s3://onlybucket`
* - `s3://bucket/key`
*
* @param key The S3 key of the object. If not specified, the S3 URL of the
* bucket is returned.
* @returns an ObjectS3Url token
*/
s3UrlForObject(key) {
const prefix = 's3://';
if (typeof key !== 'string') {
return this.urlJoin(prefix, this.bucketName);
}
return this.urlJoin(prefix, this.bucketName, key);
}
/**
* Returns an ARN that represents all objects within the bucket that match
* the key pattern specified. To represent all keys, specify ``"*"``.
*
* If you need to specify a keyPattern with multiple components, concatenate them into a single string, e.g.:
*
* arnForObjects(`home/${team}/${user}/*`)
*
*/
arnForObjects(keyPattern) {
return `${this.bucketArn}/${keyPattern}`;
}
/**
* Grant read permissions for this bucket and it's contents to an IAM
* principal (Role/Group/User).
*
* If encryption is used, permission to use the key to decrypt the contents
* of the bucket will also be granted to the same principal.
*
* @param identity The principal
* @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*')
*/
grantRead(identity, objectsKeyPattern = '*') {
return this.grant(identity, perms.BUCKET_READ_ACTIONS, perms.KEY_READ_ACTIONS, this.bucketArn, this.arnForObjects(objectsKeyPattern));
}
grantWrite(identity, objectsKeyPattern = '*') {
return this.grant(identity, this.writeActions, perms.KEY_WRITE_ACTIONS, this.bucketArn, this.arnForObjects(objectsKeyPattern));
}
/**
* Grants s3:PutObject* and s3:Abort* permissions for this bucket to an IAM principal.
*
* If encryption is used, permission to use the key to encrypt the contents
* of written files will also be granted to the same principal.
* @param identity The principal
* @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*')
*/
grantPut(identity, objectsKeyPattern = '*') {
return this.grant(identity, this.putActions, perms.KEY_WRITE_ACTIONS, this.arnForObjects(objectsKeyPattern));
}
grantPutAcl(identity, objectsKeyPattern = '*') {
return this.grant(identity, perms.BUCKET_PUT_ACL_ACTIONS, [], this.arnForObjects(objectsKeyPattern));
}
/**
* Grants s3:DeleteObject* permission to an IAM principal for objects
* in this bucket.
*
* @param identity The principal
* @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*')
*/
grantDelete(identity, objectsKeyPattern = '*') {
return this.grant(identity, perms.BUCKET_DELETE_ACTIONS, [], this.arnForObjects(objectsKeyPattern));
}
grantReadWrite(identity, objectsKeyPattern = '*') {
const bucketActions = perms.BUCKET_READ_ACTIONS.concat(this.writeActions);
// we need unique permissions because some permissions are common between read and write key actions
const keyActions = [...new Set([...perms.KEY_READ_ACTIONS, ...perms.KEY_WRITE_ACTIONS])];
return this.grant(identity, bucketActions, keyActions, this.bucketArn, this.arnForObjects(objectsKeyPattern));
}
/**
* Allows unrestricted access to objects from this bucket.
*
* IMPORTANT: This permission allows anyone to perform actions on S3 objects
* in this bucket, which is useful for when you configure your bucket as a
* website and want everyone to be able to read objects in the bucket without
* needing to authenticate.
*
* Without arguments, this method will grant read ("s3:GetObject") access to
* all objects ("*") in the bucket.
*
* The method returns the `iam.Grant` object, which can then be modified
* as needed. For example, you can add a condition that will restrict access only
* to an IPv4 range like this:
*
* const grant = bucket.grantPublicAccess();
* grant.resourceStatement!.addCondition(‘IpAddress’, { “aws:SourceIp”: “54.240.143.0/24” });
*
* Note that if this `IBucket` refers to an existing bucket, possibly not
* managed by CloudFormation, this method will have no effect, since it's
* impossible to modify the policy of an existing bucket.
*
* @param keyPrefix the prefix of S3 object keys (e.g. `home/*`). Default is "*".
* @param allowedActions the set of S3 actions to allow. Default is "s3:GetObject".
*/
grantPublicAccess(keyPrefix = '*', ...allowedActions) {
if (this.disallowPublicAccess) {
throw new Error("Cannot grant public access when 'blockPublicPolicy' is enabled");
}
allowedActions = allowedActions.length > 0 ? allowedActions : ['s3:GetObject'];
return iam.Grant.addToPrincipalOrResource({
actions: allowedActions,
resourceArns: [this.arnForObjects(keyPrefix)],
grantee: new iam.AnyPrincipal(),
resource: this,
});
}
/**
* Adds a bucket notification event destination.
* @param event The event to trigger the notification
* @param dest The notification destination (Lambda, SNS Topic or SQS Queue)
*
* @param filters S3 object key filter rules to determine which objects
* trigger this event. Each filter must include a `prefix` and/or `suffix`
* that will be matched against the s3 object key. Refer to the S3 Developer Guide
* for details about allowed filter rules.
*
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-filtering
*
* @example
*
* declare const myLambda: lambda.Function;
* const bucket = new s3.Bucket(this, 'MyBucket');
* bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(myLambda), {prefix: 'home/myusername/*'});
*
* @see
* https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html
*/
addEventNotification(event, dest, ...filters) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_EventType(event);
jsiiDeprecationWarnings._aws_cdk_aws_s3_IBucketNotificationDestination(dest);
jsiiDeprecationWarnings._aws_cdk_aws_s3_NotificationKeyFilter(filters);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.addEventNotification);
}
throw error;
}
this.withNotifications(notifications => notifications.addNotification(event, dest, ...filters));
}
withNotifications(cb) {
if (!this.notifications) {
this.notifications = new notifications_resource_1.BucketNotifications(this, 'Notifications', {
bucket: this,
handlerRole: this.notificationsHandlerRole,
});
}
cb(this.notifications);
}
/**
* Subscribes a destination to receive notifications when an object is
* created in the bucket. This is identical to calling
* `onEvent(EventType.OBJECT_CREATED)`.
*
* @param dest The notification destination (see onEvent)
* @param filters Filters (see onEvent)
*/
addObjectCreatedNotification(dest, ...filters) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_IBucketNotificationDestination(dest);
jsiiDeprecationWarnings._aws_cdk_aws_s3_NotificationKeyFilter(filters);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.addObjectCreatedNotification);
}
throw error;
}
return this.addEventNotification(EventType.OBJECT_CREATED, dest, ...filters);
}
/**
* Subscribes a destination to receive notifications when an object is
* removed from the bucket. This is identical to calling
* `onEvent(EventType.OBJECT_REMOVED)`.
*
* @param dest The notification destination (see onEvent)
* @param filters Filters (see onEvent)
*/
addObjectRemovedNotification(dest, ...filters) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_IBucketNotificationDestination(dest);
jsiiDeprecationWarnings._aws_cdk_aws_s3_NotificationKeyFilter(filters);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.addObjectRemovedNotification);
}
throw error;
}
return this.addEventNotification(EventType.OBJECT_REMOVED, dest, ...filters);
}
enableEventBridgeNotification() {
this.withNotifications(notifications => notifications.enableEventBridgeNotification());
}
get writeActions() {
return [
...perms.BUCKET_DELETE_ACTIONS,
...this.putActions,
];
}
get putActions() {
return core_1.FeatureFlags.of(this).isEnabled(cxapi.S3_GRANT_WRITE_WITHOUT_ACL)
? perms.BUCKET_PUT_ACTIONS
: perms.LEGACY_BUCKET_PUT_ACTIONS;
}
urlJoin(...components) {
return components.reduce((result, component) => {
if (result.endsWith('/')) {
result = result.slice(0, -1);
}
if (component.startsWith('/')) {
component = component.slice(1);
}
return `${result}/${component}`;
});
}
grant(grantee, bucketActions, keyActions, resourceArn, ...otherResourceArns) {
const resources = [resourceArn, ...otherResourceArns];
const ret = iam.Grant.addToPrincipalOrResource({
grantee,
actions: bucketActions,
resourceArns: resources,
resource: this,
});
if (this.encryptionKey && keyActions && keyActions.length !== 0) {
this.encryptionKey.grant(grantee, ...keyActions);
}
return ret;
}
}
exports.BucketBase = BucketBase;
_a = JSII_RTTI_SYMBOL_1;
BucketBase[_a] = { fqn: "@aws-cdk/aws-s3.BucketBase", version: "1.204.0" };
class BlockPublicAccess {
constructor(options) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_BlockPublicAccessOptions(options);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, BlockPublicAccess);
}
throw error;
}
this.blockPublicAcls = options.blockPublicAcls;
this.blockPublicPolicy = options.blockPublicPolicy;
this.ignorePublicAcls = options.ignorePublicAcls;
this.restrictPublicBuckets = options.restrictPublicBuckets;
}
}
exports.BlockPublicAccess = BlockPublicAccess;
_b = JSII_RTTI_SYMBOL_1;
BlockPublicAccess[_b] = { fqn: "@aws-cdk/aws-s3.BlockPublicAccess", version: "1.204.0" };
BlockPublicAccess.BLOCK_ALL = new BlockPublicAccess({
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true,
});
BlockPublicAccess.BLOCK_ACLS = new BlockPublicAccess({
blockPublicAcls: true,
ignorePublicAcls: true,
});
/**
* All http request methods
*/
var HttpMethods;
(function (HttpMethods) {
/**
* The GET method requests a representation of the specified resource.
*/
HttpMethods["GET"] = "GET";
/**
* The PUT method replaces all current representations of the target resource with the request payload.
*/
HttpMethods["PUT"] = "PUT";
/**
* The HEAD method asks for a response identical to that of a GET request, but without the response body.
*/
HttpMethods["HEAD"] = "HEAD";
/**
* The POST method is used to submit an entity to the specified resource, often causing a change in state or side effects on the server.
*/
HttpMethods["POST"] = "POST";
/**
* The DELETE method deletes the specified resource.
*/
HttpMethods["DELETE"] = "DELETE";
})(HttpMethods = exports.HttpMethods || (exports.HttpMethods = {}));
/**
* All http request methods
*/
var RedirectProtocol;
(function (RedirectProtocol) {
RedirectProtocol["HTTP"] = "http";
RedirectProtocol["HTTPS"] = "https";
})(RedirectProtocol = exports.RedirectProtocol || (exports.RedirectProtocol = {}));
/**
* All supported inventory list formats.
*/
var InventoryFormat;
(function (InventoryFormat) {
/**
* Generate the inventory list as CSV.
*/
InventoryFormat["CSV"] = "CSV";
/**
* Generate the inventory list as Parquet.
*/
InventoryFormat["PARQUET"] = "Parquet";
/**
* Generate the inventory list as ORC.
*/
InventoryFormat["ORC"] = "ORC";
})(InventoryFormat = exports.InventoryFormat || (exports.InventoryFormat = {}));
/**
* All supported inventory frequencies.
*/
var InventoryFrequency;
(function (InventoryFrequency) {
/**
* A report is generated every day.
*/
InventoryFrequency["DAILY"] = "Daily";
/**
* A report is generated every Sunday (UTC timezone) after the initial report.
*/
InventoryFrequency["WEEKLY"] = "Weekly";
})(InventoryFrequency = exports.InventoryFrequency || (exports.InventoryFrequency = {}));
/**
* Inventory version support.
*/
var InventoryObjectVersion;
(function (InventoryObjectVersion) {
/**
* Includes all versions of each object in the report.
*/
InventoryObjectVersion["ALL"] = "All";
/**
* Includes only the current version of each object in the report.
*/
InventoryObjectVersion["CURRENT"] = "Current";
})(InventoryObjectVersion = exports.InventoryObjectVersion || (exports.InventoryObjectVersion = {}));
/**
* The ObjectOwnership of the bucket.
*
* @see https://docs.aws.amazon.com/AmazonS3/latest/dev/about-object-ownership.html
*
*/
var ObjectOwnership;
(function (ObjectOwnership) {
/**
* ACLs are disabled, and the bucket owner automatically owns
* and has full control over every object in the bucket.
* ACLs no longer affect permissions to data in the S3 bucket.
* The bucket uses policies to define access control.
*/
ObjectOwnership["BUCKET_OWNER_ENFORCED"] = "BucketOwnerEnforced";
/**
* Objects uploaded to the bucket change ownership to the bucket owner .
*/
ObjectOwnership["BUCKET_OWNER_PREFERRED"] = "BucketOwnerPreferred";
/**
* The uploading account will own the object.
*/
ObjectOwnership["OBJECT_WRITER"] = "ObjectWriter";
})(ObjectOwnership = exports.ObjectOwnership || (exports.ObjectOwnership = {}));
/**
* An S3 bucket with associated policy objects
*
* This bucket does not yet have all features that exposed by the underlying
* BucketResource.
*/
class Bucket extends BucketBase {
constructor(scope, id, props = {}) {
super(scope, id, {
physicalName: props.bucketName,
});
this.autoCreatePolicy = true;
this.lifecycleRules = [];
this.metrics = [];
this.cors = [];
this.inventories = [];
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_BucketProps(props);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, Bucket);
}
throw error;
}
this.notificationsHandlerRole = props.notificationsHandlerRole;
const { bucketEncryption, encryptionKey } = this.parseEncryption(props);
Bucket.validateBucketName(this.physicalName);
const websiteConfiguration = this.renderWebsiteConfiguration(props);
this.isWebsite = (websiteConfiguration !== undefined);
const resource = new s3_generated_1.CfnBucket(this, 'Resource', {
bucketName: this.physicalName,
bucketEncryption,
versioningConfiguration: props.versioned ? { status: 'Enabled' } : undefined,
lifecycleConfiguration: core_1.Lazy.any({ produce: () => this.parseLifecycleConfiguration() }),
websiteConfiguration,
publicAccessBlockConfiguration: props.blockPublicAccess,
metricsConfigurations: core_1.Lazy.any({ produce: () => this.parseMetricConfiguration() }),
corsConfiguration: core_1.Lazy.any({ produce: () => this.parseCorsConfiguration() }),
accessControl: core_1.Lazy.string({ produce: () => this.accessControl }),
loggingConfiguration: this.parseServerAccessLogs(props),
inventoryConfigurations: core_1.Lazy.any({ produce: () => this.parseInventoryConfiguration() }),
ownershipControls: this.parseOwnershipControls(props),
accelerateConfiguration: props.transferAcceleration ? { accelerationStatus: 'Enabled' } : undefined,
intelligentTieringConfigurations: this.parseTieringConfig(props),
});
this._resource = resource;
resource.applyRemovalPolicy(props.removalPolicy);
this.versioned = props.versioned;
this.encryptionKey = encryptionKey;
this.eventBridgeEnabled = props.eventBridgeEnabled;
this.bucketName = this.getResourceNameAttribute(resource.ref);
this.bucketArn = this.getResourceArnAttribute(resource.attrArn, {
region: '',
account: '',
service: 's3',
resource: this.physicalName,
});
this.bucketDomainName = resource.attrDomainName;
this.bucketWebsiteUrl = resource.attrWebsiteUrl;
this.bucketWebsiteDomainName = core_1.Fn.select(2, core_1.Fn.split('/', this.bucketWebsiteUrl));
this.bucketDualStackDomainName = resource.attrDualStackDomainName;
this.bucketRegionalDomainName = resource.attrRegionalDomainName;
this.disallowPublicAccess = props.blockPublicAccess && props.blockPublicAccess.blockPublicPolicy;
this.accessControl = props.accessControl;
// Enforce AWS Foundational Security Best Practice
if (props.enforceSSL) {
this.enforceSSLStatement();
}
if (props.serverAccessLogsBucket instanceof Bucket) {
props.serverAccessLogsBucket.allowLogDelivery();
}
for (const inventory of props.inventories ?? []) {
this.addInventory(inventory);
}
// Add all bucket metric configurations rules
(props.metrics || []).forEach(this.addMetric.bind(this));
// Add all cors configuration rules
(props.cors || []).forEach(this.addCorsRule.bind(this));
// Add all lifecycle rules
(props.lifecycleRules || []).forEach(this.addLifecycleRule.bind(this));
if (props.publicReadAccess) {
this.grantPublicAccess();
}
if (props.autoDeleteObjects) {
if (props.removalPolicy !== core_1.RemovalPolicy.DESTROY) {
throw new Error('Cannot use \'autoDeleteObjects\' property on a bucket without setting removal policy to \'DESTROY\'.');
}
this.enableAutoDeleteObjects();
}
if (this.eventBridgeEnabled) {
this.enableEventBridgeNotification();
}
}
static fromBucketArn(scope, id, bucketArn) {
return Bucket.fromBucketAttributes(scope, id, { bucketArn });
}
static fromBucketName(scope, id, bucketName) {
return Bucket.fromBucketAttributes(scope, id, { bucketName });
}
/**
* Creates a Bucket construct that represents an external bucket.
*
* @param scope The parent creating construct (usually `this`).
* @param id The construct's name.
* @param attrs A `BucketAttributes` object. Can be obtained from a call to
* `bucket.export()` or manually created.
*/
static fromBucketAttributes(scope, id, attrs) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_BucketAttributes(attrs);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.fromBucketAttributes);
}
throw error;
}
const stack = core_1.Stack.of(scope);
const region = attrs.region ?? stack.region;
const urlSuffix = stack.urlSuffix;
const bucketName = util_1.parseBucketName(scope, attrs);
if (!bucketName) {
throw new Error('Bucket name is required');
}
Bucket.validateBucketName(bucketName);
const newUrlFormat = attrs.bucketWebsiteNewUrlFormat === undefined
? false
: attrs.bucketWebsiteNewUrlFormat;
const websiteDomain = newUrlFormat
? `${bucketName}.s3-website.${region}.${urlSuffix}`
: `${bucketName}.s3-website-${region}.${urlSuffix}`;
class Import extends BucketBase {
constructor() {
super(...arguments);
this.bucketName = bucketName;
this.bucketArn = util_1.parseBucketArn(scope, attrs);
this.bucketDomainName = attrs.bucketDomainName || `${bucketName}.s3.${urlSuffix}`;
this.bucketWebsiteUrl = attrs.bucketWebsiteUrl || `http://${websiteDomain}`;
this.bucketWebsiteDomainName = attrs.bucketWebsiteUrl ? core_1.Fn.select(2, core_1.Fn.split('/', attrs.bucketWebsiteUrl)) : websiteDomain;
this.bucketRegionalDomainName = attrs.bucketRegionalDomainName || `${bucketName}.s3.${region}.${urlSuffix}`;
this.bucketDualStackDomainName = attrs.bucketDualStackDomainName || `${bucketName}.s3.dualstack.${region}.${urlSuffix}`;
this.bucketWebsiteNewUrlFormat = newUrlFormat;
this.encryptionKey = attrs.encryptionKey;
this.isWebsite = attrs.isWebsite ?? false;
this.policy = undefined;
this.autoCreatePolicy = false;
this.disallowPublicAccess = false;
this.notificationsHandlerRole = attrs.notificationsHandlerRole;
}
/**
* Exports this bucket from the stack.
*/
export() {
return attrs;
}
}
return new Import(scope, id, {
account: attrs.account,
region: attrs.region,
});
}
/**
* Thrown an exception if the given bucket name is not valid.
*
* @param physicalName name of the bucket.
*/
static validateBucketName(physicalName) {
const bucketName = physicalName;
if (!bucketName || core_1.Token.isUnresolved(bucketName)) {
// the name is a late-bound value, not a defined string,
// so skip validation
return;
}
const errors = [];
// Rules codified from https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
if (bucketName.length < 3 || bucketName.length > 63) {
errors.push('Bucket name must be at least 3 and no more than 63 characters');
}
const charsetMatch = bucketName.match(/[^a-z0-9.-]/);
if (charsetMatch) {
errors.push('Bucket name must only contain lowercase characters and the symbols, period (.) and dash (-) '
+ `(offset: ${charsetMatch.index})`);
}
if (!/[a-z0-9]/.test(bucketName.charAt(0))) {
errors.push('Bucket name must start and end with a lowercase character or number '
+ '(offset: 0)');
}
if (!/[a-z0-9]/.test(bucketName.charAt(bucketName.length - 1))) {
errors.push('Bucket name must start and end with a lowercase character or number '
+ `(offset: ${bucketName.length - 1})`);
}
const consecSymbolMatch = bucketName.match(/\.-|-\.|\.\./);
if (consecSymbolMatch) {
errors.push('Bucket name must not have dash next to period, or period next to dash, or consecutive periods '
+ `(offset: ${consecSymbolMatch.index})`);
}
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(bucketName)) {
errors.push('Bucket name must not resemble an IP address');
}
if (errors.length > 0) {
throw new Error(`Invalid S3 bucket name (value: ${bucketName})${os_1.EOL}${errors.join(os_1.EOL)}`);
}
}
/**
* Add a lifecycle rule to the bucket
*
* @param rule The rule to add
*/
addLifecycleRule(rule) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_LifecycleRule(rule);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.addLifecycleRule);
}
throw error;
}
if ((rule.noncurrentVersionExpiration !== undefined
|| (rule.noncurrentVersionTransitions && rule.noncurrentVersionTransitions.length > 0))
&& !this.versioned) {
throw new Error("Cannot use 'noncurrent' rules on a nonversioned bucket");
}
this.lifecycleRules.push(rule);
}
/**
* Adds a metrics configuration for the CloudWatch request metrics from the bucket.
*
* @param metric The metric configuration to add
*/
addMetric(metric) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_BucketMetrics(metric);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.addMetric);
}
throw error;
}
this.metrics.push(metric);
}
/**
* Adds a cross-origin access configuration for objects in an Amazon S3 bucket
*
* @param rule The CORS configuration rule to add
*/
addCorsRule(rule) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_CorsRule(rule);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.addCorsRule);
}
throw error;
}
this.cors.push(rule);
}
/**
* Add an inventory configuration.
*
* @param inventory configuration to add
*/
addInventory(inventory) {
try {
jsiiDeprecationWarnings._aws_cdk_aws_s3_Inventory(inventory);
}
catch (error) {
if (process.env.JSII_DEBUG !== "1" && error.name === "DeprecationError") {
Error.captureStackTrace(error, this.addInventory);
}
throw error;
}
this.inventories.push(inventory);
}
/**
* Adds an iam statement to enforce SSL requests only.
*/
enforceSSLStatement() {
const statement = new iam.PolicyStatement({
actions: ['s3:*'],
conditions: {
Bool: { 'aws:SecureTransport': 'false' },
},
effect: iam.Effect.DENY,
resources: [
this.bucketArn,
this.arnForObjects('*'),
],
principals: [new iam.AnyPrincipal()],
});
this.addToResourcePolicy(statement);
}
/**
* Set up key properties and return the Bucket encryption property from the
* user's configuration.
*/
parseEncryption(props) {
// default based on whether encryptionKey is specified
let encryptionType = props.encryption;
if (encryptionType === undefined) {
encryptionType = props.encryptionKey ? BucketEncryption.KMS : BucketEncryption.UNENCRYPTED;
}
// if encryption key is set, encryption must be set to KMS.
if (encryptionType !== BucketEncryption.KMS && props.encryptionKey) {
throw new Error(`encryptionKey is specified, so 'encryption' must be set to KMS (value: ${encryptionType})`);
}
// if bucketKeyEnabled is set, encryption must be set to KMS.
if (props.bucketKeyEnabled && encryptionType !== BucketEncryption.KMS) {
throw new Error(`bucketKeyEnabled is specified, so 'encryption' must be set to KMS (value: ${encryptionType})`);
}
if (encryptionType === BucketEncryption.UNENCRYPTED) {
return { bucketEncryption: undefined, encryptionKey: undefined };
}
if (encryptionType === BucketEncryption.KMS) {
const encryptionKey = props.encryptionKey || new kms.Key(this, 'Key', {
description: `Created by ${this.node.path}`,
});
const bucketEncryption = {
serverSideEncryptionConfiguration: [
{
bucketKeyEnabled: props.bucketKeyEnabled,
serverSideEncryptionByDefault: {
sseAlgorithm: 'aws:kms',
kmsMasterKeyId: encryptionKey.keyArn,
},
},
],
};
return { encryptionKey, bucketEncryption };
}
if (encryptionType === BucketEncryption.S3_MANAGED) {
const bucketEncryption = {
serverSideEncryptionConfiguration: [
{ serverSideEncryptionByDefault: { sseAlgorithm: 'AES256' } },
],
};
return { bucketEncryption };
}
if (encryptionType === BucketEncryption.KMS_MANAGED) {
const bucketEncryption = {
serverSideEncryptionConfiguration: [
{ serverSideEncryptionByDefault: { sseAlgorithm: 'aws:kms' } },
],
};
return { bucketEncryption };
}
throw new Error(`Unexpected 'encryptionType': ${encryptionType}`);
}
/**
* Parse the lifecycle configuration out of the bucket props
* @param props Par
*/
parseLifecycleConfiguration() {
if (!this.lifecycleRules || this.lifecycleRules.length === 0) {
return undefined;
}
const self = this;
return { rules: this.lifecycleRules.map(parseLifecycleRule) };
function parseLifecycleRule(rule) {
const enabled = rule.enabled ?? true;
const x = {
// eslint-disable-next-line max-len
abortIncompleteMultipartUpload: rule.abortIncompleteMultipartUploadAfter !== undefined ? { daysAfterInitiation: rule.abortIncompleteMultipartUploadAfter.toDays() } : undefined,
expirationDate: rule.expirationDate,
expirationInDays: rule.expiration?.toDays(),
id: rule.id,
noncurrentVersionExpiration: rule.noncurrentVersionExpiration && {
noncurrentDays: rule.noncurrentVersionExpiration.toDays(),
newerNoncurrentVersions: rule.noncurrentVersionsToRetain,
},
noncurrentVersionTransitions: mapOrUndefined(rule.noncurrentVersionTransitions, t => ({
storageClass: t.storageClass.value,
transitionInDays: t.transitionAfter.toDays(),
newerNoncurrentVersions: t.noncurrentVersionsToRetain,
})),
prefix: rule.prefix,
status: enabled ? 'Enabled' : 'Disabled',
transitions: mapOrUndefined(rule.transitions, t => ({
storageClass: t.storageClass.value,
transitionDate: t.transitionDate,
transitionInDays: t.transitionAfter && t.transitionAfter.toDays(),
})),
expiredObjectDeleteMarker: rule.expiredObjectDeleteMarker,
tagFilters: self.parseTagFilters(rule.tagFilters),
objectSizeLessThan: rule.objectSizeLessThan,
objectSizeGreaterThan: rule.objectSizeGreaterThan,
};
return x;
}
}
parseServerAccessLogs(props) {
if (!props.serverAccessLogsBucket && !props.serverAccessLogsPrefix) {
return undefined;
}
return {
destinationBucketName: props.serverAccessLogsBucket?.bucketName,
logFilePrefix: props.serverAccessLogsPrefix,
};
}
parseMetricConfiguration() {
if (!this.metrics || this.metrics.length === 0) {
return undefined;
}
const self = this;
return this.metrics.map(parseMetric);
function parseMetric(metric) {
return {
id: metric.id,
prefix: metric.prefix,
tagFilters: self.parseTagFilters(metric.tagFilters),
};
}
}
parseCorsConfiguration() {
if (!this.cors || this.cors.length === 0) {
return undefined;
}
return { corsRules: this.cors.map(parseCors) };
function parseCors(rule) {
return {
id: rule.id,
maxAge: rule.maxAge,
allowedHeaders: rule.allowedHeaders,
allowedMethods: rule.allowedMethods,
allowedOrigins: rule.allowedOrigins,
exposedHeaders: rule.exposedHeaders,
};
}
}
parseTagFilters(tagFilters) {
if (!tagFilters || tagFilters.length === 0) {
return undefined;
}
return Object.keys(tagFilters).map(tag => ({
key: tag,
value: tagFilters[tag],
}));
}
parseOwnershipControls({ objectOwnership }) {
if (!objectOwnership) {
return undefined;
}
return {
rules: [{
objectOwnership,
}],
};
}
parseTieringConfig({ intelligentTieringConfigurations }) {
if (!intelligentTieringConfigurations) {
return undefined;
}
return intelligentTieringConfigurations.map(config => {
const tierings = [];
if (config.archiveAccessTierTime) {
tierings.push({
accessTier: 'ARCHIVE_ACCESS',
days: config.archiveAccessTierTime.toDays({ integral: true }),
});
}
if (config.deepArchiveAccessTierTime) {
tierings.push({
accessTier: 'DEEP_ARCHIVE_ACCESS',
days: config.deepArchiveAccessTierTime.toDays({ integral: true }),
});
}
return {
id: config.name,
prefix: config.prefix,
status: 'Enabled',
tagFilters: config.tags,
tierings: tierings,
};
});
}
renderWebsiteConfiguration(props) {
if (!props.websiteErrorDocument && !props.websiteIndexDocument && !props.websiteRedirect && !props.websiteRoutingRules) {
return undefined;
}
if (props.websiteErrorDocument && !props.websiteIndexDocument) {
throw new Error('"websiteIndexDocument" is required if "websiteErrorDocument" is set');
}
if (props.websiteRedirect && (props.websiteErrorDocument || props.websiteIndexDocument || props.websiteRoutingRules)) {
throw new Error('"websiteIndexDocument", "websiteErrorDocument" and, "websiteRoutingRules" cannot be set if "websiteRedirect" is used');
}
const routingRules = props.websiteRoutingRules ? props.websiteRoutingRules.map((rule) => {
if (rule.condition && !rule.condition.httpErrorCodeReturnedEquals && !rule.condition.keyPrefixEquals) {
throw new Error('The condition property cannot be an empty object');
}
return {
redirectRule: {
hostName: rule.hostName,
httpRedirectCode: rule.httpRedirectCode,
protocol: rule.protocol,
replaceKeyWith: rule.replaceKey && rule.replaceKey.withKey,
replaceKeyPrefixWith: rule.replaceKey && rule.replaceKey.prefixWithKey,
},
routingRuleCondition: rule.condition,
};
}) : undefined;
return {
indexDocument: props.websiteIndexDocument,
errorDocument: props.websiteErrorDocument,
redirectAllRequestsTo: props.websiteRedirect,
routingRules,
};
}
/**
* Allows the LogDelivery group to write, fails if ACL was set differently.
*
* @see
* https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
*/
allowLogDelivery() {
if (this.accessControl && this.accessControl !== BucketAccessControl.LOG_DELIVERY_WRITE) {
throw new Error("Cannot enable log delivery to this bucket because the bucket's ACL has been set and can't be changed");
}
this.accessControl = BucketAccessControl.LOG_DELIVERY_WRITE;
}
parseInventoryConfiguration() {
if (!this.inventories || this.inventories.length === 0) {
return undefined;
}
return this.inventories.map((inventory, index) => {
const format = inventory.format ?? InventoryFormat.CSV;
const frequency = inventory.frequency ?? InventoryFrequency.WEEKLY;
const id = inventory.inventoryId ?? `${this.node.id}Inventory${index}`;
if (inventory.destination.bucket instanceof Bucket) {
inventory.destination.bucket.addToResourcePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:PutObject'],
resources: [
inventory.destination.bucket.bucketArn,
inventory.destination.bucket.arnForObjects(`${inventory.destination.prefix ?? ''}*`),
],
principals: [new iam.ServicePrincipal('s3.amazonaws.com')],
conditions: {
ArnLike: {
'aws:SourceArn': this.bucketArn,
},
},
}));
}
return {
id,
destination: {
bucketArn: inventory.destination.bucket.bucketArn,
bucketAccountId: inventory.destination.bucketOwner,
prefix: inventory.destination.prefix,
format,
},
enabled: inventory.enabled ?? true,
includedObjectVersions: inventory.includeObjectVersions ?? InventoryObjectVersion.ALL,
scheduleFrequency: frequency,
optionalFields: inventory.optionalFields,
prefix: inventory.objectsPrefix,