Skip to content

Commit

Permalink
refactor permissions not to use custom roles
Browse files Browse the repository at this point in the history
  • Loading branch information
tsuf239 committed Jun 30, 2024
1 parent 70abbb9 commit 5d3f769
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 97 deletions.
4 changes: 2 additions & 2 deletions examples/tests/sdk_tests/bucket/load_test.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ bring cloud;

let b = new cloud.Bucket();

test "uploading many objects" {
new std.Test(inflight () => {
for i in 0..500 {
b.put("test{i}", "{i}");
}
}
}, timeout: 3m) as "uploading many objects";
23 changes: 20 additions & 3 deletions libs/wingsdk/src/target-tf-gcp/bucket.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Fn } from "cdktf";
import { Construct } from "constructs";
import { App } from "./app";
import { Function as GCPFunction } from "./function";
import { calculateBucketPermissions } from "./permissions";
import { createBucketPermissions } from "./permissions";
import { ProjectService } from "../.gen/providers/google/project-service";
import { StorageBucket } from "../.gen/providers/google/storage-bucket";
import { StorageBucketIamMember } from "../.gen/providers/google/storage-bucket-iam-member";
Expand Down Expand Up @@ -197,8 +198,24 @@ export class Bucket extends cloud.Bucket {
throw new Error("buckets can only be bound by tfgcp.Function for now");
}

const permissions = calculateBucketPermissions(ops);
host.addPermissions(permissions);
if (ops.includes(cloud.BucketInflightMethods.SIGNED_URL)) {
host._addTokenCreator();
}

for (const role of createBucketPermissions(ops)) {
const bucketHash = Fn.sha256(this.bucket.name).slice(-8);
const permissionHash = Fn.sha256(role).slice(-8);

new StorageBucketIamMember(
this,
`bucket-iam-member-${bucketHash}-${permissionHash}`,
{
bucket: this.bucket.name,
role: role,
member: `serviceAccount:${host.serviceAccountEmail}`,
}
);
}

host.addEnvironment(this.envName(), this.bucket.name);
super.onLift(host, ops);
Expand Down
5 changes: 3 additions & 2 deletions libs/wingsdk/src/target-tf-gcp/counter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ export class Counter extends cloud.Counter {
throw new Error("counters can only be bound by tfgcp.Function for now");
}

const permissions = calculateCounterPermissions(ops);
host.addPermissions(permissions);
for (const role of calculateCounterPermissions(ops)) {
host._addProjectIamMember(role);
}

host.addEnvironment(this.envName(), this.database.name);
super.onLift(host, ops);
Expand Down
72 changes: 36 additions & 36 deletions libs/wingsdk/src/target-tf-gcp/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { Bucket } from "./bucket";
import { core } from "..";
import { CloudfunctionsFunction } from "../.gen/providers/google/cloudfunctions-function";
import { CloudfunctionsFunctionIamMember } from "../.gen/providers/google/cloudfunctions-function-iam-member";
import { ProjectIamCustomRole } from "../.gen/providers/google/project-iam-custom-role";
import { ProjectIamMember } from "../.gen/providers/google/project-iam-member";
import { ServiceAccount } from "../.gen/providers/google/service-account";
import { ServiceAccountIamMember } from "../.gen/providers/google/service-account-iam-member";
import { StorageBucketObject } from "../.gen/providers/google/storage-bucket-object";
import * as cloud from "../cloud";
import { LiftMap } from "../core";
Expand Down Expand Up @@ -73,11 +73,8 @@ export class Function extends cloud.Function {
}

private readonly function: CloudfunctionsFunction;
private readonly functionServiceAccount: ServiceAccount;
private readonly functionCustomRole: ProjectIamCustomRole;
private readonly permissions: Set<string> = new Set([
"cloudfunctions.functions.get",
]);
public readonly functionServiceAccount: ServiceAccount;
private roles?: Set<string> = new Set();

private assetPath: string | undefined; // posix path

Expand Down Expand Up @@ -136,7 +133,7 @@ export class Function extends cloud.Function {
}
);

// Step 1: Create Custom Service Account
// Create Custom Service Account
this.functionServiceAccount = new ServiceAccount(
this,
`ServiceAccount${this.node.addr.substring(-8)}`,
Expand All @@ -147,25 +144,7 @@ export class Function extends cloud.Function {
)}`,
}
);
// Step 2: Create Custom Role
this.functionCustomRole = new ProjectIamCustomRole(
this,
`CustomRole${this.node.addr.substring(-8)}`,
{
roleId: `cloudfunctions.custom${this.node.addr.substring(-8)}`,
title: `Custom Role for Cloud Function ${this.node.addr.substring(-8)}`,
permissions: Lazy.listValue({
produce: () => Array.from(this.permissions),
}),
}
);
// Step 3: Grant Custom Role to Custom Service Account on the Project
new ProjectIamMember(this, "ProjectIamMember", {
project: app.projectId,
role: `projects/${app.projectId}/roles/${this.functionCustomRole.roleId}`,
member: `serviceAccount:${this.functionServiceAccount.email}`,
});
// Step 4: Create the Cloud Function with Custom Service Account
// Create the Cloud Function with Custom Service Account
this.function = new CloudfunctionsFunction(this, "DefaultFunction", {
name: ResourceNames.generateName(this, FUNCTION_NAME_OPTS),
description: "This function was created by Wing",
Expand Down Expand Up @@ -290,12 +269,6 @@ export class Function extends cloud.Function {
);
}

public addPermissions(permissions: string[]): void {
permissions.forEach((permission) => {
this.permissions.add(permission);
});
}

public onLift(host: IInflightHost, ops: string[]): void {
if (!(host instanceof Function)) {
throw new Error(
Expand All @@ -304,7 +277,7 @@ export class Function extends cloud.Function {
}

if (ops.includes(cloud.FunctionInflightMethods.INVOKE)) {
host.addPermissions(["cloudfunctions.functions.invoke"]);
this._addPermissionToInvoke(host.serviceAccountEmail);
}

const { region, projectId } = App.of(this) as App;
Expand All @@ -320,16 +293,43 @@ export class Function extends cloud.Function {
* @param serviceAccount The service account to grant invoke permissions to.
* @internal
*/
public _addPermissionToInvoke(serviceAccount: ServiceAccount): void {
const hash = Fn.sha256(serviceAccount.email).slice(-8);
public _addPermissionToInvoke(serviceAccountEmail: string): void {
const hash = Fn.sha256(serviceAccountEmail).slice(-8);

new CloudfunctionsFunctionIamMember(this, `invoker-permission-${hash}`, {
project: this.function.project,
region: this.function.region,
cloudFunction: this.function.name,
role: "roles/cloudfunctions.invoker",
member: `serviceAccount:${serviceAccount.email}`,
member: `serviceAccount:${serviceAccountEmail}`,
});
}

public _addTokenCreator() {
const tokenCreatorRole = "roles/iam.serviceAccountTokenCreator";
if (this.roles?.has(tokenCreatorRole)) {
return;
}
const hash = Fn.sha256(this.serviceAccountEmail).slice(-8);
new ServiceAccountIamMember(this, `service-iam-member-${hash}`, {
serviceAccountId: this.functionServiceAccount.name,
role: "roles/iam.serviceAccountTokenCreator",
member: `serviceAccount:${this.functionServiceAccount.email}`,
});
this.roles?.add(tokenCreatorRole);
}

public _addProjectIamMember(role: string) {
if (this.roles?.has(role)) {
return;
}
const hash = Fn.sha256(this.functionServiceAccount.email).slice(-8);
new ProjectIamMember(this, `project-iam-member-${hash}`, {
project: (App.of(this) as any).projectId,
member: `serviceAccount:${this.functionServiceAccount.email}`,
role,
});
this.roles?.add(role);
}

/** @internal */
Expand Down
73 changes: 20 additions & 53 deletions libs/wingsdk/src/target-tf-gcp/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,42 @@
import * as cloud from "../cloud";

export function calculateBucketPermissions(ops: string[]): string[] {
export function createBucketPermissions(ops: string[]): string[] {
const permissions: string[] = [];

if (
ops.includes(cloud.BucketInflightMethods.GET) ||
ops.includes(cloud.BucketInflightMethods.GET_JSON) ||
ops.includes(cloud.BucketInflightMethods.TRY_GET) ||
ops.includes(cloud.BucketInflightMethods.TRY_GET_JSON) ||
ops.includes(cloud.BucketInflightMethods.TRY_DELETE) ||
ops.includes(cloud.BucketInflightMethods.EXISTS) ||
ops.includes(cloud.BucketInflightMethods.METADATA) ||
ops.includes(cloud.BucketInflightMethods.PUBLIC_URL) ||
ops.includes(cloud.BucketInflightMethods.COPY) ||
ops.includes(cloud.BucketInflightMethods.RENAME) ||
ops.includes(cloud.BucketInflightMethods.SIGNED_URL)
) {
permissions.push("storage.objects.get");
if (ops.includes(cloud.BucketInflightMethods.PUBLIC_URL)) {
permissions.push("roles/storage.insightsCollectorService");
}

if (
ops.includes(cloud.BucketInflightMethods.PUT) ||
ops.includes(cloud.BucketInflightMethods.PUT_JSON) ||
ops.includes(cloud.BucketInflightMethods.DELETE) ||
ops.includes(cloud.BucketInflightMethods.TRY_DELETE) ||
ops.includes(cloud.BucketInflightMethods.COPY) ||
ops.includes(cloud.BucketInflightMethods.RENAME) ||
ops.includes(cloud.BucketInflightMethods.SIGNED_URL)
ops.includes(cloud.BucketInflightMethods.RENAME)
) {
permissions.push("storage.objects.create");
permissions.push("roles/storage.objectUser");
return permissions;
}

if (
ops.includes(cloud.BucketInflightMethods.DELETE) ||
ops.includes(cloud.BucketInflightMethods.TRY_DELETE) ||
ops.includes(cloud.BucketInflightMethods.PUT) ||
ops.includes(cloud.BucketInflightMethods.PUT_JSON) ||
ops.includes(cloud.BucketInflightMethods.COPY) ||
ops.includes(cloud.BucketInflightMethods.RENAME) ||
ops.includes(cloud.BucketInflightMethods.SIGNED_URL)
) {
permissions.push("storage.objects.delete");
permissions.push("roles/storage.objectCreator");
}

if (
ops.includes(cloud.BucketInflightMethods.GET) ||
ops.includes(cloud.BucketInflightMethods.GET_JSON) ||
ops.includes(cloud.BucketInflightMethods.TRY_GET) ||
ops.includes(cloud.BucketInflightMethods.TRY_GET_JSON) ||
ops.includes(cloud.BucketInflightMethods.EXISTS) ||
ops.includes(cloud.BucketInflightMethods.METADATA) ||
ops.includes(cloud.BucketInflightMethods.PUBLIC_URL) ||
ops.includes(cloud.BucketInflightMethods.LIST) ||
ops.includes(cloud.BucketInflightMethods.SIGNED_URL)
) {
permissions.push("storage.objects.list");
}

if (ops.includes(cloud.BucketInflightMethods.PUBLIC_URL)) {
permissions.push("storage.buckets.get");
}

if (ops.includes(cloud.BucketInflightMethods.SIGNED_URL)) {
permissions.push("iam.serviceAccounts.signBlob");
permissions.push("roles/storage.objectViewer");
}

return permissions;
Expand All @@ -62,29 +45,13 @@ export function calculateBucketPermissions(ops: string[]): string[] {
export function calculateCounterPermissions(ops: string[]): string[] {
const permissions: string[] = [];

if (
ops.includes(cloud.CounterInflightMethods.PEEK) ||
ops.includes(cloud.CounterInflightMethods.INC) ||
ops.includes(cloud.CounterInflightMethods.DEC)
) {
permissions.push("datastore.entities.get");
}

if (
ops.includes(cloud.CounterInflightMethods.PEEK) ||
ops.includes(cloud.CounterInflightMethods.INC) ||
ops.includes(cloud.CounterInflightMethods.DEC) ||
ops.includes(cloud.CounterInflightMethods.SET)
) {
permissions.push("datastore.entities.create");
}

if (
ops.includes(cloud.CounterInflightMethods.INC) ||
ops.includes(cloud.CounterInflightMethods.DEC) ||
ops.includes(cloud.CounterInflightMethods.SET)
ops.includes(cloud.CounterInflightMethods.SET) ||
ops.includes(cloud.CounterInflightMethods.PEEK)
) {
permissions.push("datastore.entities.update");
permissions.push("roles/datastore.user");
}

return permissions;
Expand Down
2 changes: 1 addition & 1 deletion libs/wingsdk/src/target-tf-gcp/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class Schedule extends cloud.Schedule {
);

// allow scheduler service account to invoke cron function
cronFunction._addPermissionToInvoke(schedulerServiceAccount);
cronFunction._addPermissionToInvoke(schedulerServiceAccount.email);

// create scheduler
new CloudSchedulerJob(this, "Scheduler", {
Expand Down

0 comments on commit 5d3f769

Please sign in to comment.