Repository: winglang/wing (TypeScript)

Issue:
tryGet is not asking for permissions
### I tried this:

```ts
bring cloud;
bring util;

let origin = new cloud.Bucket() as "origin";
let dest = new cloud.Bucket() as "dest";

origin.onCreate(inflight (key: str): void => {
  let s = origin.get(key);
  dest.put(key, s.uppercase());
});

test "main" {
  origin.put("file", "some");
  assert(util.waitUntil((): bool =>{
    return dest.tryGet("file") == "SOME";
  }));
}
```

### This happened:

The test failed on aws because of permissions, the iam rules where not granted for the lambda function because `tryGet` is not part of `calculateBucketPermissions` tf-aws bucket function

### I expected this:

to pass

### Is there a workaround?

_No response_

### Component

SDK

### Wing Version

_No response_

### Wing Console Version

_No response_

### Node.js Version

_No response_

### Platform(s)

_No response_

### Anything else?

The problem is that tryGet is not part of calculateBucketPermissions

 

### Community Notes

<!-- Please keep this note for the community -->
* Please vote by adding a 👍 reaction to the issue to help us prioritize.
* If you are interested to work on this issue, please leave a comment.




Fix (patch):
diff --git a/libs/wingsdk/src/shared-aws/bucket.inflight.ts b/libs/wingsdk/src/shared-aws/bucket.inflight.ts
index fb5aeef5a5e..d27e5147168 100644
--- a/libs/wingsdk/src/shared-aws/bucket.inflight.ts
+++ b/libs/wingsdk/src/shared-aws/bucket.inflight.ts
@@ -7,6 +7,8 @@ import {
   ListObjectsV2CommandOutput,
   PutObjectCommand,
   GetBucketLocationCommand,
+  GetPublicAccessBlockCommand,
+  GetPublicAccessBlockCommandOutput,
   S3Client,
   GetObjectOutput,
 } from "@aws-sdk/client-s3";
@@ -16,7 +18,6 @@ import { Duration, Json } from "../std";
 export class BucketClient implements IBucketClient {
   constructor(
     private readonly bucketName: string,
-    private readonly _public: boolean = false,
     private readonly s3Client = new S3Client({})
   ) {}
 
@@ -138,6 +139,10 @@ export class BucketClient implements IBucketClient {
   public async delete(key: string, opts?: BucketDeleteOptions): Promise<void> {
     const mustExist = opts?.mustExist ?? false;
 
+    if (mustExist && !(await this.exists(key))) {
+      throw new Error(`Object does not exist (key=${key}).`);
+    }
+
     const command = new DeleteObjectCommand({
       Key: key,
       Bucket: this.bucketName,
@@ -151,7 +156,7 @@ export class BucketClient implements IBucketClient {
         return;
       }
 
-      throw Error(`unable to delete "${key}": ${error.message}`);
+      throw new Error(`Unable to delete "${key}": ${error.message}`);
     }
   }
 
@@ -200,15 +205,34 @@ export class BucketClient implements IBucketClient {
     }
     return list;
   }
+  /**
+   * checks if the bucket is public
+   * @returns true if the bucket is public and false otherwise
+   */
+  private async checkIfPublic(): Promise<boolean> {
+    const command = new GetPublicAccessBlockCommand({
+      Bucket: this.bucketName,
+    });
+    const resp: GetPublicAccessBlockCommandOutput = await this.s3Client.send(
+      command
+    );
+    return (
+      !resp.PublicAccessBlockConfiguration?.BlockPublicAcls &&
+      !resp.PublicAccessBlockConfiguration?.BlockPublicPolicy &&
+      !resp.PublicAccessBlockConfiguration?.RestrictPublicBuckets &&
+      !resp.PublicAccessBlockConfiguration?.IgnorePublicAcls
+    );
+  }
 
   /**
    * Returns a url to the given file.
    * @Throws if the file is not public or if object does not exist.
    */
   public async publicUrl(key: string): Promise<string> {
-    if (!this._public) {
+    if (!(await this.checkIfPublic())) {
       throw new Error("Cannot provide public url for a non-public bucket");
     }
+
     if (!(await this.exists(key))) {
       throw new Error(
         `Cannot provide public url for an non-existent key (key=${key})`
diff --git a/libs/wingsdk/src/shared-aws/permissions.ts b/libs/wingsdk/src/shared-aws/permissions.ts
index f04587901a7..3e00c3ca731 100644
--- a/libs/wingsdk/src/shared-aws/permissions.ts
+++ b/libs/wingsdk/src/shared-aws/permissions.ts
@@ -85,42 +85,61 @@ export function calculateBucketPermissions(
   arn: string,
   ops: string[]
 ): PolicyStatement[] {
-  const policies: PolicyStatement[] = [];
+  const actions: string[] = [];
+  // const policies: PolicyStatement[] = [];
 
+  // contains a check if an object exists/list
+  if (
+    ops.includes(cloud.BucketInflightMethods.PUBLIC_URL) ||
+    ops.includes(cloud.BucketInflightMethods.LIST) ||
+    ops.includes(cloud.BucketInflightMethods.EXISTS) ||
+    ops.includes(cloud.BucketInflightMethods.TRY_GET) ||
+    ops.includes(cloud.BucketInflightMethods.TRY_GET_JSON) ||
+    ops.includes(cloud.BucketInflightMethods.TRY_DELETE)
+  ) {
+    actions.push("s3:List*");
+  }
+
+  // putting an object
   if (
     ops.includes(cloud.BucketInflightMethods.PUT) ||
     ops.includes(cloud.BucketInflightMethods.PUT_JSON)
   ) {
-    policies.push({
-      actions: ["s3:PutObject*", "s3:Abort*"],
-      resources: [arn, `${arn}/*`],
-    });
+    actions.push(...["s3:PutObject*", "s3:Abort*"]);
   }
 
+  // getting an object
   if (
     ops.includes(cloud.BucketInflightMethods.GET) ||
     ops.includes(cloud.BucketInflightMethods.GET_JSON) ||
     ops.includes(cloud.BucketInflightMethods.LIST) ||
+    ops.includes(cloud.BucketInflightMethods.TRY_GET) ||
+    ops.includes(cloud.BucketInflightMethods.TRY_GET_JSON) ||
     ops.includes(cloud.BucketInflightMethods.PUBLIC_URL)
   ) {
-    policies.push({
-      actions: ["s3:GetObject*", "s3:GetBucket*", "s3:List*"],
-      resources: [arn, `${arn}/*`],
-    });
+    actions.push(...["s3:GetObject*", "s3:GetBucket*"]);
   }
 
-  if (ops.includes(cloud.BucketInflightMethods.DELETE)) {
-    policies.push({
-      actions: [
+  // accessing the publicAccessBlock
+  if (ops.includes(cloud.BucketInflightMethods.PUBLIC_URL)) {
+    actions.push(...["s3:GetBucketPublicAccessBlock"]);
+  }
+
+  // deleting an object
+  if (
+    ops.includes(cloud.BucketInflightMethods.TRY_DELETE) ||
+    ops.includes(cloud.BucketInflightMethods.DELETE)
+  ) {
+    actions.push(
+      ...[
         "s3:DeleteObject*",
         "s3:DeleteObjectVersion*",
         "s3:PutLifecycleConfiguration*",
-      ],
-      resources: [arn, `${arn}/*`],
-    });
+      ]
+    );
   }
 
-  return policies;
+  return [{ actions, resources: [arn, `${arn}/*`] }];
 }
 
 export function calculateSecretPermissions(
diff --git a/libs/wingsdk/src/target-tf-aws/bucket.ts b/libs/wingsdk/src/target-tf-aws/bucket.ts
index 50a0acddbac..4c2fea77622 100644
--- a/libs/wingsdk/src/target-tf-aws/bucket.ts
+++ b/libs/wingsdk/src/target-tf-aws/bucket.ts
@@ -122,7 +122,6 @@ export class Bucket extends cloud.Bucket {
     // The bucket name needs to be passed through an environment variable since
     // it may not be resolved until deployment time.
     host.addEnvironment(this.envName(), this.bucket.bucket);
-    host.addEnvironment(this.isPublicEnvName(), `${this.public}`);
 
     super._bind(host, ops);
   }
@@ -133,17 +132,10 @@ export class Bucket extends cloud.Bucket {
       __dirname.replace("target-tf-aws", "shared-aws"),
       __filename,
       "BucketClient",
-      [
-        `process.env["${this.envName()}"]`,
-        `process.env["${this.isPublicEnvName()}"]`,
-      ]
+      [`process.env["${this.envName()}"]`]
     );
   }
 
-  private isPublicEnvName(): string {
-    return `${this.envName()}_IS_PUBLIC`;
-  }
-
   private envName(): string {
     return `BUCKET_NAME_${this.node.addr.slice(-8)}`;
   }
diff --git a/tools/hangar/__snapshots__/plugins.ts.snap b/tools/hangar/__snapshots__/plugins.ts.snap
index db68b509772..e3eb76d2edb 100644
--- a/tools/hangar/__snapshots__/plugins.ts.snap
+++ b/tools/hangar/__snapshots__/plugins.ts.snap
@@ -89,7 +89,6 @@ exports[`Plugin examples > AWS target plugins > permission-boundary.js 1`] = `
         "environment": {
           "variables": {
             "BUCKET_NAME_d755b447": "\${aws_s3_bucket.root_cloudBucket_4F3C4F53.bucket}",
-            "BUCKET_NAME_d755b447_IS_PUBLIC": "false",
             "WING_FUNCTION_NAME": "cloud-Queue-SetConsumer-cdafee6e-c8eb6a09",
             "WING_TARGET": "tf-aws",
           },
@@ -348,7 +347,6 @@ exports[`Plugin examples > AWS target plugins > replicate-s3.js 1`] = `
         "environment": {
           "variables": {
             "BUCKET_NAME_d755b447": "\${aws_s3_bucket.root_cloudBucket_4F3C4F53.bucket}",
-            "BUCKET_NAME_d755b447_IS_PUBLIC": "false",
             "WING_FUNCTION_NAME": "cloud-Queue-SetConsumer-cdafee6e-c8eb6a09",
             "WING_TARGET": "tf-aws",
           },
@@ -655,7 +653,6 @@ exports[`Plugin examples > AWS target plugins > tf-backend.js > azurerm backend
         "environment": {
           "variables": {
             "BUCKET_NAME_d755b447": "\${aws_s3_bucket.root_cloudBucket_4F3C4F53.bucket}",
-            "BUCKET_NAME_d755b447_IS_PUBLIC": "false",
             "WING_FUNCTION_NAME": "cloud-Queue-SetConsumer-cdafee6e-c8eb6a09",
             "WING_TARGET": "tf-aws",
           },
@@ -844,7 +841,6 @@ exports[`Plugin examples > AWS target plugins > tf-backend.js > gcp backend 1`]
         "environment": {
           "variables": {
             "BUCKET_NAME_d755b447": "\${aws_s3_bucket.root_cloudBucket_4F3C4F53.bucket}",
-            "BUCKET_NAME_d755b447_IS_PUBLIC": "false",
             "WING_FUNCTION_NAME": "cloud-Queue-SetConsumer-cdafee6e-c8eb6a09",
             "WING_TARGET": "tf-aws",
           },
@@ -1033,7 +1029,6 @@ exports[`Plugin examples > AWS target plugins > tf-backend.js > s3 backend 1`] =
         "environment": {
           "variables": {
             "BUCKET_NAME_d755b447": "\${aws_s3_bucket.root_cloudBucket_4F3C4F53.bucket}",
-            "BUCKET_NAME_d755b447_IS_PUBLIC": "false",
             "WING_FUNCTION_NAME": "cloud-Queue-SetConsumer-cdafee6e-c8eb6a09",
             "WING_TARGET": "tf-aws",
           },