var AWS = require('../core'); var regionUtil = require('../region_config'); var s3util = { /** * @api private */ isArnInParam: function isArnInParam(req, paramName) { var inputShape = (req.service.api.operations[req.operation] || {}).input || {}; var inputMembers = inputShape.members || {}; if (!req.params[paramName] || !inputMembers[paramName]) return false; return AWS.util.ARN.validate(req.params[paramName]); }, /** * Validate service component from ARN supplied in Bucket parameter */ validateArnService: function validateArnService(req) { var parsedArn = req._parsedArn; if (parsedArn.service !== 's3' && parsedArn.service !== 's3-outposts' && parsedArn.service !== 's3-object-lambda') { throw AWS.util.error(new Error(), { code: 'InvalidARN', message: 'expect \'s3\' or \'s3-outposts\' or \'s3-object-lambda\' in ARN service component' }); } }, /** * Validate account ID from ARN supplied in Bucket parameter is a valid account */ validateArnAccount: function validateArnAccount(req) { var parsedArn = req._parsedArn; if (!/[0-9]{12}/.exec(parsedArn.accountId)) { throw AWS.util.error(new Error(), { code: 'InvalidARN', message: 'ARN accountID does not match regex "[0-9]{12}"' }); } }, /** * Validate ARN supplied in Bucket parameter is a valid access point ARN */ validateS3AccessPointArn: function validateS3AccessPointArn(req) { var parsedArn = req._parsedArn; //can be ':' or '/' var delimiter = parsedArn.resource['accesspoint'.length]; if (parsedArn.resource.split(delimiter).length !== 2) { throw AWS.util.error(new Error(), { code: 'InvalidARN', message: 'Access Point ARN should have one resource accesspoint/{accesspointName}' }); } var accessPoint = parsedArn.resource.split(delimiter)[1]; var accessPointPrefix = accessPoint + '-' + parsedArn.accountId; if (!s3util.dnsCompatibleBucketName(accessPointPrefix) || accessPointPrefix.match(/\./)) { throw AWS.util.error(new Error(), { code: 'InvalidARN', message: 'Access point resource in ARN is not DNS compatible. Got ' + accessPoint }); } //set parsed valid access point req._parsedArn.accessPoint = accessPoint; }, /** * Validate Outposts ARN supplied in Bucket parameter is a valid outposts ARN */ validateOutpostsArn: function validateOutpostsArn(req) { var parsedArn = req._parsedArn; if ( parsedArn.resource.indexOf('outpost:') !== 0 && parsedArn.resource.indexOf('outpost/') !== 0 ) { throw AWS.util.error(new Error(), { code: 'InvalidARN', message: 'ARN resource should begin with \'outpost/\'' }); } //can be ':' or '/' var delimiter = parsedArn.resource['outpost'.length]; var outpostId = parsedArn.resource.split(delimiter)[1]; var dnsHostRegex = new RegExp(/^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/); if (!dnsHostRegex.test(outpostId)) { throw AWS.util.error(new Error(), { code: 'InvalidARN', message: 'Outpost resource in ARN is not DNS compatible. Got ' + outpostId }); } req._parsedArn.outpostId = outpostId; }, /** * Validate Outposts ARN supplied in Bucket parameter is a valid outposts ARN */ validateOutpostsAccessPointArn: function validateOutpostsAccessPointArn(req) { var parsedArn = req._parsedArn; //can be ':' or '/' var delimiter = parsedArn.resource['outpost'.length]; if (parsedArn.resource.split(delimiter).length !== 4) { throw AWS.util.error(new Error(), { code: 'InvalidARN', message: 'Outposts ARN should have two resources outpost/{outpostId}/accesspoint/{accesspointName}' }); } var accessPoint = parsedArn.resource.split(delimiter)[3]; var accessPointPrefix = accessPoint + '-' + parsedArn.accountId; if (!s3util.dnsCompatibleBucketName(accessPointPrefix) || accessPointPrefix.match(/\./)) { throw AWS.util.error(new Error(), { code: 'InvalidARN', message: 'Access point resource in ARN is not DNS compatible. Got ' + accessPoint }); } //set parsed valid access point req._parsedArn.accessPoint = accessPoint; }, /** * Validate region field in ARN supplied in Bucket parameter is a valid region */ validateArnRegion: function validateArnRegion(req, options) { if (options === undefined) { options = {}; } var useArnRegion = s3util.loadUseArnRegionConfig(req); var regionFromArn = req._parsedArn.region; var clientRegion = req.service.config.region; var useFipsEndpoint = req.service.config.useFipsEndpoint; var allowFipsEndpoint = options.allowFipsEndpoint || false; if (!regionFromArn) { var message = 'ARN region is empty'; if (req._parsedArn.service === 's3') { message = message + '\nYou may want to use multi-regional ARN. The feature is not supported in current SDK. ' + 'You should consider switching to V3(https://github.com/aws/aws-sdk-js-v3).'; } throw AWS.util.error(new Error(), { code: 'InvalidARN', message: message }); } if (useFipsEndpoint && !allowFipsEndpoint) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: 'ARN endpoint is not compatible with FIPS region' }); } if (regionFromArn.indexOf('fips') >= 0) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: 'FIPS region not allowed in ARN' }); } if (!useArnRegion && regionFromArn !== clientRegion) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: 'Configured region conflicts with access point region' }); } else if ( useArnRegion && regionUtil.getEndpointSuffix(regionFromArn) !== regionUtil.getEndpointSuffix(clientRegion) ) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: 'Configured region and access point region not in same partition' }); } if (req.service.config.useAccelerateEndpoint) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: 'useAccelerateEndpoint config is not supported with access point ARN' }); } if (req._parsedArn.service === 's3-outposts' && req.service.config.useDualstackEndpoint) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: 'Dualstack is not supported with outposts access point ARN' }); } }, loadUseArnRegionConfig: function loadUseArnRegionConfig(req) { var envName = 'AWS_S3_USE_ARN_REGION'; var configName = 's3_use_arn_region'; var useArnRegion = true; var originalConfig = req.service._originalConfig || {}; if (req.service.config.s3UseArnRegion !== undefined) { return req.service.config.s3UseArnRegion; } else if (originalConfig.s3UseArnRegion !== undefined) { useArnRegion = originalConfig.s3UseArnRegion === true; } else if (AWS.util.isNode()) { //load from environmental variable AWS_USE_ARN_REGION if (process.env[envName]) { var value = process.env[envName].trim().toLowerCase(); if (['false', 'true'].indexOf(value) < 0) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: envName + ' only accepts true or false. Got ' + process.env[envName], retryable: false }); } useArnRegion = value === 'true'; } else { //load from shared config property use_arn_region var profiles = {}; var profile = {}; try { profiles = AWS.util.getProfilesFromSharedConfig(AWS.util.iniLoader); profile = profiles[process.env.AWS_PROFILE || AWS.util.defaultProfile]; } catch (e) {} if (profile[configName]) { if (['false', 'true'].indexOf(profile[configName].trim().toLowerCase()) < 0) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: configName + ' only accepts true or false. Got ' + profile[configName], retryable: false }); } useArnRegion = profile[configName].trim().toLowerCase() === 'true'; } } } req.service.config.s3UseArnRegion = useArnRegion; return useArnRegion; }, /** * Validations before URI can be populated */ validatePopulateUriFromArn: function validatePopulateUriFromArn(req) { if (req.service._originalConfig && req.service._originalConfig.endpoint) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: 'Custom endpoint is not compatible with access point ARN' }); } if (req.service.config.s3ForcePathStyle) { throw AWS.util.error(new Error(), { code: 'InvalidConfiguration', message: 'Cannot construct path-style endpoint with access point' }); } }, /** * Returns true if the bucket name is DNS compatible. Buckets created * outside of the classic region MUST be DNS compatible. * * @api private */ dnsCompatibleBucketName: function dnsCompatibleBucketName(bucketName) { var b = bucketName; var domain = new RegExp(/^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$/); var ipAddress = new RegExp(/(\d+\.){3}\d+/); var dots = new RegExp(/\.\./); return (b.match(domain) && !b.match(ipAddress) && !b.match(dots)) ? true : false; }, }; /** * @api private */ module.exports = s3util;