Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript (v3): S3 - Audit 🧵 3/3 #6958

Merged
Merged
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
20 changes: 10 additions & 10 deletions javascriptv3/example_code/libs/scenario/scenario.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ export class Step {
console.log(
`[DEBUG ${new Date().toISOString()}] Handling step: ${
this.constructor.name
}<${this.name}>`
}<${this.name}>`,
);
console.log(
`[DEBUG ${new Date().toISOString()}] State: ${JSON.stringify(state)}`
`[DEBUG ${new Date().toISOString()}] State: ${JSON.stringify(state)}`,
);
}
}
Expand Down Expand Up @@ -162,7 +162,7 @@ export class ScenarioInput extends Step {
return true;
}
throw new Error(
`Error handling ScenarioInput. confirmAll was selected for ${this.name} but no default was provided.`
`Error handling ScenarioInput. confirmAll was selected for ${this.name} but no default was provided.`,
);
}

Expand All @@ -181,7 +181,7 @@ export class ScenarioInput extends Step {
break;
default:
throw new Error(
`Error handling ScenarioInput, ${this.stepOptions?.type} is not supported.`
`Error handling ScenarioInput, ${this.stepOptions?.type} is not supported.`,
);
}

Expand All @@ -203,7 +203,7 @@ export class ScenarioInput extends Step {

if (!rawChoices) {
throw new Error(
`Error handling ScenarioInput. Could not get choices for ${this.name}.`
`Error handling ScenarioInput. Could not get choices for ${this.name}.`,
);
}

Expand Down Expand Up @@ -235,7 +235,7 @@ export class ScenarioInput extends Step {
state[this.name] = this.default;
} else if (!result.length) {
throw new Error(
`Error handing ScenarioInput. Result of ${this.name} was empty.`
`Error handing ScenarioInput. Result of ${this.name} was empty.`,
);
} else {
state[this.name] = result;
Expand All @@ -261,9 +261,9 @@ export class ScenarioInput extends Step {

if (!result && this.default) {
state[this.name] = this.default;
} else if (!result) {
} else if (result == null) {
throw new Error(
`Error handing ScenarioInput. Result of ${this.name} was empty.`
`Error handing ScenarioInput. Result of ${this.name} was empty.`,
);
} else {
state[this.name] = result;
Expand All @@ -289,7 +289,7 @@ export class ScenarioInput extends Step {
state[this.name] = this.default;
} else if (result === undefined) {
throw new Error(
`Error handing ScenarioInput. Result of ${this.name} was empty.`
`Error handing ScenarioInput. Result of ${this.name} was empty.`,
);
} else {
state[this.name] = result;
Expand Down Expand Up @@ -362,7 +362,7 @@ export class ScenarioAction extends Step {
output &&
(await this.stepOptions.whileConfig.output.handle(
state,
stepHandlerOptions
stepHandlerOptions,
));
await input.handle(state, stepHandlerOptions);
runAction = whileFn(state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
confirmUpdateRetention,
createBuckets,
createBucketsAction,
getBucketPrefix,
populateBuckets,
populateBucketsAction,
setLegalHoldFileEnabledAction,
Expand All @@ -45,6 +46,7 @@ export const getWorkflowStages = (scenarios, initialState = {}) => {
welcome(scenarios),
welcomeContinue(scenarios),
exitOnFalse(scenarios, "welcomeContinue"),
getBucketPrefix(scenarios),
createBuckets(scenarios),
confirmCreateBuckets(scenarios),
exitOnFalse(scenarios, "confirmCreateBuckets"),
Expand Down
216 changes: 137 additions & 79 deletions javascriptv3/example_code/s3/scenarios/object-locking/setup.steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@ import {
PutObjectRetentionCommand,
ObjectLockLegalHoldStatus,
ObjectLockRetentionMode,
GetBucketVersioningCommand,
BucketAlreadyExists,
BucketAlreadyOwnedByYou,
S3ServiceException,
waitUntilBucketExists,
} from "@aws-sdk/client-s3";

import { retry } from "@aws-doc-sdk-examples/lib/utils/util-timers.js";

/**
* @typedef {import("@aws-doc-sdk-examples/lib/scenario/index.js")} Scenarios
*/
Expand All @@ -22,19 +29,26 @@ import {
* @typedef {import("@aws-sdk/client-s3").S3Client} S3Client
*/

const bucketPrefix = "js-object-locking";
/**
* @param {Scenarios} scenarios
*/
const getBucketPrefix = (scenarios) =>
new scenarios.ScenarioInput(
"bucketPrefix",
"Provide a prefix that will be used for bucket creation.",
{ type: "input", default: "amzn-s3-demo-bucket" },
);

/**
* @param {Scenarios} scenarios
* @param {S3Client} client
*/
const createBuckets = (scenarios) =>
new scenarios.ScenarioOutput(
"createBuckets",
`The following buckets will be created:
${bucketPrefix}-no-lock with object lock False.
${bucketPrefix}-lock-enabled with object lock True.
${bucketPrefix}-retention-after-creation with object lock False.`,
(state) => `The following buckets will be created:
${state.bucketPrefix}-no-lock with object lock False.
${state.bucketPrefix}-lock-enabled with object lock True.
${state.bucketPrefix}-retention-after-creation with object lock False.`,
{ preformatted: true },
);

Expand All @@ -52,22 +66,42 @@ const confirmCreateBuckets = (scenarios) =>
*/
const createBucketsAction = (scenarios, client) =>
new scenarios.ScenarioAction("createBucketsAction", async (state) => {
const noLockBucketName = `${bucketPrefix}-no-lock`;
const lockEnabledBucketName = `${bucketPrefix}-lock-enabled`;
const retentionBucketName = `${bucketPrefix}-retention-after-creation`;
const noLockBucketName = `${state.bucketPrefix}-no-lock`;
const lockEnabledBucketName = `${state.bucketPrefix}-lock-enabled`;
const retentionBucketName = `${state.bucketPrefix}-retention-after-creation`;

await client.send(new CreateBucketCommand({ Bucket: noLockBucketName }));
await client.send(
new CreateBucketCommand({
Bucket: lockEnabledBucketName,
ObjectLockEnabledForBucket: true,
}),
);
await client.send(new CreateBucketCommand({ Bucket: retentionBucketName }));
try {
await client.send(new CreateBucketCommand({ Bucket: noLockBucketName }));
await waitUntilBucketExists({ client }, { Bucket: noLockBucketName });
await client.send(
new CreateBucketCommand({
Bucket: lockEnabledBucketName,
ObjectLockEnabledForBucket: true,
}),
);
await waitUntilBucketExists(
{ client },
{ Bucket: lockEnabledBucketName },
);
await client.send(
new CreateBucketCommand({ Bucket: retentionBucketName }),
);
await waitUntilBucketExists({ client }, { Bucket: retentionBucketName });

state.noLockBucketName = noLockBucketName;
state.lockEnabledBucketName = lockEnabledBucketName;
state.retentionBucketName = retentionBucketName;
state.noLockBucketName = noLockBucketName;
state.lockEnabledBucketName = lockEnabledBucketName;
state.retentionBucketName = retentionBucketName;
} catch (caught) {
if (
caught instanceof BucketAlreadyExists ||
caught instanceof BucketAlreadyOwnedByYou
) {
console.error(`${caught.name}: ${caught.message}`);
state.earlyExit = true;
} else {
throw caught;
}
}
});

/**
Expand All @@ -76,13 +110,13 @@ const createBucketsAction = (scenarios, client) =>
const populateBuckets = (scenarios) =>
new scenarios.ScenarioOutput(
"populateBuckets",
`The following test files will be created:
file0.txt in ${bucketPrefix}-no-lock.
file1.txt in ${bucketPrefix}-no-lock.
file0.txt in ${bucketPrefix}-lock-enabled.
file1.txt in ${bucketPrefix}-lock-enabled.
file0.txt in ${bucketPrefix}-retention-after-creation.
file1.txt in ${bucketPrefix}-retention-after-creation.`,
(state) => `The following test files will be created:
file0.txt in ${state.bucketPrefix}-no-lock.
file1.txt in ${state.bucketPrefix}-no-lock.
file0.txt in ${state.bucketPrefix}-lock-enabled.
file1.txt in ${state.bucketPrefix}-lock-enabled.
file0.txt in ${state.bucketPrefix}-retention-after-creation.
file1.txt in ${state.bucketPrefix}-retention-after-creation.`,
{ preformatted: true },
);

Expand All @@ -102,54 +136,64 @@ const confirmPopulateBuckets = (scenarios) =>
*/
const populateBucketsAction = (scenarios, client) =>
new scenarios.ScenarioAction("populateBucketsAction", async (state) => {
await client.send(
new PutObjectCommand({
Bucket: state.noLockBucketName,
Key: "file0.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.noLockBucketName,
Key: "file1.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.lockEnabledBucketName,
Key: "file0.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.lockEnabledBucketName,
Key: "file1.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.retentionBucketName,
Key: "file0.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.retentionBucketName,
Key: "file1.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
try {
await client.send(
new PutObjectCommand({
Bucket: state.noLockBucketName,
Key: "file0.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.noLockBucketName,
Key: "file1.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.lockEnabledBucketName,
Key: "file0.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.lockEnabledBucketName,
Key: "file1.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.retentionBucketName,
Key: "file0.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
await client.send(
new PutObjectCommand({
Bucket: state.retentionBucketName,
Key: "file1.txt",
Body: "Content",
ChecksumAlgorithm: ChecksumAlgorithm.SHA256,
}),
);
} catch (caught) {
if (caught instanceof S3ServiceException) {
console.error(
`Error from S3 while uploading object. ${caught.name}: ${caught.message}`,
);
} else {
throw caught;
}
}
});

/**
Expand All @@ -158,8 +202,10 @@ const populateBucketsAction = (scenarios, client) =>
const updateRetention = (scenarios) =>
new scenarios.ScenarioOutput(
"updateRetention",
`A bucket can be configured to use object locking with a default retention period.
A default retention period will be configured for ${bucketPrefix}-retention-after-creation.`,
(
state,
) => `A bucket can be configured to use object locking with a default retention period.
A default retention period will be configured for ${state.bucketPrefix}-retention-after-creation.`,
{ preformatted: true },
);

Expand Down Expand Up @@ -189,6 +235,17 @@ const updateRetentionAction = (scenarios, client) =>
}),
);

const getBucketVersioning = new GetBucketVersioningCommand({
Bucket: state.retentionBucketName,
});

await retry({ intervalInMs: 500, maxRetries: 10 }, async () => {
const { Status } = await client.send(getBucketVersioning);
if (Status !== "Enabled") {
throw new Error(`Bucket versioning is not enabled.`);
}
});

await client.send(
new PutObjectLockConfigurationCommand({
Bucket: state.retentionBucketName,
Expand All @@ -211,8 +268,8 @@ const updateRetentionAction = (scenarios, client) =>
const updateLockPolicy = (scenarios) =>
new scenarios.ScenarioOutput(
"updateLockPolicy",
`Object lock policies can also be added to existing buckets.
An object lock policy will be added to ${bucketPrefix}-lock-enabled.`,
(state) => `Object lock policies can also be added to existing buckets.
An object lock policy will be added to ${state.bucketPrefix}-lock-enabled.`,
{ preformatted: true },
);

Expand Down Expand Up @@ -403,6 +460,7 @@ const setRetentionPeriodFileRetentionAction = (scenarios, client) =>
);

export {
getBucketPrefix,
createBuckets,
confirmCreateBuckets,
createBucketsAction,
Expand Down
Loading