Skip to content
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
315 changes: 180 additions & 135 deletions lib/api/apiUtils/quotas/quotaUtils.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion lib/api/objectCopy.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ function objectCopy(authInfo, request, sourceBucket, sourceObject, sourceVersion
objectKey: sourceObject,
versionId: sourceVersionId,
getDeleteMarker: true,
requestType: 'objectGet',
// Same action as the one authorized by Vault for the source
requestType: sourceVersionId ? 'objectGetVersion' : 'objectGet',
/**
* Authorization will first check the target object, with an objectPut
* action. But in this context, the source object metadata is still
Expand Down
860 changes: 506 additions & 354 deletions lib/api/objectPutCopyPart.js

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions tests/functional/aws-node-sdk/lib/utility/iam-util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const { S3Client } = require('@aws-sdk/client-s3');
const {
IAMClient,
CreateUserCommand,
CreateAccessKeyCommand,
DeleteAccessKeyCommand,
DeleteUserCommand,
CreatePolicyCommand,
AttachUserPolicyCommand,
DetachUserPolicyCommand,
DeletePolicyCommand,
} = require('@aws-sdk/client-iam');
const { v4: uuid } = require('uuid');

const getConfig = require('../../test/support/config');
const { config } = require('../../../../../lib/Config');

const vaultHost = config.vaultd?.host || 'localhost';

function makeIamClient(profile) {
const iamConfig = getConfig(profile, { region: 'us-east-1' });
iamConfig.endpoint = `http://${vaultHost}:8600`; // define outside of getConfig for Integration
return new IAMClient(iamConfig);
}

// Creates an IAM user with one managed policy, returns its S3 client and a
// function removing the user
async function createUserWithPolicy(iamClient, statements) {
const name = `test-user-${uuid()}`;
const policyResponse = await iamClient.send(
new CreatePolicyCommand({
PolicyName: name,
PolicyDocument: JSON.stringify({ Version: '2012-10-17', Statement: statements }),
}),
);
const policyArn = policyResponse.Policy.Arn;
await iamClient.send(new CreateUserCommand({ UserName: name }));
await iamClient.send(new AttachUserPolicyCommand({ UserName: name, PolicyArn: policyArn }));
const accessKeyResponse = await iamClient.send(new CreateAccessKeyCommand({ UserName: name }));
const { AccessKeyId, SecretAccessKey } = accessKeyResponse.AccessKey;
const s3Client = new S3Client(
getConfig('default', {
credentials: {
accessKeyId: AccessKeyId,
secretAccessKey: SecretAccessKey,
},
}),
);
async function deleteUser() {
await iamClient.send(new DeleteAccessKeyCommand({ UserName: name, AccessKeyId }));
await iamClient.send(new DetachUserPolicyCommand({ UserName: name, PolicyArn: policyArn }));
await iamClient.send(new DeletePolicyCommand({ PolicyArn: policyArn }));
await iamClient.send(new DeleteUserCommand({ UserName: name }));
}
return { s3Client, deleteUser };
}

module.exports = {
makeIamClient,
createUserWithPolicy,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
const assert = require('assert');
const {
PutObjectCommand,
GetObjectCommand,
CopyObjectCommand,
CreateMultipartUploadCommand,
UploadPartCopyCommand,
AbortMultipartUploadCommand,
PutBucketPolicyCommand,
DeleteBucketPolicyCommand,
PutBucketVersioningCommand,
} = require('@aws-sdk/client-s3');
const { v4: uuid } = require('uuid');

const BucketUtility = require('../../lib/utility/bucket-util');
const { makeIamClient, createUserWithPolicy } = require('../../lib/utility/iam-util');
const { config } = require('../../../../../lib/Config');

// this test needs a real vault to create IAM users
const isVaultScality = config.backends.auth !== 'mem';

const describeWithVault = isVaultScality ? describe : describe.skip;

describeWithVault('Copy of a specific source version', () => {
const secretKey = 'secret';

async function assertAccessDenied(promise) {
await assert.rejects(promise, { name: 'AccessDenied' });
}

describe('in the same account, without IAM permission to read the source', () => {
const ownBucketName = `copy-source-own-${uuid()}`;
const bucketUtil = new BucketUtility('default');
const iamClient = makeIamClient('default');
const mpuKey = 'mine/mpu-copy';
let user;
let uploadId;

before(async () => {
await bucketUtil.createOne(ownBucketName);
await bucketUtil.s3.send(
new PutObjectCommand({
Bucket: ownBucketName,
Key: secretKey,
Body: 'secret content',
}),
);
user = await createUserWithPolicy(iamClient, [
{
Effect: 'Allow',
Action: ['s3:GetObject', 's3:PutObject'],
Resource: `arn:aws:s3:::${ownBucketName}/mine/*`,
},
]);
const mpuResponse = await user.s3Client.send(
new CreateMultipartUploadCommand({
Bucket: ownBucketName,
Key: mpuKey,
}),
);
uploadId = mpuResponse.UploadId;
});

after(async () => {
if (uploadId) {
await bucketUtil.s3.send(
new AbortMultipartUploadCommand({
Bucket: ownBucketName,
Key: mpuKey,
UploadId: uploadId,
}),
);
}
await bucketUtil.empty(ownBucketName);
await bucketUtil.deleteOne(ownBucketName);
if (user) {
await user.deleteUser();
}
});

it('should deny CopyObject from the latest version', async () => {
await assertAccessDenied(
user.s3Client.send(
new CopyObjectCommand({
Bucket: ownBucketName,
Key: 'mine/copy',
CopySource: `${ownBucketName}/${secretKey}`,
}),
),
);
});

it('should deny CopyObject from the null version', async () => {
await assertAccessDenied(
user.s3Client.send(
new CopyObjectCommand({
Bucket: ownBucketName,
Key: 'mine/copy',
CopySource: `${ownBucketName}/${secretKey}?versionId=null`,
}),
),
);
});

it('should deny UploadPartCopy from the null version', async () => {
await assertAccessDenied(
user.s3Client.send(
new UploadPartCopyCommand({
Bucket: ownBucketName,
Key: mpuKey,
UploadId: uploadId,
PartNumber: 1,
CopySource: `${ownBucketName}/${secretKey}?versionId=null`,
}),
),
);
});
});

describe('with a bucket policy denying s3:GetObjectVersion', () => {
const policyBucketName = `copy-source-policy-${uuid()}`;
const bucketUtil = new BucketUtility('default');
const iamClient = makeIamClient('default');
const latestContent = 'latest content';
let user;
let oldVersionId;

before(async () => {
await bucketUtil.createOne(policyBucketName);
await bucketUtil.s3.send(
new PutBucketVersioningCommand({
Bucket: policyBucketName,
VersioningConfiguration: { Status: 'Enabled' },
}),
);
const oldPut = await bucketUtil.s3.send(
new PutObjectCommand({
Bucket: policyBucketName,
Key: secretKey,
Body: 'old content',
}),
);
oldVersionId = oldPut.VersionId;
await bucketUtil.s3.send(
new PutObjectCommand({
Bucket: policyBucketName,
Key: secretKey,
Body: latestContent,
}),
);
await bucketUtil.s3.send(
new PutBucketPolicyCommand({
Bucket: policyBucketName,
Policy: JSON.stringify({
Version: '2012-10-17',
Statement: [
{
Effect: 'Deny',
Principal: '*',
Action: 's3:GetObjectVersion',
Resource: `arn:aws:s3:::${policyBucketName}/*`,
},
],
}),
}),
);
user = await createUserWithPolicy(iamClient, [
{
Effect: 'Allow',
Action: ['s3:GetObject', 's3:GetObjectVersion', 's3:PutObject'],
Resource: `arn:aws:s3:::${policyBucketName}/*`,
},
]);
});

after(async () => {
await bucketUtil.s3.send(new DeleteBucketPolicyCommand({ Bucket: policyBucketName }));
await bucketUtil.empty(policyBucketName);
await bucketUtil.deleteOne(policyBucketName);
if (user) {
await user.deleteUser();
}
});

it('should deny GetObject of an older version', async () => {
await assertAccessDenied(
user.s3Client.send(
new GetObjectCommand({
Bucket: policyBucketName,
Key: secretKey,
VersionId: oldVersionId,
}),
),
);
});

it('should deny CopyObject from an older version', async () => {
await assertAccessDenied(
user.s3Client.send(
new CopyObjectCommand({
Bucket: policyBucketName,
Key: 'leak',
CopySource: `${policyBucketName}/${secretKey}?versionId=${oldVersionId}`,
}),
),
);
});

it('should allow CopyObject from the latest version', async () => {
await user.s3Client.send(
new CopyObjectCommand({
Bucket: policyBucketName,
Key: 'latest-copy',
CopySource: `${policyBucketName}/${secretKey}`,
}),
);
const getResponse = await user.s3Client.send(
new GetObjectCommand({
Bucket: policyBucketName,
Key: 'latest-copy',
}),
);
assert.strictEqual(await getResponse.Body.transformToString(), latestContent);
});
});
});
Loading
Loading