Skip to content

Commit 3dfab74

Browse files
committed
test(signature-v4-multi-region): add s3 e2e test
1 parent 7bd6a61 commit 3dfab74

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import "@smithy/signature-v4a";
2+
3+
import { Sha256 } from "@aws-crypto/sha256-js";
4+
import {
5+
S3Client,
6+
CreateBucketCommand,
7+
DeleteBucketCommand,
8+
PutObjectCommand,
9+
ListObjectsV2Command
10+
} from "@aws-sdk/client-s3";
11+
import {
12+
S3ControlClient,
13+
CreateMultiRegionAccessPointCommand,
14+
DeleteMultiRegionAccessPointCommand,
15+
DescribeMultiRegionAccessPointOperationCommand,
16+
GetMultiRegionAccessPointCommand
17+
} from "@aws-sdk/client-s3-control";
18+
import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
19+
import { SignatureV4MultiRegion } from "@aws-sdk/signature-v4-multi-region";
20+
import { HttpRequest } from "@smithy/protocol-http";
21+
22+
jest.setTimeout(1800000); // 30 minutes (MRAP operations can take a while)
23+
24+
describe("S3 Multi-Region Access Point with SignatureV4a (JS Implementation)", () => {
25+
let s3Client: S3Client;
26+
let s3ControlClient: S3ControlClient;
27+
let accountId: string;
28+
let signer: SignatureV4MultiRegion;
29+
let mrapName: string;
30+
let bucketName1: string;
31+
let bucketName2: string;
32+
let mrapArn: string;
33+
34+
beforeAll(async () => {
35+
const stsClient = new STSClient({});
36+
const { Account } = await stsClient.send(new GetCallerIdentityCommand({}));
37+
accountId = Account!;
38+
const timestamp = Date.now();
39+
mrapName = `test-mrap-${timestamp}`;
40+
bucketName1 = `test-bucket1-${timestamp}`;
41+
bucketName2 = `test-bucket2-${timestamp}`;
42+
43+
signer = new SignatureV4MultiRegion({
44+
service: "s3",
45+
region: "*",
46+
sha256: Sha256,
47+
credentials: {
48+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
49+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
50+
sessionToken: process.env.AWS_SESSION_TOKEN,
51+
},
52+
});
53+
54+
s3Client = new S3Client({
55+
region: "*",
56+
useArnRegion: true,
57+
signer,
58+
});
59+
60+
s3ControlClient = new S3ControlClient({
61+
region: "*",
62+
signer,
63+
});
64+
65+
// Create buckets
66+
await s3Client.send(new CreateBucketCommand({ Bucket: bucketName1, CreateBucketConfiguration: { LocationConstraint: "us-west-2" } }));
67+
await s3Client.send(new CreateBucketCommand({ Bucket: bucketName2, CreateBucketConfiguration: { LocationConstraint: "us-east-2" } }));
68+
69+
// Create MRAP
70+
const createResponse = await s3ControlClient.send(
71+
new CreateMultiRegionAccessPointCommand({
72+
AccountId: accountId,
73+
ClientToken: `create-${timestamp}`,
74+
Details: {
75+
Name: mrapName,
76+
PublicAccessBlock: {
77+
BlockPublicAcls: true,
78+
BlockPublicPolicy: true,
79+
IgnorePublicAcls: true,
80+
RestrictPublicBuckets: true,
81+
},
82+
Regions: [
83+
{ Bucket: bucketName1, BucketAccountId: accountId },
84+
{ Bucket: bucketName2, BucketAccountId: accountId },
85+
],
86+
},
87+
})
88+
);
89+
90+
// Wait for MRAP to be created
91+
let mrapReady = false;
92+
let retries = 0;
93+
while (!mrapReady && retries < 60) {
94+
const describeResponse = await s3ControlClient.send(
95+
new DescribeMultiRegionAccessPointOperationCommand({
96+
AccountId: accountId,
97+
RequestTokenARN: createResponse.RequestTokenARN,
98+
})
99+
);
100+
101+
if (describeResponse.AsyncOperation?.RequestStatus === "SUCCESS") {
102+
mrapReady = true;
103+
} else {
104+
await new Promise(resolve => setTimeout(resolve, 30000)); // Wait for 30 seconds before retrying
105+
retries++;
106+
}
107+
}
108+
109+
if (!mrapReady) {
110+
throw new Error("MRAP creation timed out");
111+
}
112+
113+
// Get MRAP ARN
114+
const getResponse = await s3ControlClient.send(
115+
new GetMultiRegionAccessPointCommand({
116+
AccountId: accountId,
117+
Name: mrapName,
118+
})
119+
);
120+
mrapArn = getResponse.AccessPoint!.Alias!;
121+
122+
// Upload a small file to one of the buckets
123+
await s3Client.send(new PutObjectCommand({
124+
Bucket: bucketName1,
125+
Key: "testfile",
126+
Body: Buffer.from("test", "utf-8")
127+
}));
128+
});
129+
130+
afterAll(async () => {
131+
// Delete MRAP
132+
try {
133+
await s3ControlClient.send(
134+
new DeleteMultiRegionAccessPointCommand({
135+
AccountId: accountId,
136+
ClientToken: `delete-${Date.now()}`,
137+
Details: {
138+
Name: mrapName,
139+
},
140+
})
141+
);
142+
} catch (error) {
143+
console.error("Failed to initiate deletion of Multi-Region Access Point:", error);
144+
}
145+
146+
// Delete buckets
147+
try {
148+
await s3Client.send(new DeleteBucketCommand({ Bucket: bucketName1 }));
149+
await s3Client.send(new DeleteBucketCommand({ Bucket: bucketName2 }));
150+
} catch (error) {
151+
console.error("Failed to delete buckets:", error);
152+
}
153+
});
154+
155+
it("should use SignatureV4a JS implementation", async () => {
156+
const mockRequest = new HttpRequest({
157+
method: "GET",
158+
protocol: "https:",
159+
hostname: "s3-global.amazonaws.com",
160+
headers: {
161+
host: "s3-global.amazonaws.com",
162+
},
163+
path: "/",
164+
});
165+
166+
const signSpy = jest.spyOn(signer, "sign");
167+
168+
await signer.sign(mockRequest, { signingRegion: "*" });
169+
170+
expect(signSpy).toHaveBeenCalled();
171+
const signArgs = signSpy.mock.calls[0];
172+
expect(signArgs[1]?.signingRegion).toBe("*");
173+
174+
// verify that signed request has the expected SigV4a headers
175+
const signedRequest = await signSpy.mock.results[0].value;
176+
expect(signedRequest.headers["x-amz-region-set"]).toBe("*");
177+
expect(signedRequest.headers["authorization"]).toContain("AWS4-ECDSA-P256-SHA256");
178+
179+
signSpy.mockRestore();
180+
});
181+
182+
it("should list objects through MRAP using SignatureV4a", async () => {
183+
const command = new ListObjectsV2Command({
184+
Bucket: mrapArn,
185+
});
186+
187+
const response = await s3Client.send(command);
188+
189+
expect(response.Contents).toBeDefined();
190+
expect(response.Contents?.length).toBeGreaterThan(0);
191+
expect(response.Contents?.some(object => object.Key === "testfile")).toBe(true);
192+
});
193+
});

0 commit comments

Comments
 (0)