Merge pull request #15861 from strapi/private-s3-bucket/only-sign-media-from-private-bucket

This commit is contained in:
Marc 2023-02-20 14:29:36 +01:00 committed by GitHub
commit 13d90f66d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 113 additions and 0 deletions

View File

@ -0,0 +1,43 @@
'use strict';
const { getBucketFromUrl } = require('../utils');
describe('Test for URLs', () => {
test('Virtual hosted style', async () => {
const url = 'https://bucket.s3.us-east-1.amazonaws.com/img.png';
const { bucket } = getBucketFromUrl(url);
expect(bucket).toEqual('bucket');
});
describe('Path style', () => {
test('No key', async () => {
const url = 'https://s3.us-east-1.amazonaws.com/bucket';
const { bucket } = getBucketFromUrl(url);
expect(bucket).toEqual('bucket');
});
test('With trailing slash', async () => {
const url = 'https://s3.us-east-1.amazonaws.com/bucket/';
const { bucket } = getBucketFromUrl(url);
expect(bucket).toEqual('bucket');
});
test('With key', async () => {
const url = 'https://s3.us-east-1.amazonaws.com/bucket/img.png';
const { bucket } = getBucketFromUrl(url);
expect(bucket).toEqual('bucket');
});
});
test('S3 access point', async () => {
const url = 'https://bucket.s3-accesspoint.us-east-1.amazonaws.com';
const { bucket } = getBucketFromUrl(url);
expect(bucket).toEqual('bucket');
});
test('S3://', async () => {
const url = 'S3://bucket/img.png';
const { bucket } = getBucketFromUrl(url);
expect(bucket).toEqual('bucket');
});
});

View File

@ -8,6 +8,7 @@
// Public node modules.
const get = require('lodash/get');
const AWS = require('aws-sdk');
const { getBucketFromUrl } = require('./utils');
function assertUrlProtocol(url) {
// Regex to test protocol like "http://", "https://"
@ -66,6 +67,12 @@ module.exports = {
* @returns {Promise<{url: string}>}
*/
getSignedUrl(file, customParams = {}) {
// Do not sign the url if it does not come from the same bucket.
const { bucket } = getBucketFromUrl(file.url);
if (bucket !== config.params.Bucket) {
return { url: file.url };
}
return new Promise((resolve, reject) => {
const path = file.path ? `${file.path}/` : '';
const fileKey = `${path}${file.hash}${file.ext}`;

View File

@ -0,0 +1,63 @@
'use strict';
const ENDPOINT_PATTERN = /^(.+\.)?s3[.-]([a-z0-9-]+)\./;
/**
* Parse the bucket name from a URL.
* See all URL formats in https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html
*
* @param {string} fileUrl - the URL to parse
* @returns {object} result
* @returns {string} result.bucket - the bucket name
* @returns {string} result.error - if any
*/
function getBucketFromUrl(fileUrl) {
const uri = new URL(fileUrl);
// S3://<bucket-name>/<key>
if (uri.protocol === 's3:') {
const bucket = uri.host;
if (!bucket) {
return { err: `Invalid S3 URI: no bucket: ${uri}` };
}
return { bucket };
}
if (!uri.host) {
return { err: `Invalid S3 URI: no hostname: ${uri}` };
}
const matches = uri.host.match(ENDPOINT_PATTERN);
if (!matches) {
return { err: `Invalid S3 URI: hostname does not appear to be a valid S3 endpoint: ${uri}` };
}
const prefix = matches[1];
// https://s3.amazonaws.com/<bucket-name>
if (!prefix) {
if (uri.pathname === '/') {
return { bucket: null };
}
const index = uri.pathname.indexOf('/', 1);
// https://s3.amazonaws.com/<bucket-name>
if (index === -1) {
return { bucket: uri.pathname.substring(1) };
}
// https://s3.amazonaws.com/<bucket-name>/
if (index === uri.pathname.length - 1) {
return { bucket: uri.pathname.substring(1, index) };
}
// https://s3.amazonaws.com/<bucket-name>/key
return { bucket: uri.pathname.substring(1, index) };
}
// https://<bucket-name>.s3.amazonaws.com/
return { bucket: prefix.substring(0, prefix.length - 1) };
}
module.exports = { getBucketFromUrl };