Compare commits
10 Commits
developmen
...
bigfix/S3C
Author | SHA1 | Date |
---|---|---|
Dora Korpar | be6f339953 | |
Dora Korpar | 65a64ee3b9 | |
Dora Korpar | d56eb5e1b3 | |
Dora Korpar | 536091af43 | |
Dora Korpar | 29bf102e54 | |
Dora Korpar | 9ac1af7676 | |
Dora Korpar | 26ae9086dd | |
Dora Korpar | 4c2236e65c | |
Dora Korpar | eb09dea1a8 | |
Dora Korpar | eceb8f0477 |
23
constants.js
23
constants.js
|
@ -122,6 +122,29 @@ const constants = {
|
||||||
'(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'),
|
'(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'),
|
||||||
// user metadata applied on zenko objects
|
// user metadata applied on zenko objects
|
||||||
zenkoIDHeader: 'x-amz-meta-zenko-instance-id',
|
zenkoIDHeader: 'x-amz-meta-zenko-instance-id',
|
||||||
|
bucketOwnerActions: [
|
||||||
|
'bucketDeleteCors',
|
||||||
|
'bucketDeleteLifecycle',
|
||||||
|
'bucketDeletePolicy',
|
||||||
|
'bucketDeleteReplication',
|
||||||
|
'bucketDeleteWebsite',
|
||||||
|
'bucketGetCors',
|
||||||
|
'bucketGetLifecycle',
|
||||||
|
'bucketGetLocation',
|
||||||
|
'bucketGetPolicy',
|
||||||
|
'bucketGetReplication',
|
||||||
|
'bucketGetVersioning',
|
||||||
|
'bucketGetWebsite',
|
||||||
|
'bucketPutCors',
|
||||||
|
'bucketPutLifecycle',
|
||||||
|
'bucketPutPolicy',
|
||||||
|
'bucketPutReplication',
|
||||||
|
'bucketPutVersioning',
|
||||||
|
'bucketPutWebsite',
|
||||||
|
'objectDeleteTagging',
|
||||||
|
'objectGetTagging',
|
||||||
|
'objectPutTagging',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = constants;
|
module.exports = constants;
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
# Bucket Policy Documentation
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Bucket policy is a method of controlling access to a user's account at the
|
||||||
|
resource level.
|
||||||
|
There are three associated APIs:
|
||||||
|
|
||||||
|
- PUT Bucket policy (see https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTpolicy.html)
|
||||||
|
- GET Bucket policy (see https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETpolicy.html)
|
||||||
|
- DELETE Bucket policy (see https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketDELETEpolicy.html)
|
||||||
|
|
||||||
|
More information on bucket policies in general can be found at
|
||||||
|
https://docs.aws.amazon.com/AmazonS3/latest/dev/using-iam-policies.html.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
To prevent loss of access to a bucket, the root owner of a bucket will always
|
||||||
|
be able to perform any of the three bucket policy-related operations, even
|
||||||
|
if permission is explicitly denied.
|
||||||
|
All other users must have permission to perform the desired operation.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
On a PUTBucketPolicy request, the user provides a policy in JSON format.
|
||||||
|
The policy is evaluated against our policy schema in Arsenal and, once
|
||||||
|
validated, is stored as part of the bucket's metadata.
|
||||||
|
On a GETBucketPolicy request, the policy is retrieved from the bucket's
|
||||||
|
metadata.
|
||||||
|
On a DELETEBucketPolicy request, the policy is deleted from the bucket's
|
||||||
|
metadata.
|
||||||
|
|
||||||
|
All other APIs are updated to check if a bucket policy is attached to the bucket
|
||||||
|
the request is made on. If there is a policy, user authorization to perform
|
||||||
|
the requested action is checked.
|
||||||
|
|
||||||
|
### Differences Between Bucket and IAM Policies
|
||||||
|
|
||||||
|
IAM policies are attached to an IAM identity and define what actions that
|
||||||
|
identity is allowed to or denied from doing on what resource.
|
||||||
|
Bucket policies attach only to buckets and define what actions are allowed or
|
||||||
|
denied for which principles on that bucket. Permissions specified in a bucket
|
||||||
|
policy apply to all objects in that bucket unless otherwise specified.
|
||||||
|
|
||||||
|
Besides their attachment origins, the main structural difference between
|
||||||
|
IAM policy and bucket policy is the requirement of a "Principal" element in
|
||||||
|
bucket policies. This field is redundant in IAM policies.
|
||||||
|
|
||||||
|
### Policy Validation
|
||||||
|
|
||||||
|
For general guidelines for bucket policy structure, see examples here:
|
||||||
|
https://docs.aws.amazon.com/AmazonS3/latest/dev//example-bucket-policies.html.
|
||||||
|
|
||||||
|
Each bucket policy statement object requires at least four keys:
|
||||||
|
"Effect", "Principle", "Resource", and "Action".
|
||||||
|
|
||||||
|
"Effect" defines the effect of the policy and can have a string value of either
|
||||||
|
"Allow" or "Deny".
|
||||||
|
"Resource" defines to which bucket or list of buckets a policy is attached.
|
||||||
|
An object within the bucket is also a valid resource. The element value can be
|
||||||
|
either a single bucket or object ARN string or an array of ARNs.
|
||||||
|
"Action" lists which action(s) the policy controls. Its value can also be either
|
||||||
|
a string or array of S3 APIs. Each action is the API name prepended by "s3:".
|
||||||
|
"Principle" specifies which user(s) are granted or denied access to the bucket
|
||||||
|
resource. Its value can be a string or an object containing an array of users.
|
||||||
|
Valid users can be identified with an account ARN, account id, or user ARN.
|
||||||
|
|
||||||
|
There are also two optional bucket policy statement keys: Sid and Condition.
|
||||||
|
|
||||||
|
"Sid" stands for "statement id". If this key is not included, one will be
|
||||||
|
generated for the statement.
|
||||||
|
"Condition" lists the condition under which a statement will take affect.
|
||||||
|
The possibilities are as follows:
|
||||||
|
|
||||||
|
- ArnEquals
|
||||||
|
- ArnEqualsIfExists
|
||||||
|
- ArnLike
|
||||||
|
- ArnLikeIfExists
|
||||||
|
- ArnNotEquals
|
||||||
|
- ArnNotEqualsIfExists
|
||||||
|
- ArnNotLike
|
||||||
|
- ArnNotLikeIfExists
|
||||||
|
- BinaryEquals
|
||||||
|
- BinaryEqualsIfExists
|
||||||
|
- BinaryNotEquals
|
||||||
|
- BinaryNotEqualsIfExists
|
||||||
|
- Bool
|
||||||
|
- BoolIfExists
|
||||||
|
- DateEquals
|
||||||
|
- DateEqualsIfExists
|
||||||
|
- DateGreaterThan
|
||||||
|
- DateGreaterThanEquals
|
||||||
|
- DateGreaterThanEqualsIfExists
|
||||||
|
- DateGreaterThanIfExists
|
||||||
|
- DateLessThan
|
||||||
|
- DateLessThanEquals
|
||||||
|
- DateLessThanEqualsIfExists
|
||||||
|
- DateLessThanIfExists
|
||||||
|
- DateNotEquals
|
||||||
|
- DateNotEqualsIfExists
|
||||||
|
- IpAddress
|
||||||
|
- IpAddressIfExists
|
||||||
|
- NotIpAddress
|
||||||
|
- NotIpAddressIfExists
|
||||||
|
- Null
|
||||||
|
- NumericEquals
|
||||||
|
- NumericEqualsIfExists
|
||||||
|
- NumericGreaterThan
|
||||||
|
- NumericGreaterThanEquals
|
||||||
|
- NumericGreaterThanEqualsIfExists
|
||||||
|
- NumericGreaterThanIfExists
|
||||||
|
- NumericLessThan
|
||||||
|
- NumericLessThanEquals
|
||||||
|
- NumericLessThanEqualsIfExists
|
||||||
|
- NumericLessThanIfExists
|
||||||
|
- NumericNotEquals
|
||||||
|
- NumericNotEqualsIfExists
|
||||||
|
- StringEquals
|
||||||
|
- StringEqualsIfExists
|
||||||
|
- StringEqualsIgnoreCase
|
||||||
|
- StringEqualsIgnoreCaseIfExists
|
||||||
|
- StringLike
|
||||||
|
- StringLikeIfExists
|
||||||
|
- StringNotEquals
|
||||||
|
- StringNotEqualsIfExists
|
||||||
|
- StringNotEqualsIgnoreCase
|
||||||
|
- StringNotEqualsIgnoreCaseIfExists
|
||||||
|
- StringNotLike
|
||||||
|
- StringNotLikeIfExists
|
||||||
|
|
||||||
|
The value of the Condition key will be an object containing the desired
|
||||||
|
condition name as that key. The value of inner object can be a string, boolean,
|
||||||
|
number, or object, depending on the condition.
|
||||||
|
|
||||||
|
## Authorization with Multiple Access Control Mechanisms
|
||||||
|
|
||||||
|
In the case where multiple access control mechanisms (such as IAM policies,
|
||||||
|
bucket policies, and ACLs) refer to the same resource, the principle of
|
||||||
|
least-privilege is applied. Unless an action is explicitly allowed, access will
|
||||||
|
by default be denied. An explicit DENY in any policy will trump another
|
||||||
|
policy's ALLOW for an action. The request will only be allowed if at least one
|
||||||
|
policy specifies an ALLOW, and there is no overriding DENY.
|
||||||
|
|
||||||
|
The following diagram illustrates this logic:
|
||||||
|
|
||||||
|
![Access_Control_Authorization_Chart](./images/access_control_authorization.png)
|
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
|
@ -1,138 +0,0 @@
|
||||||
const constants = require('../../../../constants');
|
|
||||||
|
|
||||||
// whitelist buckets to allow public read on objects
|
|
||||||
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS ?
|
|
||||||
process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];
|
|
||||||
|
|
||||||
function isBucketAuthorized(bucket, requestType, canonicalID) {
|
|
||||||
// Check to see if user is authorized to perform a
|
|
||||||
// particular action on bucket based on ACLs.
|
|
||||||
// TODO: Add IAM checks and bucket policy checks.
|
|
||||||
if (bucket.getOwner() === canonicalID) {
|
|
||||||
return true;
|
|
||||||
} else if (requestType === 'bucketOwnerAction') {
|
|
||||||
// only bucket owner can modify or retrieve this property of a bucket
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const bucketAcl = bucket.getAcl();
|
|
||||||
if (requestType === 'bucketGet' || requestType === 'bucketHead') {
|
|
||||||
if (bucketAcl.Canned === 'public-read'
|
|
||||||
|| bucketAcl.Canned === 'public-read-write'
|
|
||||||
|| (bucketAcl.Canned === 'authenticated-read'
|
|
||||||
&& canonicalID !== constants.publicId)) {
|
|
||||||
return true;
|
|
||||||
} else if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
|
||||||
|| bucketAcl.READ.indexOf(canonicalID) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestType === 'bucketGetACL') {
|
|
||||||
if ((bucketAcl.Canned === 'log-delivery-write'
|
|
||||||
&& canonicalID === constants.logId)
|
|
||||||
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
|
||||||
|| bucketAcl.READ_ACP.indexOf(canonicalID) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestType === 'bucketPutACL') {
|
|
||||||
if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
|
||||||
|| bucketAcl.WRITE_ACP.indexOf(canonicalID) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestType === 'bucketDelete' && bucket.getOwner() === canonicalID) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestType === 'objectDelete' || requestType === 'objectPut') {
|
|
||||||
if (bucketAcl.Canned === 'public-read-write'
|
|
||||||
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
|
||||||
|| bucketAcl.WRITE.indexOf(canonicalID) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Note that an account can have the ability to do objectPutACL,
|
|
||||||
// objectGetACL, objectHead or objectGet even if the account has no rights
|
|
||||||
// to the bucket holding the object. So, if the request type is
|
|
||||||
// objectPutACL, objectGetACL, objectHead or objectGet, the bucket
|
|
||||||
// authorization check should just return true so can move on to check
|
|
||||||
// rights at the object level.
|
|
||||||
|
|
||||||
return (requestType === 'objectPutACL' || requestType === 'objectGetACL' ||
|
|
||||||
requestType === 'objectGet' || requestType === 'objectHead');
|
|
||||||
}
|
|
||||||
|
|
||||||
function isObjAuthorized(bucket, objectMD, requestType, canonicalID) {
|
|
||||||
const bucketOwner = bucket.getOwner();
|
|
||||||
if (!objectMD) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (objectMD['owner-id'] === canonicalID) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// account is authorized if:
|
|
||||||
// - requesttype is "bucketOwnerAction" (example: for objectTagging) and
|
|
||||||
// - account is the bucket owner
|
|
||||||
if (requestType === 'bucketOwnerAction' && bucketOwner === canonicalID) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (requestType === 'objectGet' || requestType === 'objectHead') {
|
|
||||||
if (objectMD.acl.Canned === 'public-read'
|
|
||||||
|| objectMD.acl.Canned === 'public-read-write'
|
|
||||||
|| (objectMD.acl.Canned === 'authenticated-read'
|
|
||||||
&& canonicalID !== constants.publicId)) {
|
|
||||||
return true;
|
|
||||||
} else if (objectMD.acl.Canned === 'bucket-owner-read'
|
|
||||||
&& bucketOwner === canonicalID) {
|
|
||||||
return true;
|
|
||||||
} else if ((objectMD.acl.Canned === 'bucket-owner-full-control'
|
|
||||||
&& bucketOwner === canonicalID)
|
|
||||||
|| objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
|
|
||||||
|| objectMD.acl.READ.indexOf(canonicalID) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User is already authorized on the bucket for FULL_CONTROL or WRITE or
|
|
||||||
// bucket has canned ACL public-read-write
|
|
||||||
if (requestType === 'objectPut' || requestType === 'objectDelete') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestType === 'objectPutACL') {
|
|
||||||
if ((objectMD.acl.Canned === 'bucket-owner-full-control'
|
|
||||||
&& bucketOwner === canonicalID)
|
|
||||||
|| objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
|
|
||||||
|| objectMD.acl.WRITE_ACP.indexOf(canonicalID) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestType === 'objectGetACL') {
|
|
||||||
if ((objectMD.acl.Canned === 'bucket-owner-full-control'
|
|
||||||
&& bucketOwner === canonicalID)
|
|
||||||
|| objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
|
|
||||||
|| objectMD.acl.READ_ACP.indexOf(canonicalID) > -1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow public reads on buckets that are whitelisted for anonymous reads
|
|
||||||
// TODO: remove this after bucket policies are implemented
|
|
||||||
const bucketAcl = bucket.getAcl();
|
|
||||||
const allowPublicReads = publicReadBuckets.includes(bucket.getName()) &&
|
|
||||||
bucketAcl.Canned === 'public-read' &&
|
|
||||||
(requestType === 'objectGet' || requestType === 'objectHead');
|
|
||||||
if (allowPublicReads) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
isBucketAuthorized,
|
|
||||||
isObjAuthorized,
|
|
||||||
};
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
const { evaluators } = require('arsenal').policies;
|
||||||
|
const constants = require('../../../../constants');
|
||||||
|
|
||||||
|
const actionMap = {
|
||||||
|
's3:AbortMultipartUpload': 'multipartDelete',
|
||||||
|
's3:DeleteBucket': 'bucketDelete',
|
||||||
|
's3:DeleteBucketPolicy': 'bucketDeletePolicy',
|
||||||
|
's3:DeleteBucketWebsite': 'bucketDeleteWebsite',
|
||||||
|
's3:DeleteObject': 'objectDelete',
|
||||||
|
's3:DeleteObjectTagging': 'objectDeleteTagging',
|
||||||
|
's3:GetBucketAcl': 'bucketGetACL',
|
||||||
|
's3:GetBucketCORS': 'bucketGetCors',
|
||||||
|
's3:GetBucketLocation': 'bucketGetLocation',
|
||||||
|
's3:GetBucketPolicy': 'bucketGetPolicy',
|
||||||
|
's3:GetBucketVersioning': 'bucketGetVersioning',
|
||||||
|
's3:GetBucketWebsite': 'bucketGetWebsite',
|
||||||
|
's3:GetLifecycleConfiguration': 'bucketGetLifecycle',
|
||||||
|
's3:GetObject': 'objectGet',
|
||||||
|
's3:GetObjectAcl': 'objectGetACL',
|
||||||
|
's3:GetObjectTagging': 'objectGetTagging',
|
||||||
|
's3:GetReplicationConfiguration': 'bucketGetReplication',
|
||||||
|
's3:ListBucket': 'bucketHead',
|
||||||
|
's3:ListBucketMultipartUploads': 'listMultipartUploads',
|
||||||
|
's3:ListMultipartUploadParts': 'listParts',
|
||||||
|
's3:PutBucketAcl': 'bucketPutACL',
|
||||||
|
's3:PutBucketCORS': 'bucketPutCors',
|
||||||
|
's3:PutBucketPolicy': 'bucketPutPolicy',
|
||||||
|
's3:PutBucketVersioning': 'bucketPutVersioning',
|
||||||
|
's3:PutBucketWebsite': 'bucketPutWebsite',
|
||||||
|
's3:PutLifecycleConfiguration': 'bucketPutLifecycle',
|
||||||
|
's3:PutObject': 'objectPut',
|
||||||
|
's3:PutObjectAcl': 'objectPutACL',
|
||||||
|
's3:PutObjectTagging': 'objectPutTagging',
|
||||||
|
's3:PutReplicationConfiguration': 'bucketPutReplication',
|
||||||
|
};
|
||||||
|
|
||||||
|
// whitelist buckets to allow public read on objects
|
||||||
|
const publicReadBuckets = process.env.ALLOW_PUBLIC_READ_BUCKETS ?
|
||||||
|
process.env.ALLOW_PUBLIC_READ_BUCKETS.split(',') : [];
|
||||||
|
|
||||||
|
function checkBucketAcls(bucket, requestType, canonicalID) {
|
||||||
|
if (constants.bucketOwnerActions.includes(requestType)) {
|
||||||
|
// only bucket owner can modify or retrieve this property of a bucket
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bucketAcl = bucket.getAcl();
|
||||||
|
if (requestType === 'bucketGet' || requestType === 'bucketHead') {
|
||||||
|
if (bucketAcl.Canned === 'public-read'
|
||||||
|
|| bucketAcl.Canned === 'public-read-write'
|
||||||
|
|| (bucketAcl.Canned === 'authenticated-read'
|
||||||
|
&& canonicalID !== constants.publicId)) {
|
||||||
|
return true;
|
||||||
|
} else if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||||
|
|| bucketAcl.READ.indexOf(canonicalID) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (requestType === 'bucketGetACL') {
|
||||||
|
if ((bucketAcl.Canned === 'log-delivery-write'
|
||||||
|
&& canonicalID === constants.logId)
|
||||||
|
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||||
|
|| bucketAcl.READ_ACP.indexOf(canonicalID) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestType === 'bucketPutACL') {
|
||||||
|
if (bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||||
|
|| bucketAcl.WRITE_ACP.indexOf(canonicalID) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestType === 'bucketDelete' && bucket.getOwner() === canonicalID) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestType === 'objectDelete' || requestType === 'objectPut') {
|
||||||
|
if (bucketAcl.Canned === 'public-read-write'
|
||||||
|
|| bucketAcl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||||
|
|| bucketAcl.WRITE.indexOf(canonicalID) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Note that an account can have the ability to do objectPutACL,
|
||||||
|
// objectGetACL, objectHead or objectGet even if the account has no rights
|
||||||
|
// to the bucket holding the object. So, if the request type is
|
||||||
|
// objectPutACL, objectGetACL, objectHead or objectGet, the bucket
|
||||||
|
// authorization check should just return true so can move on to check
|
||||||
|
// rights at the object level.
|
||||||
|
return (requestType === 'objectPutACL' || requestType === 'objectGetACL' ||
|
||||||
|
requestType === 'objectGet' || requestType === 'objectHead');
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkObjectAcls(bucket, objectMD, requestType, canonicalID) {
|
||||||
|
if (!objectMD.acl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bucketOwner = bucket.getOwner();
|
||||||
|
|
||||||
|
if (requestType === 'objectGet' || requestType === 'objectHead') {
|
||||||
|
if (objectMD.acl.Canned === 'public-read'
|
||||||
|
|| objectMD.acl.Canned === 'public-read-write'
|
||||||
|
|| (objectMD.acl.Canned === 'authenticated-read'
|
||||||
|
&& canonicalID !== constants.publicId)) {
|
||||||
|
return true;
|
||||||
|
} else if (objectMD.acl.Canned === 'bucket-owner-read'
|
||||||
|
&& bucketOwner === canonicalID) {
|
||||||
|
return true;
|
||||||
|
} else if ((objectMD.acl.Canned === 'bucket-owner-full-control'
|
||||||
|
&& bucketOwner === canonicalID)
|
||||||
|
|| objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||||
|
|| objectMD.acl.READ.indexOf(canonicalID) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is already authorized on the bucket for FULL_CONTROL or WRITE or
|
||||||
|
// bucket has canned ACL public-read-write
|
||||||
|
if (requestType === 'objectPut' || requestType === 'objectDelete') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestType === 'objectPutACL') {
|
||||||
|
if ((objectMD.acl.Canned === 'bucket-owner-full-control'
|
||||||
|
&& bucketOwner === canonicalID)
|
||||||
|
|| objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||||
|
|| objectMD.acl.WRITE_ACP.indexOf(canonicalID) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestType === 'objectGetACL') {
|
||||||
|
if ((objectMD.acl.Canned === 'bucket-owner-full-control'
|
||||||
|
&& bucketOwner === canonicalID)
|
||||||
|
|| objectMD.acl.FULL_CONTROL.indexOf(canonicalID) > -1
|
||||||
|
|| objectMD.acl.READ_ACP.indexOf(canonicalID) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow public reads on buckets that are whitelisted for anonymous reads
|
||||||
|
// TODO: remove this after bucket policies are implemented
|
||||||
|
const bucketAcl = bucket.getAcl();
|
||||||
|
const allowPublicReads = publicReadBuckets.includes(bucket.getName()) &&
|
||||||
|
bucketAcl.Canned === 'public-read' &&
|
||||||
|
(requestType === 'objectGet' || requestType === 'objectHead');
|
||||||
|
if (allowPublicReads) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _checkActions(requestType, actions, log) {
|
||||||
|
// if requestType isn't in list of controlled actions
|
||||||
|
if (!Object.values(actionMap).includes(requestType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const mappedAction = Object.keys(actionMap)
|
||||||
|
[Object.values(actionMap).indexOf(requestType)];
|
||||||
|
return evaluators.isActionApplicable(mappedAction, actions, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getAccountId(arn) {
|
||||||
|
// account or user arn is of format 'arn:aws:iam::<12-digit-acct-id>:etc...
|
||||||
|
return arn.substr(13, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isAccountId(principal) {
|
||||||
|
return (principal.length === 12 && /^\d+$/.test(principal));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _checkPrincipal(requester, principal) {
|
||||||
|
if (principal === '*') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (principal === requester) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (_isAccountId(principal)) {
|
||||||
|
return _getAccountId(requester) === principal;
|
||||||
|
}
|
||||||
|
if (principal.endsWith('root')) {
|
||||||
|
return _getAccountId(requester) === _getAccountId(principal);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _checkPrincipals(canonicalID, arn, principal) {
|
||||||
|
if (principal === '*') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (principal.CanonicalUser) {
|
||||||
|
if (Array.isArray(principal.CanonicalUser)) {
|
||||||
|
return principal.CanonicalUser.some(p => _checkPrincipal(canonicalID, p));
|
||||||
|
}
|
||||||
|
return _checkPrincipal(canonicalID, principal.CanonicalUser);
|
||||||
|
}
|
||||||
|
if (principal.AWS) {
|
||||||
|
if (Array.isArray(principal.AWS)) {
|
||||||
|
return principal.AWS.some(p => _checkPrincipal(arn, p));
|
||||||
|
}
|
||||||
|
return _checkPrincipal(arn, principal.AWS);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkBucketPolicy(policy, requestType, canonicalID, arn, log) {
|
||||||
|
let permission = 'defaultDeny';
|
||||||
|
let copiedStatement = JSON.parse(JSON.stringify(policy.Statement));
|
||||||
|
while (copiedStatement.length > 0) {
|
||||||
|
const s = copiedStatement[0];
|
||||||
|
const principalMatch = _checkPrincipals(canonicalID, arn, s.Principal);
|
||||||
|
const actionMatch = _checkActions(requestType, s.Action, log);
|
||||||
|
|
||||||
|
if (principalMatch && actionMatch && s.Effect === 'Deny') {
|
||||||
|
// explicit deny trumps any allows, so return immediately
|
||||||
|
return 'explicitDeny';
|
||||||
|
}
|
||||||
|
if (principalMatch && actionMatch && s.Effect === 'Allow') {
|
||||||
|
permission = 'allow';
|
||||||
|
}
|
||||||
|
copiedStatement = copiedStatement.splice(1);
|
||||||
|
}
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBucketAuthorized(bucket, requestType, canonicalID, arn, log) {
|
||||||
|
// Check to see if user is authorized to perform a
|
||||||
|
// particular action on bucket based on ACLs.
|
||||||
|
// TODO: Add IAM checks
|
||||||
|
if (bucket.getOwner() === canonicalID) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const aclPermission = checkBucketAcls(bucket, requestType, canonicalID);
|
||||||
|
const bucketPolicy = bucket.getBucketPolicy();
|
||||||
|
if (!bucketPolicy) {
|
||||||
|
if (constants.bucketOwnerActions.includes(requestType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return aclPermission;
|
||||||
|
}
|
||||||
|
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType,
|
||||||
|
canonicalID, arn, log);
|
||||||
|
if (bucketPolicyPermission === 'explicitDeny') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (aclPermission || (bucketPolicyPermission === 'allow'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObjAuthorized(bucket, objectMD, requestType, canonicalID, arn, log) {
|
||||||
|
const bucketOwner = bucket.getOwner();
|
||||||
|
if (!objectMD) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (objectMD['owner-id'] === canonicalID) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// account is authorized if:
|
||||||
|
// - requesttype is included in bucketOwnerActions and
|
||||||
|
// - account is the bucket owner
|
||||||
|
if (constants.bucketOwnerActions.includes(requestType)
|
||||||
|
&& bucketOwner === canonicalID) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const aclPermission = checkObjectAcls(bucket, objectMD, requestType,
|
||||||
|
canonicalID);
|
||||||
|
const bucketPolicy = bucket.getBucketPolicy();
|
||||||
|
if (!bucketPolicy) {
|
||||||
|
return aclPermission;
|
||||||
|
}
|
||||||
|
const bucketPolicyPermission = checkBucketPolicy(bucketPolicy, requestType,
|
||||||
|
canonicalID, arn, log);
|
||||||
|
if (bucketPolicyPermission === 'explicitDeny') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (aclPermission || (bucketPolicyPermission === 'allow'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _checkResource(resource, bucketArn) {
|
||||||
|
if (resource === bucketArn) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (resource.includes('/')) {
|
||||||
|
const rSubs = resource.split('/');
|
||||||
|
return rSubs[0] === bucketArn;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the resources specified in the bucket policy should contain the bucket name
|
||||||
|
function validatePolicyResource(bucketName, policy) {
|
||||||
|
const bucketArn = `arn:aws:s3:::${bucketName}`;
|
||||||
|
|
||||||
|
return policy.Statement.every(s => {
|
||||||
|
if (Array.isArray(s.Resource)) {
|
||||||
|
return s.Resource.every(r => _checkResource(r, bucketArn));
|
||||||
|
}
|
||||||
|
if (typeof s.Resource === 'string') {
|
||||||
|
return _checkResource(s.Resource, bucketArn);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isBucketAuthorized,
|
||||||
|
isObjAuthorized,
|
||||||
|
checkBucketAcls,
|
||||||
|
checkObjectAcls,
|
||||||
|
validatePolicyResource,
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
const invisiblyDelete = require('./invisiblyDelete');
|
const invisiblyDelete = require('./invisiblyDelete');
|
||||||
|
const constants = require('../../../../constants');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether to proceed with a request based on the bucket flags
|
* Checks whether to proceed with a request based on the bucket flags
|
||||||
|
@ -8,9 +9,17 @@ const invisiblyDelete = require('./invisiblyDelete');
|
||||||
* @return {boolean} true if the bucket should be shielded, false otherwise
|
* @return {boolean} true if the bucket should be shielded, false otherwise
|
||||||
*/
|
*/
|
||||||
function bucketShield(bucket, requestType) {
|
function bucketShield(bucket, requestType) {
|
||||||
const invisiblyDeleteRequests = ['bucketGet', 'bucketHead',
|
const invisiblyDeleteRequests = constants.bucketOwnerActions.concat(
|
||||||
'bucketGetACL', 'bucketOwnerAction', 'objectGet', 'objectGetACL',
|
[
|
||||||
'objectHead', 'objectPutACL', 'objectDelete'];
|
'bucketGet',
|
||||||
|
'bucketHead',
|
||||||
|
'bucketGetACL',
|
||||||
|
'objectGet',
|
||||||
|
'objectGetACL',
|
||||||
|
'objectHead',
|
||||||
|
'objectPutACL',
|
||||||
|
'objectDelete',
|
||||||
|
]);
|
||||||
if (invisiblyDeleteRequests.indexOf(requestType) > -1 &&
|
if (invisiblyDeleteRequests.indexOf(requestType) > -1 &&
|
||||||
bucket.hasDeletedFlag()) {
|
bucket.hasDeletedFlag()) {
|
||||||
invisiblyDelete(bucket.getName(), bucket.getOwner());
|
invisiblyDelete(bucket.getName(), bucket.getOwner());
|
||||||
|
|
|
@ -16,7 +16,7 @@ function abortMultipartUpload(authInfo, bucketName, objectKey, uploadId, log,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
uploadId,
|
uploadId,
|
||||||
requestType: 'deleteMPU',
|
preciseRequestType: 'multipartDelete',
|
||||||
};
|
};
|
||||||
// For validating the request at the destinationBucket level
|
// For validating the request at the destinationBucket level
|
||||||
// params are the same as validating at the MPU level
|
// params are the same as validating at the MPU level
|
||||||
|
|
|
@ -2,11 +2,12 @@ const { errors } = require('arsenal');
|
||||||
|
|
||||||
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
||||||
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
|
|
||||||
const requestType = 'bucketOwnerAction';
|
const requestType = 'bucketDeleteCors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bucket Delete CORS - Delete bucket cors configuration
|
* Bucket Delete CORS - Delete bucket cors configuration
|
||||||
|
@ -19,6 +20,7 @@ const requestType = 'bucketOwnerAction';
|
||||||
function bucketDeleteCors(authInfo, request, log, callback) {
|
function bucketDeleteCors(authInfo, request, log, callback) {
|
||||||
const bucketName = request.bucketName;
|
const bucketName = request.bucketName;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
|
|
||||||
return metadata.getBucket(bucketName, log, (err, bucket) => {
|
return metadata.getBucket(bucketName, log, (err, bucket) => {
|
||||||
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
||||||
|
@ -32,7 +34,8 @@ function bucketDeleteCors(authInfo, request, log, callback) {
|
||||||
}
|
}
|
||||||
log.trace('found bucket in metadata');
|
log.trace('found bucket in metadata');
|
||||||
|
|
||||||
if (!isBucketAuthorized(bucket, requestType, canonicalID)) {
|
if (!isBucketAuthorized(bucket, requestType, canonicalID, requestArn,
|
||||||
|
log)) {
|
||||||
log.debug('access denied for user on bucket', {
|
log.debug('access denied for user on bucket', {
|
||||||
requestType,
|
requestType,
|
||||||
method: 'bucketDeleteCors',
|
method: 'bucketDeleteCors',
|
||||||
|
|
|
@ -17,7 +17,7 @@ function bucketDeleteLifecycle(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketDeleteLifecycle',
|
||||||
};
|
};
|
||||||
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
||||||
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
||||||
|
|
|
@ -16,7 +16,7 @@ function bucketDeletePolicy(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketDeletePolicy',
|
||||||
};
|
};
|
||||||
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
||||||
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
||||||
|
|
|
@ -17,7 +17,7 @@ function bucketDeleteReplication(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketDeleteReplication',
|
||||||
};
|
};
|
||||||
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
||||||
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
||||||
|
|
|
@ -2,15 +2,17 @@ const { errors } = require('arsenal');
|
||||||
|
|
||||||
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
||||||
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
|
|
||||||
const requestType = 'bucketOwnerAction';
|
const requestType = 'bucketDeleteWebsite';
|
||||||
|
|
||||||
function bucketDeleteWebsite(authInfo, request, log, callback) {
|
function bucketDeleteWebsite(authInfo, request, log, callback) {
|
||||||
const bucketName = request.bucketName;
|
const bucketName = request.bucketName;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
|
|
||||||
return metadata.getBucket(bucketName, log, (err, bucket) => {
|
return metadata.getBucket(bucketName, log, (err, bucket) => {
|
||||||
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
||||||
|
@ -24,7 +26,8 @@ function bucketDeleteWebsite(authInfo, request, log, callback) {
|
||||||
}
|
}
|
||||||
log.trace('found bucket in metadata');
|
log.trace('found bucket in metadata');
|
||||||
|
|
||||||
if (!isBucketAuthorized(bucket, requestType, canonicalID)) {
|
if (!isBucketAuthorized(bucket, requestType, canonicalID, requestArn,
|
||||||
|
log)) {
|
||||||
log.debug('access denied for user on bucket', {
|
log.debug('access denied for user on bucket', {
|
||||||
requestType,
|
requestType,
|
||||||
method: 'bucketDeleteWebsite',
|
method: 'bucketDeleteWebsite',
|
||||||
|
|
|
@ -3,11 +3,12 @@ const { errors } = require('arsenal');
|
||||||
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
||||||
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const { convertToXml } = require('./apiUtils/bucket/bucketCors');
|
const { convertToXml } = require('./apiUtils/bucket/bucketCors');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
|
|
||||||
const requestType = 'bucketOwnerAction';
|
const requestType = 'bucketGetCors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bucket Get CORS - Get bucket cors configuration
|
* Bucket Get CORS - Get bucket cors configuration
|
||||||
|
@ -20,6 +21,7 @@ const requestType = 'bucketOwnerAction';
|
||||||
function bucketGetCors(authInfo, request, log, callback) {
|
function bucketGetCors(authInfo, request, log, callback) {
|
||||||
const bucketName = request.bucketName;
|
const bucketName = request.bucketName;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
|
|
||||||
metadata.getBucket(bucketName, log, (err, bucket) => {
|
metadata.getBucket(bucketName, log, (err, bucket) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -33,7 +35,8 @@ function bucketGetCors(authInfo, request, log, callback) {
|
||||||
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
||||||
request.method, bucket);
|
request.method, bucket);
|
||||||
|
|
||||||
if (!isBucketAuthorized(bucket, requestType, canonicalID)) {
|
if (!isBucketAuthorized(bucket, requestType, canonicalID, requestArn,
|
||||||
|
log)) {
|
||||||
log.debug('access denied for user on bucket', {
|
log.debug('access denied for user on bucket', {
|
||||||
requestType,
|
requestType,
|
||||||
method: 'bucketGetCors',
|
method: 'bucketGetCors',
|
||||||
|
|
|
@ -20,7 +20,7 @@ function bucketGetLifecycle(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketGetLifecycle',
|
||||||
};
|
};
|
||||||
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
||||||
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
const { errors, s3middleware } = require('arsenal');
|
const { errors, s3middleware } = require('arsenal');
|
||||||
|
|
||||||
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
const escapeForXml = s3middleware.escapeForXml;
|
const escapeForXml = s3middleware.escapeForXml;
|
||||||
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
|
|
||||||
const requestType = 'bucketOwnerAction';
|
const requestType = 'bucketGetLocation';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bucket Get Location - Get bucket locationConstraint configuration
|
* Bucket Get Location - Get bucket locationConstraint configuration
|
||||||
|
@ -21,6 +22,7 @@ const requestType = 'bucketOwnerAction';
|
||||||
function bucketGetLocation(authInfo, request, log, callback) {
|
function bucketGetLocation(authInfo, request, log, callback) {
|
||||||
const bucketName = request.bucketName;
|
const bucketName = request.bucketName;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
|
|
||||||
return metadata.getBucket(bucketName, log, (err, bucket) => {
|
return metadata.getBucket(bucketName, log, (err, bucket) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -35,7 +37,8 @@ function bucketGetLocation(authInfo, request, log, callback) {
|
||||||
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
||||||
request.method, bucket);
|
request.method, bucket);
|
||||||
|
|
||||||
if (!isBucketAuthorized(bucket, requestType, canonicalID)) {
|
if (!isBucketAuthorized(bucket, requestType, canonicalID, requestArn,
|
||||||
|
log)) {
|
||||||
log.debug('access denied for account on bucket', {
|
log.debug('access denied for account on bucket', {
|
||||||
requestType,
|
requestType,
|
||||||
method: 'bucketGetLocation',
|
method: 'bucketGetLocation',
|
||||||
|
|
|
@ -17,11 +17,8 @@ function bucketGetPolicy(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketGetPolicy',
|
||||||
};
|
};
|
||||||
if (!process.env.BUCKET_POLICY) {
|
|
||||||
return callback(errors.NotImplemented);
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
||||||
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
||||||
|
@ -42,7 +39,9 @@ function bucketGetPolicy(authInfo, request, log, callback) {
|
||||||
corsHeaders);
|
corsHeaders);
|
||||||
}
|
}
|
||||||
// TODO: implement Utapi metric support
|
// TODO: implement Utapi metric support
|
||||||
return callback(null, bucketPolicy, corsHeaders);
|
// bucketPolicy needs to be JSON stringified on return for proper
|
||||||
|
// parsing on return to caller function
|
||||||
|
return callback(null, JSON.stringify(bucketPolicy), corsHeaders);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ function bucketGetReplication(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketGetReplication',
|
||||||
};
|
};
|
||||||
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
return metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
||||||
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
const corsHeaders = collectCorsHeaders(headers.origin, method, bucket);
|
||||||
|
|
|
@ -53,7 +53,7 @@ function bucketGetVersioning(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketGetVersioning',
|
||||||
};
|
};
|
||||||
|
|
||||||
metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
metadataValidateBucket(metadataValParams, log, (err, bucket) => {
|
||||||
|
|
|
@ -3,11 +3,12 @@ const { errors } = require('arsenal');
|
||||||
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
||||||
const { convertToXml } = require('./apiUtils/bucket/bucketWebsite');
|
const { convertToXml } = require('./apiUtils/bucket/bucketWebsite');
|
||||||
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
|
|
||||||
const requestType = 'bucketOwnerAction';
|
const requestType = 'bucketGetWebsite';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bucket Get Website - Get bucket website configuration
|
* Bucket Get Website - Get bucket website configuration
|
||||||
|
@ -20,6 +21,7 @@ const requestType = 'bucketOwnerAction';
|
||||||
function bucketGetWebsite(authInfo, request, log, callback) {
|
function bucketGetWebsite(authInfo, request, log, callback) {
|
||||||
const bucketName = request.bucketName;
|
const bucketName = request.bucketName;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
|
|
||||||
metadata.getBucket(bucketName, log, (err, bucket) => {
|
metadata.getBucket(bucketName, log, (err, bucket) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -33,7 +35,8 @@ function bucketGetWebsite(authInfo, request, log, callback) {
|
||||||
|
|
||||||
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
const corsHeaders = collectCorsHeaders(request.headers.origin,
|
||||||
request.method, bucket);
|
request.method, bucket);
|
||||||
if (!isBucketAuthorized(bucket, requestType, canonicalID)) {
|
if (!isBucketAuthorized(bucket, requestType, canonicalID, requestArn,
|
||||||
|
log)) {
|
||||||
log.debug('access denied for user on bucket', {
|
log.debug('access denied for user on bucket', {
|
||||||
requestType,
|
requestType,
|
||||||
method: 'bucketGetWebsite',
|
method: 'bucketGetWebsite',
|
||||||
|
|
|
@ -4,12 +4,13 @@ const { errors } = require('arsenal');
|
||||||
|
|
||||||
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
||||||
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { parseCorsXml } = require('./apiUtils/bucket/bucketCors');
|
const { parseCorsXml } = require('./apiUtils/bucket/bucketCors');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
|
|
||||||
const requestType = 'bucketOwnerAction';
|
const requestType = 'bucketPutCors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bucket Put Cors - Adds cors rules to bucket
|
* Bucket Put Cors - Adds cors rules to bucket
|
||||||
|
@ -23,6 +24,7 @@ function bucketPutCors(authInfo, request, log, callback) {
|
||||||
log.debug('processing request', { method: 'bucketPutCors' });
|
log.debug('processing request', { method: 'bucketPutCors' });
|
||||||
const bucketName = request.bucketName;
|
const bucketName = request.bucketName;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
|
|
||||||
if (!request.post) {
|
if (!request.post) {
|
||||||
log.debug('CORS xml body is missing',
|
log.debug('CORS xml body is missing',
|
||||||
|
@ -65,7 +67,8 @@ function bucketPutCors(authInfo, request, log, callback) {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function validateBucketAuthorization(bucket, rules, corsHeaders, next) {
|
function validateBucketAuthorization(bucket, rules, corsHeaders, next) {
|
||||||
if (!isBucketAuthorized(bucket, requestType, canonicalID)) {
|
if (!isBucketAuthorized(bucket, requestType, canonicalID,
|
||||||
|
requestArn, log)) {
|
||||||
log.debug('access denied for account on bucket', {
|
log.debug('access denied for account on bucket', {
|
||||||
requestType,
|
requestType,
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,7 +24,7 @@ function bucketPutLifecycle(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketPutLifecycle',
|
||||||
};
|
};
|
||||||
return waterfall([
|
return waterfall([
|
||||||
next => parseXML(request.post, log, next),
|
next => parseXML(request.post, log, next),
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const { BucketPolicy } = require('arsenal').models;
|
const { errors, models } = require('arsenal');
|
||||||
|
|
||||||
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { metadataValidateBucket } = require('../metadata/metadataUtils');
|
const { metadataValidateBucket } = require('../metadata/metadataUtils');
|
||||||
const { errors } = require('arsenal');
|
const { validatePolicyResource } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
|
const { BucketPolicy } = models;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bucketPutPolicy - create or update a bucket policy
|
* bucketPutPolicy - create or update a bucket policy
|
||||||
|
@ -21,27 +23,33 @@ function bucketPutPolicy(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketPutPolicy',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!process.env.BUCKET_POLICY) {
|
|
||||||
return callback(errors.NotImplemented);
|
|
||||||
}
|
|
||||||
|
|
||||||
return async.waterfall([
|
return async.waterfall([
|
||||||
next => {
|
next => {
|
||||||
const policyJSON = JSON.parse(request.post);
|
const bucketPolicy = new BucketPolicy(request.post);
|
||||||
const bucketPolicy = new BucketPolicy(policyJSON);
|
|
||||||
// if there was an error getting bucket policy,
|
// if there was an error getting bucket policy,
|
||||||
// returned policyObj will contain 'error' key
|
// returned policyObj will contain 'error' key
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
const policyObj = bucketPolicy.getBucketPolicy();
|
const policyObj = bucketPolicy.getBucketPolicy();
|
||||||
if (policyObj.error) {
|
if (policyObj.error) {
|
||||||
return next(policyObj.error);
|
const err = errors.MalformedPolicy.customizeDescription(
|
||||||
|
policyObj.error.description);
|
||||||
|
return next(err);
|
||||||
}
|
}
|
||||||
return next(null, policyObj);
|
return next(null, policyObj);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
(bucketPolicy, next) => {
|
||||||
|
process.nextTick(() => {
|
||||||
|
if (!validatePolicyResource(bucketName, bucketPolicy)) {
|
||||||
|
return next(errors.MalformedPolicy.customizeDescription(
|
||||||
|
'Policy has invalid resource'));
|
||||||
|
}
|
||||||
|
return next(null, bucketPolicy);
|
||||||
|
});
|
||||||
|
},
|
||||||
(bucketPolicy, next) => metadataValidateBucket(metadataValParams, log,
|
(bucketPolicy, next) => metadataValidateBucket(metadataValParams, log,
|
||||||
(err, bucket) => {
|
(err, bucket) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
|
@ -27,7 +27,7 @@ function bucketPutReplication(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketPutReplication',
|
||||||
};
|
};
|
||||||
return waterfall([
|
return waterfall([
|
||||||
// Validate the request XML and return the replication configuration.
|
// Validate the request XML and return the replication configuration.
|
||||||
|
|
|
@ -81,7 +81,7 @@ function bucketPutVersioning(authInfo, request, log, callback) {
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'bucketPutVersioning',
|
||||||
};
|
};
|
||||||
|
|
||||||
return waterfall([
|
return waterfall([
|
||||||
|
|
|
@ -3,12 +3,13 @@ const { errors } = require('arsenal');
|
||||||
|
|
||||||
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
||||||
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { parseWebsiteConfigXml } = require('./apiUtils/bucket/bucketWebsite');
|
const { parseWebsiteConfigXml } = require('./apiUtils/bucket/bucketWebsite');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
|
|
||||||
const requestType = 'bucketOwnerAction';
|
const requestType = 'bucketPutWebsite';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bucket Put Website - Create bucket website configuration
|
* Bucket Put Website - Create bucket website configuration
|
||||||
|
@ -22,6 +23,7 @@ function bucketPutWebsite(authInfo, request, log, callback) {
|
||||||
log.debug('processing request', { method: 'bucketPutWebsite' });
|
log.debug('processing request', { method: 'bucketPutWebsite' });
|
||||||
const bucketName = request.bucketName;
|
const bucketName = request.bucketName;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
|
|
||||||
if (!request.post) {
|
if (!request.post) {
|
||||||
return callback(errors.MissingRequestBodyError);
|
return callback(errors.MissingRequestBodyError);
|
||||||
|
@ -45,7 +47,8 @@ function bucketPutWebsite(authInfo, request, log, callback) {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function validateBucketAuthorization(bucket, config, next) {
|
function validateBucketAuthorization(bucket, config, next) {
|
||||||
if (!isBucketAuthorized(bucket, requestType, canonicalID)) {
|
if (!isBucketAuthorized(bucket, requestType, canonicalID,
|
||||||
|
requestArn, log)) {
|
||||||
log.debug('access denied for user on bucket', {
|
log.debug('access denied for user on bucket', {
|
||||||
requestType,
|
requestType,
|
||||||
method: 'bucketPutWebsite',
|
method: 'bucketPutWebsite',
|
||||||
|
|
|
@ -96,6 +96,7 @@ function listMultipartUploads(authInfo, request, log, callback) {
|
||||||
// the authorization to list multipart uploads is the same
|
// the authorization to list multipart uploads is the same
|
||||||
// as listing objects in a bucket.
|
// as listing objects in a bucket.
|
||||||
requestType: 'bucketGet',
|
requestType: 'bucketGet',
|
||||||
|
preciseRequestType: 'listMultipartUploads',
|
||||||
};
|
};
|
||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
|
|
|
@ -95,7 +95,7 @@ function listParts(authInfo, request, log, callback) {
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
uploadId,
|
uploadId,
|
||||||
requestType: 'listParts',
|
preciseRequestType: 'listParts',
|
||||||
};
|
};
|
||||||
// For validating the request at the destinationBucket level
|
// For validating the request at the destinationBucket level
|
||||||
// params are the same as validating at the MPU level
|
// params are the same as validating at the MPU level
|
||||||
|
|
|
@ -11,7 +11,8 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const services = require('../services');
|
const services = require('../services');
|
||||||
const vault = require('../auth/vault');
|
const vault = require('../auth/vault');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const { preprocessingVersioningDelete }
|
const { preprocessingVersioningDelete }
|
||||||
= require('./apiUtils/object/versioning');
|
= require('./apiUtils/object/versioning');
|
||||||
const createAndStoreObject = require('./apiUtils/object/createAndStoreObject');
|
const createAndStoreObject = require('./apiUtils/object/createAndStoreObject');
|
||||||
|
@ -316,6 +317,7 @@ function multiObjectDelete(authInfo, request, log, callback) {
|
||||||
|
|
||||||
const bucketName = request.bucketName;
|
const bucketName = request.bucketName;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
|
|
||||||
return async.waterfall([
|
return async.waterfall([
|
||||||
function parseXML(next) {
|
function parseXML(next) {
|
||||||
|
@ -448,7 +450,7 @@ function multiObjectDelete(authInfo, request, log, callback) {
|
||||||
bucketMD);
|
bucketMD);
|
||||||
}
|
}
|
||||||
if (!isBucketAuthorized(bucketMD, 'objectDelete',
|
if (!isBucketAuthorized(bucketMD, 'objectDelete',
|
||||||
canonicalID)) {
|
canonicalID, requestArn, log)) {
|
||||||
log.trace("access denied due to bucket acl's");
|
log.trace("access denied due to bucket acl's");
|
||||||
// if access denied at the bucket level, no access for
|
// if access denied at the bucket level, no access for
|
||||||
// any of the objects so all results will be error results
|
// any of the objects so all results will be error results
|
||||||
|
|
|
@ -41,7 +41,7 @@ function objectDeleteTagging(authInfo, request, log, callback) {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'objectDeleteTagging',
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ function objectGetTagging(authInfo, request, log, callback) {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'objectGetTagging',
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ const { BackendInfo } = require('./apiUtils/object/BackendInfo');
|
||||||
const constants = require('../../constants');
|
const constants = require('../../constants');
|
||||||
const data = require('../data/wrapper');
|
const data = require('../data/wrapper');
|
||||||
const { dataStore } = require('./apiUtils/object/storeObject');
|
const { dataStore } = require('./apiUtils/object/storeObject');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const kms = require('../kms/wrapper');
|
const kms = require('../kms/wrapper');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
|
@ -81,6 +82,7 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
|
||||||
log.trace('owner canonicalid to send to data', {
|
log.trace('owner canonicalid to send to data', {
|
||||||
canonicalID: authInfo.getCanonicalID,
|
canonicalID: authInfo.getCanonicalID,
|
||||||
});
|
});
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
// Note that keys in the query object retain their case, so
|
// Note that keys in the query object retain their case, so
|
||||||
// `request.query.uploadId` must be called with that exact capitalization.
|
// `request.query.uploadId` must be called with that exact capitalization.
|
||||||
const uploadId = request.query.uploadId;
|
const uploadId = request.query.uploadId;
|
||||||
|
@ -109,7 +111,7 @@ function objectPutPart(authInfo, request, streamingV4Params, log,
|
||||||
// `requestType` is the general 'objectPut'.
|
// `requestType` is the general 'objectPut'.
|
||||||
const requestType = 'objectPut';
|
const requestType = 'objectPut';
|
||||||
if (!isBucketAuthorized(destinationBucket, requestType,
|
if (!isBucketAuthorized(destinationBucket, requestType,
|
||||||
canonicalID)) {
|
canonicalID, requestArn, log)) {
|
||||||
log.debug('access denied for user on bucket', { requestType });
|
log.debug('access denied for user on bucket', { requestType });
|
||||||
return next(errors.AccessDenied, destinationBucket);
|
return next(errors.AccessDenied, destinationBucket);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ function objectPutTagging(authInfo, request, log, callback) {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'bucketOwnerAction',
|
requestType: 'objectPutTagging',
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ const metadata = require('../metadata/wrapper');
|
||||||
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
||||||
const { findRoutingRule, extractRedirectInfo } =
|
const { findRoutingRule, extractRedirectInfo } =
|
||||||
require('./apiUtils/object/websiteServing');
|
require('./apiUtils/object/websiteServing');
|
||||||
const { isObjAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isObjAuthorized, isBucketAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
const collectResponseHeaders = require('../utilities/collectResponseHeaders');
|
const collectResponseHeaders = require('../utilities/collectResponseHeaders');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* _errorActions - take a number of actions once have error getting obj
|
* _errorActions - take a number of actions once have error getting obj
|
||||||
|
@ -47,7 +47,7 @@ function _errorActions(err, errorDocument, routingRules,
|
||||||
// return the default error message if the object is private
|
// return the default error message if the object is private
|
||||||
// rather than sending a stored error file
|
// rather than sending a stored error file
|
||||||
if (!isObjAuthorized(bucket, errObjMD, 'objectGet',
|
if (!isObjAuthorized(bucket, errObjMD, 'objectGet',
|
||||||
constants.publicId)) {
|
constants.publicId, null, log)) {
|
||||||
log.trace('errorObj not authorized', { error: err });
|
log.trace('errorObj not authorized', { error: err });
|
||||||
return callback(err, true, null, corsHeaders);
|
return callback(err, true, null, corsHeaders);
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ function websiteGet(request, log, callback) {
|
||||||
{ error: err });
|
{ error: err });
|
||||||
let returnErr = err;
|
let returnErr = err;
|
||||||
const bucketAuthorized = isBucketAuthorized(bucket,
|
const bucketAuthorized = isBucketAuthorized(bucket,
|
||||||
'bucketGet', constants.publicId);
|
'bucketGet', constants.publicId, null, log);
|
||||||
// if index object does not exist and bucket is private AWS
|
// if index object does not exist and bucket is private AWS
|
||||||
// returns 403 - AccessDenied error.
|
// returns 403 - AccessDenied error.
|
||||||
if (err === errors.NoSuchKey && !bucketAuthorized) {
|
if (err === errors.NoSuchKey && !bucketAuthorized) {
|
||||||
|
@ -156,7 +156,7 @@ function websiteGet(request, log, callback) {
|
||||||
callback);
|
callback);
|
||||||
}
|
}
|
||||||
if (!isObjAuthorized(bucket, objMD, 'objectGet',
|
if (!isObjAuthorized(bucket, objMD, 'objectGet',
|
||||||
constants.publicId)) {
|
constants.publicId, null, log)) {
|
||||||
const err = errors.AccessDenied;
|
const err = errors.AccessDenied;
|
||||||
log.trace('request not authorized', { error: err });
|
log.trace('request not authorized', { error: err });
|
||||||
return _errorActions(err, websiteConfig.getErrorDocument(),
|
return _errorActions(err, websiteConfig.getErrorDocument(),
|
||||||
|
|
|
@ -7,10 +7,10 @@ const metadata = require('../metadata/wrapper');
|
||||||
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
const bucketShield = require('./apiUtils/bucket/bucketShield');
|
||||||
const { findRoutingRule, extractRedirectInfo } =
|
const { findRoutingRule, extractRedirectInfo } =
|
||||||
require('./apiUtils/object/websiteServing');
|
require('./apiUtils/object/websiteServing');
|
||||||
const { isObjAuthorized } = require('./apiUtils/authorization/aclChecks');
|
|
||||||
const collectResponseHeaders = require('../utilities/collectResponseHeaders');
|
const collectResponseHeaders = require('../utilities/collectResponseHeaders');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
const { isBucketAuthorized } = require('./apiUtils/authorization/aclChecks');
|
const { isBucketAuthorized, isObjAuthorized } =
|
||||||
|
require('./apiUtils/authorization/permissionChecks');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,7 +104,7 @@ function websiteHead(request, log, callback) {
|
||||||
{ error: err });
|
{ error: err });
|
||||||
let returnErr = err;
|
let returnErr = err;
|
||||||
const bucketAuthorized = isBucketAuthorized(bucket,
|
const bucketAuthorized = isBucketAuthorized(bucket,
|
||||||
'bucketGet', constants.publicId);
|
'bucketGet', constants.publicId, null, log);
|
||||||
// if index object does not exist and bucket is private AWS
|
// if index object does not exist and bucket is private AWS
|
||||||
// returns 403 - AccessDenied error.
|
// returns 403 - AccessDenied error.
|
||||||
if (err === errors.NoSuchKey && !bucketAuthorized) {
|
if (err === errors.NoSuchKey && !bucketAuthorized) {
|
||||||
|
@ -114,7 +114,7 @@ function websiteHead(request, log, callback) {
|
||||||
reqObjectKey, corsHeaders, log, callback);
|
reqObjectKey, corsHeaders, log, callback);
|
||||||
}
|
}
|
||||||
if (!isObjAuthorized(bucket, objMD, 'objectGet',
|
if (!isObjAuthorized(bucket, objMD, 'objectGet',
|
||||||
constants.publicId)) {
|
constants.publicId, null, log)) {
|
||||||
const err = errors.AccessDenied;
|
const err = errors.AccessDenied;
|
||||||
log.trace('request not authorized', { error: err });
|
log.trace('request not authorized', { error: err });
|
||||||
return _errorActions(err, routingRules, reqObjectKey,
|
return _errorActions(err, routingRules, reqObjectKey,
|
||||||
|
|
|
@ -4,7 +4,7 @@ const { errors } = require('arsenal');
|
||||||
const metadata = require('./wrapper');
|
const metadata = require('./wrapper');
|
||||||
const BucketInfo = require('arsenal').models.BucketInfo;
|
const BucketInfo = require('arsenal').models.BucketInfo;
|
||||||
const { isBucketAuthorized, isObjAuthorized } =
|
const { isBucketAuthorized, isObjAuthorized } =
|
||||||
require('../api/apiUtils/authorization/aclChecks');
|
require('../api/apiUtils/authorization/permissionChecks');
|
||||||
const bucketShield = require('../api/apiUtils/bucket/bucketShield');
|
const bucketShield = require('../api/apiUtils/bucket/bucketShield');
|
||||||
|
|
||||||
/** _parseListEntries - parse the values returned in a listing by metadata
|
/** _parseListEntries - parse the values returned in a listing by metadata
|
||||||
|
@ -174,15 +174,23 @@ function metadataGetObject(bucketName, objectKey, versionId, log, cb) {
|
||||||
* @return {undefined} - and call callback with params err, bucket md
|
* @return {undefined} - and call callback with params err, bucket md
|
||||||
*/
|
*/
|
||||||
function metadataValidateBucketAndObj(params, log, callback) {
|
function metadataValidateBucketAndObj(params, log, callback) {
|
||||||
const { authInfo, bucketName, objectKey, versionId, requestType } = params;
|
const { authInfo, bucketName, objectKey, versionId, requestType, preciseRequestType } = params;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
function getBucketAndObjectMD(next) {
|
function getBucketAndObjectMD(next) {
|
||||||
return metadataGetBucketAndObject(requestType, bucketName,
|
return metadataGetBucketAndObject(requestType, bucketName,
|
||||||
objectKey, versionId, log, next);
|
objectKey, versionId, log, next);
|
||||||
},
|
},
|
||||||
function checkBucketAuth(bucket, objMD, next) {
|
function checkBucketAuth(bucket, objMD, next) {
|
||||||
if (!isBucketAuthorized(bucket, requestType, canonicalID)) {
|
// if requester is not bucket owner, bucket policy actions should be denied with
|
||||||
|
// MethodNotAllowed error
|
||||||
|
const onlyOwnerAllowed = ['bucketDeletePolicy', 'bucketGetPolicy', 'bucketPutPolicy'];
|
||||||
|
if (bucket.getOwner() !== canonicalID && onlyOwnerAllowed.includes(requestType)) {
|
||||||
|
return next(errors.MethodNotAllowed, bucket);
|
||||||
|
}
|
||||||
|
if (!isBucketAuthorized(bucket, (preciseRequestType || requestType), canonicalID,
|
||||||
|
requestArn, log)) {
|
||||||
log.debug('access denied for user on bucket', { requestType });
|
log.debug('access denied for user on bucket', { requestType });
|
||||||
return next(errors.AccessDenied, bucket);
|
return next(errors.AccessDenied, bucket);
|
||||||
}
|
}
|
||||||
|
@ -199,7 +207,8 @@ function metadataValidateBucketAndObj(params, log, callback) {
|
||||||
if (!objMD) {
|
if (!objMD) {
|
||||||
return next(null, bucket);
|
return next(null, bucket);
|
||||||
}
|
}
|
||||||
if (!isObjAuthorized(bucket, objMD, requestType, canonicalID)) {
|
if (!isObjAuthorized(bucket, objMD, requestType, canonicalID,
|
||||||
|
requestArn, log)) {
|
||||||
log.debug('access denied for user on object', { requestType });
|
log.debug('access denied for user on object', { requestType });
|
||||||
return next(errors.AccessDenied, bucket);
|
return next(errors.AccessDenied, bucket);
|
||||||
}
|
}
|
||||||
|
@ -251,14 +260,21 @@ function metadataGetBucket(requestType, bucketName, log, cb) {
|
||||||
* @return {undefined} - and call callback with params err, bucket md
|
* @return {undefined} - and call callback with params err, bucket md
|
||||||
*/
|
*/
|
||||||
function metadataValidateBucket(params, log, callback) {
|
function metadataValidateBucket(params, log, callback) {
|
||||||
const { authInfo, bucketName, requestType } = params;
|
const { authInfo, bucketName, requestType, preciseRequestType } = params;
|
||||||
const canonicalID = authInfo.getCanonicalID();
|
const canonicalID = authInfo.getCanonicalID();
|
||||||
|
const requestArn = authInfo.getArn();
|
||||||
return metadataGetBucket(requestType, bucketName, log, (err, bucket) => {
|
return metadataGetBucket(requestType, bucketName, log, (err, bucket) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
// if requester is not bucket owner, bucket policy actions should be denied with
|
||||||
|
// MethodNotAllowed error
|
||||||
|
const onlyOwnerAllowed = ['bucketDeletePolicy', 'bucketGetPolicy', 'bucketPutPolicy'];
|
||||||
|
if (bucket.getOwner() !== canonicalID && onlyOwnerAllowed.includes(requestType)) {
|
||||||
|
return callback(errors.MethodNotAllowed, bucket);
|
||||||
|
}
|
||||||
// still return bucket for cors headers
|
// still return bucket for cors headers
|
||||||
if (!isBucketAuthorized(bucket, requestType, canonicalID)) {
|
if (!isBucketAuthorized(bucket, (preciseRequestType || requestType), canonicalID, requestArn, log)) {
|
||||||
log.debug('access denied for user on bucket', { requestType });
|
log.debug('access denied for user on bucket', { requestType });
|
||||||
return callback(errors.AccessDenied, bucket);
|
return callback(errors.AccessDenied, bucket);
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,7 +19,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/scality/S3#readme",
|
"homepage": "https://github.com/scality/S3#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"arsenal": "github:scality/Arsenal#98737a69",
|
"arsenal": "github:scality/Arsenal#61d7790",
|
||||||
"async": "~2.5.0",
|
"async": "~2.5.0",
|
||||||
"aws-sdk": "2.28.0",
|
"aws-sdk": "2.28.0",
|
||||||
"azure-storage": "^2.1.0",
|
"azure-storage": "^2.1.0",
|
||||||
|
|
|
@ -9,11 +9,11 @@ const bucket = 'deletebucketpolicy-test-bucket';
|
||||||
const bucketPolicy = {
|
const bucketPolicy = {
|
||||||
Version: '2012-10-17',
|
Version: '2012-10-17',
|
||||||
Statement: [{
|
Statement: [{
|
||||||
Sid: 'test-id',
|
Sid: 'testid',
|
||||||
Effect: 'Allow',
|
Effect: 'Allow',
|
||||||
Principle: '*',
|
Principal: '*',
|
||||||
Action: 's3:putBucketPolicy',
|
Action: 's3:putBucketPolicy',
|
||||||
Resource: `arn:aws:s3::${bucket}`,
|
Resource: `arn:aws:s3:::${bucket}`,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,10 +31,7 @@ function assertError(err, expectedErr, cb) {
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
const describeSkipUntilImpl =
|
describe('aws-sdk test delete bucket policy', () => {
|
||||||
process.env.BUCKET_POLICY ? describe : describe.skip;
|
|
||||||
|
|
||||||
describeSkipUntilImpl('aws-sdk test delete bucket policy', () => {
|
|
||||||
let s3;
|
let s3;
|
||||||
let otherAccountS3;
|
let otherAccountS3;
|
||||||
|
|
||||||
|
@ -55,9 +52,9 @@ describeSkipUntilImpl('aws-sdk test delete bucket policy', () => {
|
||||||
|
|
||||||
afterEach(done => s3.deleteBucket({ Bucket: bucket }, done));
|
afterEach(done => s3.deleteBucket({ Bucket: bucket }, done));
|
||||||
|
|
||||||
it('should return AccessDenied if user is not bucket owner', done => {
|
it('should return MethodNotAllowed if user is not bucket owner', done => {
|
||||||
otherAccountS3.deleteBucketPolicy({ Bucket: bucket },
|
otherAccountS3.deleteBucketPolicy({ Bucket: bucket },
|
||||||
err => assertError(err, 'AccessDenied', done));
|
err => assertError(err, 'MethodNotAllowed', done));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return no error if no policy on bucket', done => {
|
it('should return no error if no policy on bucket', done => {
|
||||||
|
@ -66,7 +63,7 @@ describeSkipUntilImpl('aws-sdk test delete bucket policy', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete policy from bucket', done => {
|
it('should delete policy from bucket', done => {
|
||||||
const params = { Bucket: bucket, Policy: bucketPolicy };
|
const params = { Bucket: bucket, Policy: JSON.stringify(bucketPolicy) };
|
||||||
s3.putBucketPolicy(params, err => {
|
s3.putBucketPolicy(params, err => {
|
||||||
assert.equal(err, null);
|
assert.equal(err, null);
|
||||||
s3.deleteBucketPolicy({ Bucket: bucket }, err => {
|
s3.deleteBucketPolicy({ Bucket: bucket }, err => {
|
||||||
|
|
|
@ -9,19 +9,19 @@ const bucket = 'getbucketpolicy-testbucket';
|
||||||
const bucketPolicy = {
|
const bucketPolicy = {
|
||||||
Version: '2012-10-17',
|
Version: '2012-10-17',
|
||||||
Statement: [{
|
Statement: [{
|
||||||
Sid: 'test-id',
|
Sid: 'testid',
|
||||||
Effect: 'Allow',
|
Effect: 'Allow',
|
||||||
Principle: '*',
|
Principal: '*',
|
||||||
Action: 's3:putBucketPolicy',
|
Action: 's3:putBucketPolicy',
|
||||||
Resource: 'arn:aws:s3::getbucketpolicy-testbucket',
|
Resource: `arn:aws:s3:::${bucket}`,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
const expectedPolicy = {
|
const expectedPolicy = {
|
||||||
Sid: 'test-id',
|
Sid: 'testid',
|
||||||
Effect: 'Allow',
|
Effect: 'Allow',
|
||||||
Principle: '*',
|
Principal: '*',
|
||||||
Action: 's3:putBucketPolicy',
|
Action: 's3:putBucketPolicy',
|
||||||
Resource: 'arn:aws:s3::getbucketpolicy-testbucket',
|
Resource: `arn:aws:s3:::${bucket}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for the expected error response code and status code.
|
// Check for the expected error response code and status code.
|
||||||
|
@ -38,10 +38,7 @@ function assertError(err, expectedErr, cb) {
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
const describeSkipUntilImpl =
|
describe('aws-sdk test get bucket policy', () => {
|
||||||
process.env.BUCKET_POLICY ? describe : describe.skip;
|
|
||||||
|
|
||||||
describeSkipUntilImpl('aws-sdk test get bucket policy', () => {
|
|
||||||
const config = getConfig('default', { signatureVersion: 'v4' });
|
const config = getConfig('default', { signatureVersion: 'v4' });
|
||||||
const s3 = new S3(config);
|
const s3 = new S3(config);
|
||||||
const otherAccountS3 = new BucketUtility('lisa', {}).s3;
|
const otherAccountS3 = new BucketUtility('lisa', {}).s3;
|
||||||
|
@ -56,9 +53,9 @@ describeSkipUntilImpl('aws-sdk test get bucket policy', () => {
|
||||||
|
|
||||||
afterEach(done => s3.deleteBucket({ Bucket: bucket }, done));
|
afterEach(done => s3.deleteBucket({ Bucket: bucket }, done));
|
||||||
|
|
||||||
it('should return AccessDenied if user is not bucket owner', done => {
|
it('should return MethodNotAllowed if user is not bucket owner', done => {
|
||||||
otherAccountS3.getBucketPolicy({ Bucket: bucket },
|
otherAccountS3.getBucketPolicy({ Bucket: bucket },
|
||||||
err => assertError(err, 'AccessDenied', done));
|
err => assertError(err, 'MethodNotAllowed', done));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return NoSuchBucketPolicy error if no policy put to bucket',
|
it('should return NoSuchBucketPolicy error if no policy put to bucket',
|
||||||
|
@ -71,14 +68,15 @@ describeSkipUntilImpl('aws-sdk test get bucket policy', () => {
|
||||||
it('should get bucket policy', done => {
|
it('should get bucket policy', done => {
|
||||||
s3.putBucketPolicy({
|
s3.putBucketPolicy({
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Policy: bucketPolicy,
|
Policy: JSON.stringify(bucketPolicy),
|
||||||
}, err => {
|
}, err => {
|
||||||
assert.equal(err, null, `Err putting bucket policy: ${err}`);
|
assert.equal(err, null, `Err putting bucket policy: ${err}`);
|
||||||
s3.getBucketPolicy({ Bucket: bucket },
|
s3.getBucketPolicy({ Bucket: bucket },
|
||||||
(err, res) => {
|
(err, res) => {
|
||||||
|
const parsedRes = JSON.parse(res.Policy);
|
||||||
assert.equal(err, null, 'Error getting bucket policy: ' +
|
assert.equal(err, null, 'Error getting bucket policy: ' +
|
||||||
`${err}`);
|
`${err}`);
|
||||||
assert.deepStrictEqual(res.Statement[0], expectedPolicy);
|
assert.deepStrictEqual(parsedRes.Statement[0], expectedPolicy);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,11 +7,11 @@ const BucketUtility = require('../../lib/utility/bucket-util');
|
||||||
|
|
||||||
const bucket = 'policyputtestbucket';
|
const bucket = 'policyputtestbucket';
|
||||||
const basicStatement = {
|
const basicStatement = {
|
||||||
Sid: 'statement-id',
|
Sid: 'statementid',
|
||||||
Effect: 'Allow',
|
Effect: 'Allow',
|
||||||
Principal: '*',
|
Principal: '*',
|
||||||
Action: ['s3:putBucketPolicy'],
|
Action: ['s3:putBucketPolicy'],
|
||||||
Resource: 'aws:arn:s3::example-bucket',
|
Resource: `arn:aws:s3:::${bucket}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getPolicyParams(paramToChange) {
|
function getPolicyParams(paramToChange) {
|
||||||
|
@ -26,7 +26,7 @@ function getPolicyParams(paramToChange) {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Policy: bucketPolicy,
|
Policy: JSON.stringify(bucketPolicy),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,10 +44,7 @@ function assertError(err, expectedErr, cb) {
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
const describeSkipUntilImpl =
|
describe('aws-sdk test put bucket policy', () => {
|
||||||
process.env.BUCKET_POLICY ? describe : describe.skip;
|
|
||||||
|
|
||||||
describeSkipUntilImpl('aws-sdk test put bucket policy', () => {
|
|
||||||
let s3;
|
let s3;
|
||||||
let otherAccountS3;
|
let otherAccountS3;
|
||||||
|
|
||||||
|
@ -69,10 +66,10 @@ describeSkipUntilImpl('aws-sdk test put bucket policy', () => {
|
||||||
|
|
||||||
afterEach(done => s3.deleteBucket({ Bucket: bucket }, done));
|
afterEach(done => s3.deleteBucket({ Bucket: bucket }, done));
|
||||||
|
|
||||||
it('should return AccessDenied if user is not bucket owner', done => {
|
it('should return MethodNotAllowed if user is not bucket owner', done => {
|
||||||
const params = getPolicyParams();
|
const params = getPolicyParams();
|
||||||
otherAccountS3.putBucketPolicy(params,
|
otherAccountS3.putBucketPolicy(params,
|
||||||
err => assertError(err, 'AccessDenied', done));
|
err => assertError(err, 'MethodNotAllowed', done));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should put a bucket policy on bucket', done => {
|
it('should put a bucket policy on bucket', done => {
|
||||||
|
|
|
@ -2,13 +2,15 @@ const assert = require('assert');
|
||||||
const BucketInfo = require('arsenal').models.BucketInfo;
|
const BucketInfo = require('arsenal').models.BucketInfo;
|
||||||
const constants = require('../../../constants');
|
const constants = require('../../../constants');
|
||||||
const { isBucketAuthorized }
|
const { isBucketAuthorized }
|
||||||
= require('../../../lib/api/apiUtils/authorization/aclChecks');
|
= require('../../../lib/api/apiUtils/authorization/permissionChecks');
|
||||||
|
const { DummyRequestLogger } = require('../helpers');
|
||||||
|
|
||||||
const ownerCanonicalId = 'ownerCanonicalId';
|
const ownerCanonicalId = 'ownerCanonicalId';
|
||||||
const creationDate = new Date().toJSON();
|
const creationDate = new Date().toJSON();
|
||||||
const bucket = new BucketInfo('niftyBucket', ownerCanonicalId,
|
const bucket = new BucketInfo('niftyBucket', ownerCanonicalId,
|
||||||
'iAmTheOwnerDisplayName', creationDate);
|
'iAmTheOwnerDisplayName', creationDate);
|
||||||
const accountToVet = 'accountToVetId';
|
const accountToVet = 'accountToVetId';
|
||||||
|
const log = new DummyRequestLogger();
|
||||||
|
|
||||||
describe('bucket authorization for bucketGet, bucketHead, ' +
|
describe('bucket authorization for bucketGet, bucketHead, ' +
|
||||||
'objectGet, and objectHead', () => {
|
'objectGet, and objectHead', () => {
|
||||||
|
@ -92,7 +94,7 @@ describe('bucket authorization for bucketGet, bucketHead, ' +
|
||||||
}
|
}
|
||||||
bucket.setCannedAcl(value.canned);
|
bucket.setCannedAcl(value.canned);
|
||||||
const results = requestTypes.map(type =>
|
const results = requestTypes.map(type =>
|
||||||
isBucketAuthorized(bucket, type, value.id));
|
isBucketAuthorized(bucket, type, value.id, null, log));
|
||||||
assert.deepStrictEqual(results, value.response);
|
assert.deepStrictEqual(results, value.response);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -199,7 +201,7 @@ describe('bucket authorization for bucketOwnerAction', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access to bucket owner', () => {
|
it('should allow access to bucket owner', () => {
|
||||||
const result = isBucketAuthorized(bucket, 'bucketOwnerAction',
|
const result = isBucketAuthorized(bucket, 'bucketDeleteCors',
|
||||||
ownerCanonicalId);
|
ownerCanonicalId);
|
||||||
assert.strictEqual(result, true);
|
assert.strictEqual(result, true);
|
||||||
});
|
});
|
||||||
|
@ -221,7 +223,7 @@ describe('bucket authorization for bucketOwnerAction', () => {
|
||||||
bucket.setSpecificAcl(value.aclParam[1], value.aclParam[0]);
|
bucket.setSpecificAcl(value.aclParam[1], value.aclParam[0]);
|
||||||
}
|
}
|
||||||
bucket.setCannedAcl(value.canned);
|
bucket.setCannedAcl(value.canned);
|
||||||
const result = isBucketAuthorized(bucket, 'bucketOwnerAction',
|
const result = isBucketAuthorized(bucket, 'bucketDeleteCors',
|
||||||
value.id);
|
value.id);
|
||||||
assert.strictEqual(result, false);
|
assert.strictEqual(result, false);
|
||||||
done();
|
done();
|
||||||
|
|
|
@ -22,14 +22,13 @@ function _makeRequest(includePolicy) {
|
||||||
};
|
};
|
||||||
if (includePolicy) {
|
if (includePolicy) {
|
||||||
const examplePolicy = {
|
const examplePolicy = {
|
||||||
version: '2012-10-17',
|
Version: '2012-10-17',
|
||||||
statements: [
|
Statement: [
|
||||||
{
|
{
|
||||||
sid: '',
|
Effect: 'Allow',
|
||||||
effect: 'Allow',
|
Resource: `arn:aws:s3:::${bucketName}`,
|
||||||
resource: 'arn:aws:s3::bucketname',
|
Principal: '*',
|
||||||
principal: '*',
|
Action: ['s3:GetBucketLocation'],
|
||||||
actions: ['s3:sampleAction'],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -38,10 +37,7 @@ function _makeRequest(includePolicy) {
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
const describeSkipUntilImpl =
|
describe('deleteBucketPolicy API', () => {
|
||||||
process.env.BUCKET_POLICY ? describe : describe.skip;
|
|
||||||
|
|
||||||
describeSkipUntilImpl('deleteBucketPolicy API', () => {
|
|
||||||
before(() => cleanup());
|
before(() => cleanup());
|
||||||
beforeEach(done => bucketPut(authInfo, _makeRequest(), log, done));
|
beforeEach(done => bucketPut(authInfo, _makeRequest(), log, done));
|
||||||
afterEach(() => cleanup());
|
afterEach(() => cleanup());
|
||||||
|
|
|
@ -19,14 +19,13 @@ const testBasicRequest = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedBucketPolicy = {
|
const expectedBucketPolicy = {
|
||||||
version: '2012-10-17',
|
Version: '2012-10-17',
|
||||||
statements: [
|
Statement: [
|
||||||
{
|
{
|
||||||
sid: '',
|
Effect: 'Allow',
|
||||||
effect: '',
|
Resource: `arn:aws:s3:::${bucketName}`,
|
||||||
resource: '',
|
Principal: '*',
|
||||||
principal: '',
|
Action: ['s3:GetBucketLocation'],
|
||||||
actions: '',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -37,10 +36,7 @@ const testPutPolicyRequest = {
|
||||||
post: JSON.stringify(expectedBucketPolicy),
|
post: JSON.stringify(expectedBucketPolicy),
|
||||||
};
|
};
|
||||||
|
|
||||||
const describeSkipUntilImpl =
|
describe('getBucketPolicy API', () => {
|
||||||
process.env.BUCKET_POLICY ? describe : describe.skip;
|
|
||||||
|
|
||||||
describeSkipUntilImpl('getBucketPolicy API', () => {
|
|
||||||
before(() => cleanup());
|
before(() => cleanup());
|
||||||
beforeEach(done => bucketPut(authInfo, testBasicRequest, log, done));
|
beforeEach(done => bucketPut(authInfo, testBasicRequest, log, done));
|
||||||
afterEach(() => cleanup());
|
afterEach(() => cleanup());
|
||||||
|
@ -64,7 +60,7 @@ describeSkipUntilImpl('getBucketPolicy API', () => {
|
||||||
it('should return bucket policy', done => {
|
it('should return bucket policy', done => {
|
||||||
bucketGetPolicy(authInfo, testBasicRequest, log, (err, res) => {
|
bucketGetPolicy(authInfo, testBasicRequest, log, (err, res) => {
|
||||||
assert.equal(err, null);
|
assert.equal(err, null);
|
||||||
assert.deepStrictEqual(expectedBucketPolicy, res);
|
assert.deepStrictEqual(expectedBucketPolicy, JSON.parse(res));
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,358 @@
|
||||||
|
const assert = require('assert');
|
||||||
|
const { BucketInfo, BucketPolicy } = require('arsenal').models;
|
||||||
|
const constants = require('../../../constants');
|
||||||
|
const { isBucketAuthorized, isObjAuthorized, validatePolicyResource }
|
||||||
|
= require('../../../lib/api/apiUtils/authorization/permissionChecks');
|
||||||
|
const { DummyRequestLogger } = require('../helpers');
|
||||||
|
|
||||||
|
const bucketOwnerCanonicalId = 'bucketOwnerCanonicalId';
|
||||||
|
const creationDate = new Date().toJSON();
|
||||||
|
const bucket = new BucketInfo('policyBucketAuthTester', bucketOwnerCanonicalId,
|
||||||
|
'iAmTheOwnerDisplayName', creationDate);
|
||||||
|
const objectOwnerCanonicalId = 'objectOwnerCanonicalId';
|
||||||
|
const object = { 'owner-id': objectOwnerCanonicalId };
|
||||||
|
const canonicalIdToVet = 'canonicalIdToVet';
|
||||||
|
const userArn = 'arn:aws:iam::123456789012:user/user';
|
||||||
|
const otherUserArn = 'arn:aws:iam::123456789012:user/other';
|
||||||
|
const otherAccountUserArn = 'arn:aws:iam:987654321098:user/other';
|
||||||
|
const accountArn = 'arn:aws:iam::123456789012:root';
|
||||||
|
const accountId = '123456789012';
|
||||||
|
const bucAction = 'bucketHead';
|
||||||
|
const objAction = 'objectPut';
|
||||||
|
const basePolicyObj = {
|
||||||
|
Version: '2012-10-17',
|
||||||
|
Statement: {
|
||||||
|
Effect: 'Allow',
|
||||||
|
Principal: '*',
|
||||||
|
Resource: `arn:aws:s3:::${bucket.getName()}`,
|
||||||
|
Action: 's3:*',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const bucketName = 'matchme';
|
||||||
|
const log = new DummyRequestLogger();
|
||||||
|
|
||||||
|
const authTests = [
|
||||||
|
{
|
||||||
|
name: 'should allow access if canonical user principal matches non-',
|
||||||
|
bucketId: canonicalIdToVet,
|
||||||
|
objectId: canonicalIdToVet,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { CanonicalUser: [canonicalIdToVet] },
|
||||||
|
objectValue: { CanonicalUser: [canonicalIdToVet] },
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should allow access if user arn principal matches non-',
|
||||||
|
bucketId: userArn,
|
||||||
|
objectId: userArn,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { AWS: userArn },
|
||||||
|
objectValue: { AWS: userArn },
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should allow access if account arn principal matches non-',
|
||||||
|
bucketId: accountArn,
|
||||||
|
objectId: accountArn,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { AWS: accountArn },
|
||||||
|
objectValue: { AWS: accountArn },
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should allow access if account id principal matches non-',
|
||||||
|
bucketId: accountId,
|
||||||
|
objectId: accountId,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { AWS: accountId },
|
||||||
|
objectValue: { AWS: accountId },
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should allow access if account id principal is contained in ' +
|
||||||
|
'user arn of non-',
|
||||||
|
bucketId: userArn,
|
||||||
|
objectId: userArn,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { AWS: accountId },
|
||||||
|
objectValue: { AWS: accountId },
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should allow access if account id principal is contained in ' +
|
||||||
|
'account arn of non-',
|
||||||
|
bucketId: accountArn,
|
||||||
|
objectId: accountArn,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { AWS: accountId },
|
||||||
|
objectValue: { AWS: accountId },
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should allow access if account arn principal is contained in ' +
|
||||||
|
'user arn of non-',
|
||||||
|
bucketId: userArn,
|
||||||
|
objectId: userArn,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { AWS: accountArn },
|
||||||
|
objectValue: { AWS: accountArn },
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should deny access if account arn principal doesn\'t match ' +
|
||||||
|
'user arn of non-',
|
||||||
|
bucketId: otherAccountUserArn,
|
||||||
|
objectId: otherAccountUserArn,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { AWS: accountArn },
|
||||||
|
objectValue: { AWS: accountArn },
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should deny access if user arn principal doesn\'t match ' +
|
||||||
|
'user arn of non-',
|
||||||
|
bucketId: userArn,
|
||||||
|
objectId: userArn,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { AWS: otherUserArn },
|
||||||
|
objectValue: { AWS: otherUserArn },
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should deny access if principal doesn\'t match non-',
|
||||||
|
bucketId: canonicalIdToVet,
|
||||||
|
objectId: canonicalIdToVet,
|
||||||
|
keyToChange: 'Principal',
|
||||||
|
bucketValue: { CanonicalUser: [bucketOwnerCanonicalId] },
|
||||||
|
objectValue: { CanonicalUser: [objectOwnerCanonicalId] },
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should allow access if principal and action match policy for ' +
|
||||||
|
'non-',
|
||||||
|
bucketId: canonicalIdToVet,
|
||||||
|
objectId: canonicalIdToVet,
|
||||||
|
keyToChange: 'Action',
|
||||||
|
bucketValue: ['s3:ListBucket'],
|
||||||
|
objectValue: ['s3:PutObject'],
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should deny access if principal matches but action does not ' +
|
||||||
|
'match policy for non-',
|
||||||
|
bucketId: canonicalIdToVet,
|
||||||
|
objectId: canonicalIdToVet,
|
||||||
|
keyToChange: 'Action',
|
||||||
|
bucketValue: ['s3:GetBucketLocation'],
|
||||||
|
objectValue: ['s3:GetObject'],
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should allow access even if bucket policy denies for ',
|
||||||
|
bucketId: bucketOwnerCanonicalId,
|
||||||
|
objectId: objectOwnerCanonicalId,
|
||||||
|
keyToChange: 'Effect',
|
||||||
|
bucketValue: 'Deny',
|
||||||
|
objectValue: 'Deny',
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const resourceTests = [
|
||||||
|
{
|
||||||
|
name: 'true if policy resource matches bucket arn',
|
||||||
|
rValue: `arn:aws:s3:::${bucketName}`,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'true if policy resource matches obj in bucket',
|
||||||
|
rValue: `arn:aws:s3:::${bucketName}/*`,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'false if policy resource is bucket name',
|
||||||
|
rValue: bucketName,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'false if policy resource does not match bucket arn',
|
||||||
|
rValue: 'arn:aws:s3:::nomatch',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'false if policy resource is array and any elements do not ' +
|
||||||
|
'match bucket arn',
|
||||||
|
rValue: [`arn:aws:s3:::${bucketName}`, 'arn:aws:s3:::nomatch'],
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('bucket policy authorization', () => {
|
||||||
|
describe('isBucketAuthorized with no policy set', () => {
|
||||||
|
it('should allow access to bucket owner', done => {
|
||||||
|
const allowed = isBucketAuthorized(bucket, 'bucketPut',
|
||||||
|
bucketOwnerCanonicalId, null, log);
|
||||||
|
assert.equal(allowed, true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to non-bucket owner',
|
||||||
|
done => {
|
||||||
|
const allowed = isBucketAuthorized(bucket, 'bucketPut',
|
||||||
|
canonicalIdToVet, null, log);
|
||||||
|
assert.equal(allowed, false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isBucketAuthorized with bucket policy set', () => {
|
||||||
|
beforeEach(function beFn() {
|
||||||
|
this.currentTest.basePolicy = new BucketPolicy(JSON.stringify(
|
||||||
|
basePolicyObj)).getBucketPolicy();
|
||||||
|
bucket.setBucketPolicy(this.currentTest.basePolicy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access to non-bucket owner if principal is set to "*"',
|
||||||
|
done => {
|
||||||
|
const allowed = isBucketAuthorized(bucket, bucAction,
|
||||||
|
canonicalIdToVet, null, log);
|
||||||
|
assert.equal(allowed, true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access to public user if principal is set to "*"',
|
||||||
|
done => {
|
||||||
|
const allowed = isBucketAuthorized(bucket, bucAction,
|
||||||
|
constants.publicId, null, log);
|
||||||
|
assert.equal(allowed, true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
authTests.forEach(t => {
|
||||||
|
it(`${t.name}bucket owner`, function itFn(done) {
|
||||||
|
const newPolicy = this.test.basePolicy;
|
||||||
|
newPolicy.Statement[0][t.keyToChange] = t.bucketValue;
|
||||||
|
bucket.setBucketPolicy(newPolicy);
|
||||||
|
const allowed = isBucketAuthorized(bucket, bucAction,
|
||||||
|
t.bucketId, t.bucketId, log);
|
||||||
|
assert.equal(allowed, t.expected);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to non-bucket owner if two statements apply ' +
|
||||||
|
'to principal but one denies access', function itFn(done) {
|
||||||
|
const newPolicy = this.test.basePolicy;
|
||||||
|
newPolicy.Statement[1] = {
|
||||||
|
Effect: 'Deny',
|
||||||
|
Principal: { CanonicalUser: [canonicalIdToVet] },
|
||||||
|
Resource: `arn:aws:s3:::${bucket.getName()}`,
|
||||||
|
Action: 's3:*',
|
||||||
|
};
|
||||||
|
bucket.setBucketPolicy(newPolicy);
|
||||||
|
const allowed = isBucketAuthorized(bucket, bucAction,
|
||||||
|
canonicalIdToVet, null, log);
|
||||||
|
assert.equal(allowed, false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isObjAuthorized with no policy set', () => {
|
||||||
|
before(() => {
|
||||||
|
bucket.setBucketPolicy(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access to object owner', done => {
|
||||||
|
const allowed = isObjAuthorized(bucket, object, objAction,
|
||||||
|
objectOwnerCanonicalId, null, log);
|
||||||
|
assert.equal(allowed, true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to non-object owner',
|
||||||
|
done => {
|
||||||
|
const allowed = isObjAuthorized(bucket, object, objAction,
|
||||||
|
canonicalIdToVet, null, log);
|
||||||
|
assert.equal(allowed, false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isObjAuthorized with bucket policy set', () => {
|
||||||
|
beforeEach(function beFn() {
|
||||||
|
const newPolicyObj = basePolicyObj;
|
||||||
|
newPolicyObj.Statement.Resource =
|
||||||
|
`arn:aws:s3:::${bucket.getName()}/*`;
|
||||||
|
this.currentTest.basePolicy = new BucketPolicy(JSON.stringify(
|
||||||
|
newPolicyObj)).getBucketPolicy();
|
||||||
|
bucket.setBucketPolicy(this.currentTest.basePolicy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access to non-object owner if principal is set to "*"',
|
||||||
|
done => {
|
||||||
|
const allowed = isObjAuthorized(bucket, object, objAction,
|
||||||
|
canonicalIdToVet, null, log);
|
||||||
|
assert.equal(allowed, true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access to public user if principal is set to "*"',
|
||||||
|
done => {
|
||||||
|
const allowed = isObjAuthorized(bucket, object, objAction,
|
||||||
|
constants.publicId, null, log);
|
||||||
|
assert.equal(allowed, true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
authTests.forEach(t => {
|
||||||
|
it(`${t.name}object owner`, function itFn(done) {
|
||||||
|
const newPolicy = this.test.basePolicy;
|
||||||
|
newPolicy.Statement[0][t.keyToChange] = t.objectValue;
|
||||||
|
bucket.setBucketPolicy(newPolicy);
|
||||||
|
const allowed = isObjAuthorized(bucket, object, objAction,
|
||||||
|
t.objectId, t.objectId, log);
|
||||||
|
assert.equal(allowed, t.expected);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to non-object owner if two statements apply ' +
|
||||||
|
'to principal but one denies access', function itFn(done) {
|
||||||
|
const newPolicy = this.test.basePolicy;
|
||||||
|
newPolicy.Statement[1] = {
|
||||||
|
Effect: 'Deny',
|
||||||
|
Principal: { CanonicalUser: [canonicalIdToVet] },
|
||||||
|
Resource: `arn:aws:s3:::${bucket.getName()}/*`,
|
||||||
|
Action: 's3:*',
|
||||||
|
};
|
||||||
|
bucket.setBucketPolicy(newPolicy);
|
||||||
|
const allowed = isObjAuthorized(bucket, object, objAction,
|
||||||
|
canonicalIdToVet, null, log);
|
||||||
|
assert.equal(allowed, false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validate policy resource', () => {
|
||||||
|
resourceTests.forEach(t => {
|
||||||
|
it(`should return ${t.name}`, done => {
|
||||||
|
const newPolicy = basePolicyObj;
|
||||||
|
newPolicy.Statement.Resource = t.rValue;
|
||||||
|
newPolicy.Statement = [newPolicy.Statement];
|
||||||
|
assert.equal(
|
||||||
|
validatePolicyResource(bucketName, newPolicy), t.expected);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if any statement resource does not match ' +
|
||||||
|
'bucket arn', done => {
|
||||||
|
const newPolicy = basePolicyObj;
|
||||||
|
newPolicy.Statement = [newPolicy.Statement];
|
||||||
|
newPolicy.Statement[1] = basePolicyObj.Statement;
|
||||||
|
newPolicy.Statement[0].Resource = `arn:aws:s3:::${bucketName}`;
|
||||||
|
assert.equal(validatePolicyResource(bucketName, newPolicy), false);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -18,36 +18,35 @@ const testBucketPutRequest = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectedBucketPolicy = {
|
const expectedBucketPolicy = {
|
||||||
version: '2012-10-17',
|
Version: '2012-10-17',
|
||||||
statements: [
|
Statement: [
|
||||||
{
|
{
|
||||||
sid: '',
|
Effect: 'Allow',
|
||||||
effect: 'Allow',
|
Resource: `arn:aws:s3:::${bucketName}`,
|
||||||
resource: 'arn:aws:s3::bucketname',
|
Principal: '*',
|
||||||
principal: '*',
|
Action: ['s3:GetBucketLocation'],
|
||||||
actions: ['s3:sampleAction'],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const testPutPolicyRequest = {
|
function getPolicyRequest(policy) {
|
||||||
bucketName,
|
return {
|
||||||
headers: {
|
bucketName,
|
||||||
host: `${bucketName}.s3.amazonaws.com`,
|
headers: {
|
||||||
},
|
host: `${bucketName}.s3.amazonaws.com`,
|
||||||
post: JSON.stringify(expectedBucketPolicy),
|
},
|
||||||
};
|
post: JSON.stringify(policy),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const describeSkipUntilImpl =
|
describe('putBucketPolicy API', () => {
|
||||||
process.env.BUCKET_POLICY ? describe : describe.skip;
|
|
||||||
|
|
||||||
describeSkipUntilImpl('putBucketPolicy API', () => {
|
|
||||||
before(() => cleanup());
|
before(() => cleanup());
|
||||||
beforeEach(done => bucketPut(authInfo, testBucketPutRequest, log, done));
|
beforeEach(done => bucketPut(authInfo, testBucketPutRequest, log, done));
|
||||||
afterEach(() => cleanup());
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
it('should update a bucket\'s metadata with bucket policy obj', done => {
|
it('should update a bucket\'s metadata with bucket policy obj', done => {
|
||||||
bucketPutPolicy(authInfo, testPutPolicyRequest, log, err => {
|
bucketPutPolicy(authInfo, getPolicyRequest(expectedBucketPolicy),
|
||||||
|
log, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
process.stdout.write(`Err putting bucket policy ${err}`);
|
process.stdout.write(`Err putting bucket policy ${err}`);
|
||||||
return done(err);
|
return done(err);
|
||||||
|
@ -63,4 +62,15 @@ describeSkipUntilImpl('putBucketPolicy API', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return error if policy resource does not include bucket name',
|
||||||
|
done => {
|
||||||
|
expectedBucketPolicy.Statement[0].Resource = 'arn:aws::s3:::badname';
|
||||||
|
bucketPutPolicy(authInfo, getPolicyRequest(expectedBucketPolicy),
|
||||||
|
log, err => {
|
||||||
|
assert.strictEqual(err.MalformedPolicy, true);
|
||||||
|
assert.strictEqual(err.description, 'Policy has invalid resource');
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,8 @@ const assert = require('assert');
|
||||||
const BucketInfo = require('arsenal').models.BucketInfo;
|
const BucketInfo = require('arsenal').models.BucketInfo;
|
||||||
const constants = require('../../../constants');
|
const constants = require('../../../constants');
|
||||||
const { isObjAuthorized }
|
const { isObjAuthorized }
|
||||||
= require('../../../lib/api/apiUtils/authorization/aclChecks');
|
= require('../../../lib/api/apiUtils/authorization/permissionChecks');
|
||||||
|
const { DummyRequestLogger } = require('../helpers');
|
||||||
|
|
||||||
const bucketOwnerCanonicalId = 'bucketOwnerCanonicalId';
|
const bucketOwnerCanonicalId = 'bucketOwnerCanonicalId';
|
||||||
const creationDate = new Date().toJSON();
|
const creationDate = new Date().toJSON();
|
||||||
|
@ -21,6 +22,7 @@ const object = {
|
||||||
READ_ACP: [],
|
READ_ACP: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const log = new DummyRequestLogger();
|
||||||
|
|
||||||
describe('object acl authorization for objectGet and objectHead', () => {
|
describe('object acl authorization for objectGet and objectHead', () => {
|
||||||
// Reset the object ACLs
|
// Reset the object ACLs
|
||||||
|
@ -38,21 +40,22 @@ describe('object acl authorization for objectGet and objectHead', () => {
|
||||||
|
|
||||||
it('should allow access to object owner', () => {
|
it('should allow access to object owner', () => {
|
||||||
const results = requestTypes.map(type =>
|
const results = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, objectOwnerCanonicalId));
|
isObjAuthorized(bucket, object, type, objectOwnerCanonicalId,
|
||||||
|
null, log));
|
||||||
assert.deepStrictEqual(results, [true, true]);
|
assert.deepStrictEqual(results, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access to anyone if canned public-read ACL', () => {
|
it('should allow access to anyone if canned public-read ACL', () => {
|
||||||
object.acl.Canned = 'public-read';
|
object.acl.Canned = 'public-read';
|
||||||
const results = requestTypes.map(type =>
|
const results = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(results, [true, true]);
|
assert.deepStrictEqual(results, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access to anyone if canned public-read-write ACL', () => {
|
it('should allow access to anyone if canned public-read-write ACL', () => {
|
||||||
object.acl.Canned = 'public-read-write';
|
object.acl.Canned = 'public-read-write';
|
||||||
const results = requestTypes.map(type =>
|
const results = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(results, [true, true]);
|
assert.deepStrictEqual(results, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,7 +63,7 @@ describe('object acl authorization for objectGet and objectHead', () => {
|
||||||
'authenticated-read ACL', () => {
|
'authenticated-read ACL', () => {
|
||||||
object.acl.Canned = 'authenticated-read';
|
object.acl.Canned = 'authenticated-read';
|
||||||
const publicResults = requestTypes.map(type =>
|
const publicResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, constants.publicId));
|
isObjAuthorized(bucket, object, type, constants.publicId, null, log));
|
||||||
assert.deepStrictEqual(publicResults, [false, false]);
|
assert.deepStrictEqual(publicResults, [false, false]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -68,63 +71,67 @@ describe('object acl authorization for objectGet and objectHead', () => {
|
||||||
'authenticated-read ACL', () => {
|
'authenticated-read ACL', () => {
|
||||||
object.acl.Canned = 'authenticated-read';
|
object.acl.Canned = 'authenticated-read';
|
||||||
const results = requestTypes.map(type =>
|
const results = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(results, [true, true]);
|
assert.deepStrictEqual(results, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access to bucker owner if ' +
|
it('should allow access to bucker owner if ' +
|
||||||
'bucket-owner-read ACL', () => {
|
'bucket-owner-read ACL', () => {
|
||||||
const noAuthResults = requestTypes.map(type =>
|
const noAuthResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId));
|
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId, null,
|
||||||
|
log));
|
||||||
assert.deepStrictEqual(noAuthResults, [false, false]);
|
assert.deepStrictEqual(noAuthResults, [false, false]);
|
||||||
object.acl.Canned = 'bucket-owner-read';
|
object.acl.Canned = 'bucket-owner-read';
|
||||||
const authResults = requestTypes.map(type =>
|
const authResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId));
|
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId, null,
|
||||||
|
log));
|
||||||
assert.deepStrictEqual(authResults, [true, true]);
|
assert.deepStrictEqual(authResults, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access to bucker owner if ' +
|
it('should allow access to bucker owner if ' +
|
||||||
'bucket-owner-full-control ACL', () => {
|
'bucket-owner-full-control ACL', () => {
|
||||||
const noAuthResults = requestTypes.map(type =>
|
const noAuthResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId));
|
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId, null,
|
||||||
|
log));
|
||||||
assert.deepStrictEqual(noAuthResults, [false, false]);
|
assert.deepStrictEqual(noAuthResults, [false, false]);
|
||||||
object.acl.Canned = 'bucket-owner-full-control';
|
object.acl.Canned = 'bucket-owner-full-control';
|
||||||
const authResults = requestTypes.map(type =>
|
const authResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId));
|
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId, null,
|
||||||
|
log));
|
||||||
assert.deepStrictEqual(authResults, [true, true]);
|
assert.deepStrictEqual(authResults, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access to account if ' +
|
it('should allow access to account if ' +
|
||||||
'account was granted FULL_CONTROL', () => {
|
'account was granted FULL_CONTROL', () => {
|
||||||
const noAuthResults = requestTypes.map(type =>
|
const noAuthResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(noAuthResults, [false, false]);
|
assert.deepStrictEqual(noAuthResults, [false, false]);
|
||||||
object.acl.FULL_CONTROL = [accountToVet];
|
object.acl.FULL_CONTROL = [accountToVet];
|
||||||
const authResults = requestTypes.map(type =>
|
const authResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(authResults, [true, true]);
|
assert.deepStrictEqual(authResults, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access to account if ' +
|
it('should allow access to account if ' +
|
||||||
'account was granted READ right', () => {
|
'account was granted READ right', () => {
|
||||||
const noAuthResults = requestTypes.map(type =>
|
const noAuthResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(noAuthResults, [false, false]);
|
assert.deepStrictEqual(noAuthResults, [false, false]);
|
||||||
object.acl.READ = [accountToVet];
|
object.acl.READ = [accountToVet];
|
||||||
const authResults = requestTypes.map(type =>
|
const authResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(authResults, [true, true]);
|
assert.deepStrictEqual(authResults, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow access to public user if private canned ACL', () => {
|
it('should not allow access to public user if private canned ACL', () => {
|
||||||
const results = requestTypes.map(type =>
|
const results = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(results, [false, false]);
|
assert.deepStrictEqual(results, [false, false]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow access to just any user if private canned ACL', () => {
|
it('should not allow access to just any user if private canned ACL', () => {
|
||||||
const results = requestTypes.map(type =>
|
const results = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(results, [false, false]);
|
assert.deepStrictEqual(results, [false, false]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -134,10 +141,11 @@ describe('object authorization for objectPut and objectDelete', () => {
|
||||||
'are done at bucket level', () => {
|
'are done at bucket level', () => {
|
||||||
const requestTypes = ['objectPut', 'objectDelete'];
|
const requestTypes = ['objectPut', 'objectDelete'];
|
||||||
const results = requestTypes.map(type =>
|
const results = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(results, [true, true]);
|
assert.deepStrictEqual(results, [true, true]);
|
||||||
const publicUserResults = requestTypes.map(type =>
|
const publicUserResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, constants.publicId));
|
isObjAuthorized(bucket, object, type, constants.publicId, null,
|
||||||
|
log));
|
||||||
assert.deepStrictEqual(publicUserResults, [true, true]);
|
assert.deepStrictEqual(publicUserResults, [true, true]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -159,51 +167,54 @@ describe('object authorization for objectPutACL and objectGetACL', () => {
|
||||||
|
|
||||||
it('should allow access to object owner', () => {
|
it('should allow access to object owner', () => {
|
||||||
const results = requestTypes.map(type =>
|
const results = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, objectOwnerCanonicalId));
|
isObjAuthorized(bucket, object, type, objectOwnerCanonicalId,
|
||||||
|
null, log));
|
||||||
assert.deepStrictEqual(results, [true, true]);
|
assert.deepStrictEqual(results, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access to bucket owner if ' +
|
it('should allow access to bucket owner if ' +
|
||||||
'bucket-owner-full-control canned ACL set on object', () => {
|
'bucket-owner-full-control canned ACL set on object', () => {
|
||||||
const noAuthResults = requestTypes.map(type =>
|
const noAuthResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId));
|
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId, null,
|
||||||
|
log));
|
||||||
assert.deepStrictEqual(noAuthResults, [false, false]);
|
assert.deepStrictEqual(noAuthResults, [false, false]);
|
||||||
object.acl.Canned = 'bucket-owner-full-control';
|
object.acl.Canned = 'bucket-owner-full-control';
|
||||||
const authorizedResults = requestTypes.map(type =>
|
const authorizedResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId));
|
isObjAuthorized(bucket, object, type, bucketOwnerCanonicalId,
|
||||||
|
null, log));
|
||||||
assert.deepStrictEqual(authorizedResults, [true, true]);
|
assert.deepStrictEqual(authorizedResults, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access to account if ' +
|
it('should allow access to account if ' +
|
||||||
'account was granted FULL_CONTROL right', () => {
|
'account was granted FULL_CONTROL right', () => {
|
||||||
const noAuthResults = requestTypes.map(type =>
|
const noAuthResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(noAuthResults, [false, false]);
|
assert.deepStrictEqual(noAuthResults, [false, false]);
|
||||||
object.acl.FULL_CONTROL = [accountToVet];
|
object.acl.FULL_CONTROL = [accountToVet];
|
||||||
const authorizedResults = requestTypes.map(type =>
|
const authorizedResults = requestTypes.map(type =>
|
||||||
isObjAuthorized(bucket, object, type, accountToVet));
|
isObjAuthorized(bucket, object, type, accountToVet, null, log));
|
||||||
assert.deepStrictEqual(authorizedResults, [true, true]);
|
assert.deepStrictEqual(authorizedResults, [true, true]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow objectPutACL access to account if ' +
|
it('should allow objectPutACL access to account if ' +
|
||||||
'account was granted WRITE_ACP right', () => {
|
'account was granted WRITE_ACP right', () => {
|
||||||
const noAuthResult = isObjAuthorized(bucket, object, 'objectPutACL',
|
const noAuthResult = isObjAuthorized(bucket, object, 'objectPutACL',
|
||||||
accountToVet);
|
accountToVet, null, log);
|
||||||
assert.strictEqual(noAuthResult, false);
|
assert.strictEqual(noAuthResult, false);
|
||||||
object.acl.WRITE_ACP = [accountToVet];
|
object.acl.WRITE_ACP = [accountToVet];
|
||||||
const authorizedResult = isObjAuthorized(bucket, object, 'objectPutACL',
|
const authorizedResult = isObjAuthorized(bucket, object, 'objectPutACL',
|
||||||
accountToVet);
|
accountToVet, null, log);
|
||||||
assert.strictEqual(authorizedResult, true);
|
assert.strictEqual(authorizedResult, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow objectGetACL access to account if ' +
|
it('should allow objectGetACL access to account if ' +
|
||||||
'account was granted READ_ACP right', () => {
|
'account was granted READ_ACP right', () => {
|
||||||
const noAuthResult = isObjAuthorized(bucket, object, 'objectGetACL',
|
const noAuthResult = isObjAuthorized(bucket, object, 'objectGetACL',
|
||||||
accountToVet);
|
accountToVet, null, log);
|
||||||
assert.strictEqual(noAuthResult, false);
|
assert.strictEqual(noAuthResult, false);
|
||||||
object.acl.READ_ACP = [accountToVet];
|
object.acl.READ_ACP = [accountToVet];
|
||||||
const authorizedResult = isObjAuthorized(bucket, object, 'objectGetACL',
|
const authorizedResult = isObjAuthorized(bucket, object, 'objectGetACL',
|
||||||
accountToVet);
|
accountToVet, null, log);
|
||||||
assert.strictEqual(authorizedResult, true);
|
assert.strictEqual(authorizedResult, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue