@@ -17,6 +17,7 @@ const OP_NAME_TO_ACTION = Object.freeze({
1717 delete_bucket_replication : { regular : "s3:PutReplicationConfiguration" } ,
1818 delete_bucket_tagging : { regular : "s3:PutBucketTagging" } ,
1919 delete_bucket_website : { regular : "s3:DeleteBucketWebsite" } ,
20+ delete_bucket_public_access_block : { regular : "s3:PutBucketPublicAccessBlock" } ,
2021 delete_bucket : { regular : "s3:DeleteBucket" } ,
2122 delete_object_tagging : { regular : "s3:DeleteObjectTagging" , versioned : "s3:DeleteObjectVersionTagging" } ,
2223 delete_object_uploadId : { regular : "s3:AbortMultipartUpload" } ,
@@ -43,6 +44,7 @@ const OP_NAME_TO_ACTION = Object.freeze({
4344 get_bucket_versions : { regular : "s3:ListBucketVersions" } ,
4445 get_bucket_website : { regular : "s3:GetBucketWebsite" } ,
4546 get_bucket_object_lock : { regular : "s3:GetBucketObjectLockConfiguration" } ,
47+ get_bucket_public_access_block : { regular : "s3:GetBucketPublicAccessBlock" } ,
4648 get_bucket : { regular : "s3:ListBucket" } ,
4749 get_object_acl : { regular : "s3:GetObjectAcl" } ,
4850 get_object_attributes : { regular : [ "s3:GetObject" , "s3:GetObjectAttributes" ] , versioned : [ "s3:GetObjectVersion" , "s3:GetObjectVersionAttributes" ] } , // Notice - special case
@@ -80,6 +82,7 @@ const OP_NAME_TO_ACTION = Object.freeze({
8082 put_bucket_versioning : { regular : "s3:PutBucketVersioning" } ,
8183 put_bucket_website : { regular : "s3:PutBucketWebsite" } ,
8284 put_bucket_object_lock : { regular : "s3:PutBucketObjectLockConfiguration" } ,
85+ put_bucket_public_access_block : { regular : "s3:PutBucketPublicAccessBlock" } ,
8386 put_bucket : { regular : "s3:CreateBucket" } ,
8487 put_object_acl : { regular : "s3:PutObjectAcl" } ,
8588 put_object_tagging : { regular : "s3:PutObjectTagging" , versioned : "s3:PutObjectVersionTagging" } ,
@@ -146,18 +149,20 @@ async function _is_object_version_fit(req, predicate, value) {
146149 return res ;
147150}
148151
149- async function has_bucket_policy_permission ( policy , account , method , arn_path , req ) {
152+ async function has_bucket_policy_permission ( policy , account , method , arn_path , req , disallow_public_access = false ) {
150153 const [ allow_statements , deny_statements ] = _ . partition ( policy . Statement , statement => statement . Effect === 'Allow' ) ;
151154
152155 // the case where the permission is an array started in op get_object_attributes
153156 const method_arr = Array . isArray ( method ) ? method : [ method ] ;
154157
155158 // look for explicit denies
156- const res_arr_deny = await is_statement_fit_of_method_array ( deny_statements , account , method_arr , arn_path , req ) ;
159+ const res_arr_deny = await is_statement_fit_of_method_array (
160+ deny_statements , account , method_arr , arn_path , req , disallow_public_access ) ;
157161 if ( res_arr_deny . every ( item => item ) ) return 'DENY' ;
158162
159163 // look for explicit allows
160- const res_arr_allow = await is_statement_fit_of_method_array ( allow_statements , account , method_arr , arn_path , req ) ;
164+ const res_arr_allow = await is_statement_fit_of_method_array (
165+ allow_statements , account , method_arr , arn_path , req , disallow_public_access ) ;
161166 if ( res_arr_allow . every ( item => item ) ) return 'ALLOW' ;
162167
163168 // implicit deny
@@ -177,14 +182,19 @@ function _is_action_fit(method, statement) {
177182 return statement . Action ? action_fit : ! action_fit ;
178183}
179184
180- function _is_principal_fit ( account , statement ) {
185+ function _is_principal_fit ( account , statement , ignore_anon_principal = false ) {
181186 let statement_principal = statement . Principal || statement . NotPrincipal ;
182187
183188 let principal_fit = false ;
184189 statement_principal = statement_principal . AWS ? statement_principal . AWS : statement_principal ;
185190 for ( const principal of _ . flatten ( [ statement_principal ] ) ) {
186191 dbg . log1 ( 'bucket_policy: ' , statement . Principal ? 'Principal' : 'NotPrincipal' , ' fit?' , principal , account ) ;
187192 if ( ( principal . unwrap ( ) === '*' ) || ( principal . unwrap ( ) === account ) ) {
193+ if ( ignore_anon_principal && principal . unwrap ( ) === '*' && statement . Principal ) {
194+ // Ignore the "fit" if ignore_anon_principal is requested
195+ continue ;
196+ }
197+
188198 principal_fit = true ;
189199 break ;
190200 }
@@ -207,15 +217,15 @@ function _is_resource_fit(arn_path, statement) {
207217 return statement . Resource ? resource_fit : ! resource_fit ;
208218}
209219
210- async function is_statement_fit_of_method_array ( statements , account , method_arr , arn_path , req ) {
220+ async function is_statement_fit_of_method_array ( statements , account , method_arr , arn_path , req , disallow_public_access = false ) {
211221 return Promise . all ( method_arr . map ( method_permission =>
212- _is_statements_fit ( statements , account , method_permission , arn_path , req ) ) ) ;
222+ _is_statements_fit ( statements , account , method_permission , arn_path , req , disallow_public_access ) ) ) ;
213223}
214224
215- async function _is_statements_fit ( statements , account , method , arn_path , req ) {
225+ async function _is_statements_fit ( statements , account , method , arn_path , req , disallow_public_access = false ) {
216226 for ( const statement of statements ) {
217227 const action_fit = _is_action_fit ( method , statement ) ;
218- const principal_fit = _is_principal_fit ( account , statement ) ;
228+ const principal_fit = _is_principal_fit ( account , statement , disallow_public_access ) ;
219229 const resource_fit = _is_resource_fit ( arn_path , statement ) ;
220230 const condition_fit = await _is_condition_fit ( statement , req , method ) ;
221231
@@ -304,6 +314,34 @@ async function validate_s3_policy(policy, bucket_name, get_account_handler) {
304314 }
305315}
306316
317+ /**
318+ * allows_public_access returns true if a policy will allow public access
319+ * to a resource
320+ *
321+ * NOTE: It assumes that the given policy has already been validated
322+ * @param {* } policy
323+ * @returns {boolean }
324+ */
325+ function allows_public_access ( policy ) {
326+ for ( const statement of policy . Statement ) {
327+ if ( statement . Effect === 'Deny' ) continue ;
328+
329+ const statement_principal = statement . Principal ;
330+ if ( statement_principal . AWS ) {
331+ for ( const principal of _ . flatten ( [ statement_principal . AWS ] ) ) {
332+ if ( typeof principal === 'string' ? principal === '*' : principal . unwrap ( ) === '*' ) {
333+ return true ;
334+ }
335+ }
336+ } else if ( typeof statement_principal === 'string' ? statement_principal === '*' : statement_principal . unwrap ( ) === '*' ) {
337+ return true ;
338+ }
339+ }
340+
341+ return false ;
342+ }
343+
307344exports . OP_NAME_TO_ACTION = OP_NAME_TO_ACTION ;
308345exports . has_bucket_policy_permission = has_bucket_policy_permission ;
309346exports . validate_s3_policy = validate_s3_policy ;
347+ exports . allows_public_access = allows_public_access ;
0 commit comments