From 8077c2e4dd9dff1a1e452717ad6c633cfc0e09a2 Mon Sep 17 00:00:00 2001 From: Milan Pavlik Date: Fri, 6 Jan 2023 09:02:56 +0000 Subject: [PATCH] [openfga] Setup component --- components/openfga/model.openfga | 20 +++ components/openfga/scripts.hack | 163 ++++++++++++++++++ components/server/package.json | 1 + components/server/src/perms.ts | 142 +++++++++++++++ .../src/workspace/gitpod-server-impl.ts | 38 +++- .../custom-pull-repository/output.golden | 8 + .../render/customization/output.golden | 8 + .../render/external-registry/output.golden | 8 + .../testdata/render/http-proxy/output.golden | 8 + .../testdata/render/kind-meta/output.golden | 8 + .../testdata/render/kind-webapp/output.golden | 8 + .../cmd/testdata/render/minimal/output.golden | 8 + .../testdata/render/shortname/output.golden | 8 + .../statefulset-customization/output.golden | 8 + .../use-pod-security-policies/output.golden | 8 + .../render/vsxproxy-pvc/output.golden | 8 + .../workspace-requests-limits/output.golden | 8 + .../incluster/init/02-recreate-gitpod-db.sql | 8 + .../pkg/components/openfga/deployment.go | 11 ++ yarn.lock | 20 +++ 20 files changed, 496 insertions(+), 3 deletions(-) create mode 100644 components/openfga/model.openfga create mode 100644 components/openfga/scripts.hack create mode 100644 components/server/src/perms.ts diff --git a/components/openfga/model.openfga b/components/openfga/model.openfga new file mode 100644 index 00000000000000..12bf1337f1f496 --- /dev/null +++ b/components/openfga/model.openfga @@ -0,0 +1,20 @@ +model + schema 1.1 + +type user +type team + relations + define owner: [user] + define member: [user] or owner + + define grant_owner: owner + define list_members: owner or member + define add_members: owner + define remove_members: owner + define delete_team: owner + + define project_creator: owner or member + +type project + relations + define maintainer: [user,team#member] diff --git a/components/openfga/scripts.hack b/components/openfga/scripts.hack new file mode 100644 index 00000000000000..1dbcfcebd5f51c --- /dev/null +++ b/components/openfga/scripts.hack @@ -0,0 +1,163 @@ +curl -X POST "openfga:8080/stores/01GP4CRKXESH1JE5E0SNHMZYG1/authorization-models" \ + -H "content-type: application/json" \ + -d '{ + "type_definitions": [ + { + "type": "user", + "relations": {} + }, + { + "type": "team", + "relations": { + "owner": { + "this": {} + }, + "member": { + "union": { + "child": [ + { + "this": {} + }, + { + "computedUserset": { + "object": "", + "relation": "owner" + } + } + ] + } + }, + "grant_owner": { + "computedUserset": { + "object": "", + "relation": "owner" + } + }, + "list_members": { + "union": { + "child": [ + { + "computedUserset": { + "object": "", + "relation": "owner" + } + }, + { + "computedUserset": { + "object": "", + "relation": "member" + } + } + ] + } + }, + "add_members": { + "computedUserset": { + "object": "", + "relation": "owner" + } + }, + "remove_members": { + "computedUserset": { + "object": "", + "relation": "owner" + } + }, + "delete_team": { + "computedUserset": { + "object": "", + "relation": "owner" + } + }, + "project_creator": { + "union": { + "child": [ + { + "computedUserset": { + "object": "", + "relation": "owner" + } + }, + { + "computedUserset": { + "object": "", + "relation": "member" + } + } + ] + } + } + }, + "metadata": { + "relations": { + "owner": { + "directly_related_user_types": [ + { + "type": "user" + } + ] + }, + "member": { + "directly_related_user_types": [ + { + "type": "user" + } + ] + }, + "grant_owner": { + "directly_related_user_types": [] + }, + "list_members": { + "directly_related_user_types": [] + }, + "add_members": { + "directly_related_user_types": [] + }, + "remove_members": { + "directly_related_user_types": [] + }, + "delete_team": { + "directly_related_user_types": [] + }, + "project_creator": { + "directly_related_user_types": [] + } + } + } + }, + { + "type": "project", + "relations": { + "maintainer": { + "this": {} + } + }, + "metadata": { + "relations": { + "maintainer": { + "directly_related_user_types": [ + { + "type": "user" + }, + { + "type": "team", + "relation": "member" + } + ] + } + } + } + } + ], + "schema_version": "1.1" +}' + +curl -X POST "openfga:8080/stores/01GP4CRKXESH1JE5E0SNHMZYG1/check" \ + -H "content-type: application/json" \ + -d '{"tuple_key":{"user":"user:milan","relation":"maintainer","object":"project:project1"}}' + +# Response: {"allowed":true} +b23f24d7-47f7-4366-a642-ce46b61b499e + +curl -X GET "localhost:8080/stores/01GP4CRKXESH1JE5E0SNHMZYG1/changes" \ + -H "content-type: application/json" diff --git a/components/server/package.json b/components/server/package.json index 20f84ddb2d3ffa..75c5b64168ba08 100644 --- a/components/server/package.json +++ b/components/server/package.json @@ -43,6 +43,7 @@ "@improbable-eng/grpc-web-node-http-transport": "^0.14.0", "@jmondi/oauth2-server": "^2.6.1", "@octokit/rest": "18.6.1", + "@openfga/sdk": "^0.2.0", "@probot/get-private-key": "^1.1.1", "@types/jaeger-client": "^3.18.3", "amqplib": "^0.8.0", diff --git a/components/server/src/perms.ts b/components/server/src/perms.ts new file mode 100644 index 00000000000000..66fddfd947456a --- /dev/null +++ b/components/server/src/perms.ts @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2023 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License.AGPL.txt in the project root for license information. + */ + +import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; +import { OpenFgaApi, TupleKey } from "@openfga/sdk"; +import { ResponseError } from "vscode-jsonrpc"; + +export const OpenFGA = new OpenFgaApi({ + apiScheme: "http", // Optional. Can be "http" or "https". Defaults to "https" + apiHost: "openfga:8080", // required, define without the scheme (e.g. api.openfga.example instead of https://api.openfga.example) + storeId: "01GP4CRKXESH1JE5E0SNHMZYG1", +}); + +function tup(user: string, relation: string, object: string): TupleKey { + return { user, relation, object }; +} + +function user(id: string): string { + return `user:${id}`; +} + +function team(id: string): string { + return `team:${id}`; +} + +function proj(id: string): string { + return `project:${id}`; +} + +export async function isTeamOwner(userID: string, teamID: string): Promise { + return ( + ( + await OpenFGA.check({ + tuple_key: tup(user(userID), "owner", team(teamID)), + }) + ).allowed || false + ); +} + +export async function isTeamMember(userID: string, teamID: string): Promise { + return ( + ( + await OpenFGA.check({ + tuple_key: tup(user(userID), "member", team(teamID)), + }) + ).allowed || false + ); +} + +export async function grantTeamOwner(userID: string, teamID: string) { + const deletes: TupleKey[] = []; + + const isMember = await isTeamMember(userID, teamID); + if (isMember) { + deletes.push(tup(user(userID), "member", team(teamID))); + } + + return await OpenFGA.write({ + writes: { + tuple_keys: [tup(user(userID), "owner", team(teamID))], + }, + deletes: { + tuple_keys: deletes, + }, + }); +} + +export async function grantTeamMember(userID: string, teamID: string) { + const deletes: TupleKey[] = []; + + const isMember = await isTeamOwner(userID, teamID); + if (isMember) { + deletes.push(tup(user(userID), "owner", team(teamID))); + } + await OpenFGA.write({ + writes: { + tuple_keys: [tup(user(userID), "member", team(teamID))], + }, + deletes: { + tuple_keys: deletes, + }, + }); +} + +export async function removeUserFromTeam(userID: string, teamID: string) { + return OpenFGA.write({ + deletes: { + tuple_keys: [tup(user(userID), "owner", team(teamID)), tup(user(userID), "member", team(teamID))], + }, + }); +} + +export async function canCreateProject(userID: string, teamID: string) { + const response = await OpenFGA.check({ + tuple_key: tup(user(userID), "member", team(teamID)), + }); + + if (!response.allowed) { + throw newPermissionDenied(`user ${userID} is not allowed to create projects for team ${teamID}`); + } + return response.allowed || false; +} + +export async function canDeleteProject(userID: string, projectID: string) { + const response = await OpenFGA.check({ + tuple_key: tup(user(userID), "maintainer", proj(projectID)), + }); + + if (!response.allowed) { + throw newPermissionDenied(`user ${userID} is not allowed to delete project ${projectID}`); + } + return response.allowed || false; +} + +export async function canAccessProject(userID: string, projectID: string): Promise { + const response = await OpenFGA.check({ + tuple_key: tup(user(userID), "maintainer", proj(projectID)), + }); + + return response.allowed || false; +} + +export async function grantTeamProjectMaintainer(teamID: string, projectID: string) { + return OpenFGA.write({ + writes: { + tuple_keys: [ + { + user: `team:${teamID}#member`, + relation: "maintainer", + object: proj(projectID), + }, + ], + }, + }); +} + +function newPermissionDenied(msg: string): ResponseError { + return new ResponseError(ErrorCodes.PERMISSION_DENIED, msg); +} diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index fe1deb3f7b359f..f96dd0d238c745 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -182,6 +182,7 @@ import * as grpc from "@grpc/grpc-js"; import { CachingBlobServiceClientProvider } from "../util/content-service-sugar"; import { CostCenterJSON } from "@gitpod/gitpod-protocol/lib/usage"; import { createCookielessId, maskIp } from "../analytics"; +import * as perms from "../perms"; // shortcut export const traceWI = (ctx: TraceContext, wi: Omit) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager @@ -2081,6 +2082,9 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { // Note: this operation is per-user only, hence needs no resource guard const user = this.checkAndBlockUser("createTeam"); const team = await this.teamDB.createTeam(user.id, name); + + await perms.grantTeamOwner(user.id, team.id); + const invite = await this.getGenericInvite(ctx, team.id); ctx.span?.setTag("teamId", team.id); this.analytics.track({ @@ -2108,6 +2112,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { } ctx.span?.setTag("teamId", invite.teamId); const result = await this.teamDB.addMemberToTeam(user.id, invite.teamId); + await perms.grantTeamMember(user.id, invite.teamId); const team = await this.teamDB.findTeamById(invite.teamId); if (result !== "already_member") { await this.onTeamMemberAdded(user.id, invite.teamId); @@ -2148,7 +2153,13 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { this.checkAndBlockUser("setTeamMemberRole"); await this.guardTeamOperation(teamId, "update"); + await this.teamDB.setTeamMemberRole(userId, teamId, role); + if (role === "member") { + await perms.grantTeamMember(userId, teamId); + } else if (role === "owner") { + await perms.grantTeamOwner(userId, teamId); + } } public async removeTeamMember(ctx: TraceContext, teamId: string, userId: string): Promise { @@ -2170,6 +2181,9 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { throw new Error(`Could not find membership for user '${userId}' in team '${teamId}'`); } await this.teamDB.removeMemberFromTeam(userId, teamId); + + await perms.removeUserFromTeam(userId, teamId); + await this.onTeamMemberRemoved(userId, teamId, membership.id); this.analytics.track({ userId: user.id, @@ -2250,9 +2264,14 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { } else { // Anyone who can read a team's information (i.e. any team member) can create a new project. await this.guardTeamOperation(params.teamId, "get"); + + await perms.canCreateProject(user.id, params.teamId!); } - return this.projectsService.createProject(params, user); + const project = await this.projectsService.createProject(params, user); + await perms.grantTeamProjectMaintainer(params.teamId!, project.id); + + return project; } public async deleteProject(ctx: TraceContext, projectId: string): Promise { @@ -2260,6 +2279,9 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { const user = this.checkUser("deleteProject"); await this.guardProjectOperation(user, projectId, "delete"); + + await perms.canDeleteProject(user.id, projectId); + this.analytics.track({ userId: user.id, event: "project_deleted", @@ -2305,10 +2327,20 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { public async getTeamProjects(ctx: TraceContext, teamId: string): Promise { traceAPIParams(ctx, { teamId }); - this.checkUser("getTeamProjects"); + const user = this.checkUser("getTeamProjects"); await this.guardTeamOperation(teamId, "get"); - return this.projectsService.getTeamProjects(teamId); + const projects = await this.projectsService.getTeamProjects(teamId); + + const permitted: Project[] = []; + for (var p of projects) { + const allowed = await perms.canAccessProject(user.id, p.id); + if (allowed) { + permitted.push(p); + } + } + + return permitted; } public async getUserProjects(ctx: TraceContext): Promise { diff --git a/install/installer/cmd/testdata/render/custom-pull-repository/output.golden b/install/installer/cmd/testdata/render/custom-pull-repository/output.golden index e09bc59faffcf6..8a17b50d3ec3b1 100644 --- a/install/installer/cmd/testdata/render/custom-pull-repository/output.golden +++ b/install/installer/cmd/testdata/render/custom-pull-repository/output.golden @@ -1505,6 +1505,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/customization/output.golden b/install/installer/cmd/testdata/render/customization/output.golden index 9b9d0831b19ca8..671f1cf69998ca 100644 --- a/install/installer/cmd/testdata/render/customization/output.golden +++ b/install/installer/cmd/testdata/render/customization/output.golden @@ -1640,6 +1640,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/external-registry/output.golden b/install/installer/cmd/testdata/render/external-registry/output.golden index 811dab8ab88faa..fb4efccf07cc33 100644 --- a/install/installer/cmd/testdata/render/external-registry/output.golden +++ b/install/installer/cmd/testdata/render/external-registry/output.golden @@ -1434,6 +1434,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/http-proxy/output.golden b/install/installer/cmd/testdata/render/http-proxy/output.golden index 9c06b47c2f0f1e..13123c677e15e6 100644 --- a/install/installer/cmd/testdata/render/http-proxy/output.golden +++ b/install/installer/cmd/testdata/render/http-proxy/output.golden @@ -1505,6 +1505,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/kind-meta/output.golden b/install/installer/cmd/testdata/render/kind-meta/output.golden index cf06e9e1437c9c..fffd04966b8c7d 100644 --- a/install/installer/cmd/testdata/render/kind-meta/output.golden +++ b/install/installer/cmd/testdata/render/kind-meta/output.golden @@ -1052,6 +1052,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/kind-webapp/output.golden b/install/installer/cmd/testdata/render/kind-webapp/output.golden index 58564b922520aa..e4a004e14eebea 100644 --- a/install/installer/cmd/testdata/render/kind-webapp/output.golden +++ b/install/installer/cmd/testdata/render/kind-webapp/output.golden @@ -786,6 +786,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/minimal/output.golden b/install/installer/cmd/testdata/render/minimal/output.golden index 47d3ec532f9f17..8cdfc44e9bc9c7 100644 --- a/install/installer/cmd/testdata/render/minimal/output.golden +++ b/install/installer/cmd/testdata/render/minimal/output.golden @@ -1505,6 +1505,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/shortname/output.golden b/install/installer/cmd/testdata/render/shortname/output.golden index d87bf862b7804b..0c8ecbe8e75a95 100644 --- a/install/installer/cmd/testdata/render/shortname/output.golden +++ b/install/installer/cmd/testdata/render/shortname/output.golden @@ -1505,6 +1505,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/statefulset-customization/output.golden b/install/installer/cmd/testdata/render/statefulset-customization/output.golden index 5e4ccb536262a8..708539e756109a 100644 --- a/install/installer/cmd/testdata/render/statefulset-customization/output.golden +++ b/install/installer/cmd/testdata/render/statefulset-customization/output.golden @@ -1505,6 +1505,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/use-pod-security-policies/output.golden b/install/installer/cmd/testdata/render/use-pod-security-policies/output.golden index 888431e6484283..5d621d7cc8a68a 100644 --- a/install/installer/cmd/testdata/render/use-pod-security-policies/output.golden +++ b/install/installer/cmd/testdata/render/use-pod-security-policies/output.golden @@ -1727,6 +1727,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/vsxproxy-pvc/output.golden b/install/installer/cmd/testdata/render/vsxproxy-pvc/output.golden index 2a466228ac6066..83eb8fcff01e92 100644 --- a/install/installer/cmd/testdata/render/vsxproxy-pvc/output.golden +++ b/install/installer/cmd/testdata/render/vsxproxy-pvc/output.golden @@ -1505,6 +1505,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/cmd/testdata/render/workspace-requests-limits/output.golden b/install/installer/cmd/testdata/render/workspace-requests-limits/output.golden index 91f2fad194d143..ef89127d200f6b 100644 --- a/install/installer/cmd/testdata/render/workspace-requests-limits/output.golden +++ b/install/installer/cmd/testdata/render/workspace-requests-limits/output.golden @@ -1505,6 +1505,14 @@ data: @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + + SET + @statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); + PREPARE statement FROM @statementStr; + EXECUTE statement; + + -- Grant privileges + GRANT ALL ON `openfga`.* TO "gitpod"@"%"; tuneMysql.sql: SET GLOBAL innodb_lru_scan_depth=256; kind: ConfigMap metadata: diff --git a/install/installer/pkg/components/database/incluster/init/02-recreate-gitpod-db.sql b/install/installer/pkg/components/database/incluster/init/02-recreate-gitpod-db.sql index a4737921686e57..dbf402b5ca9383 100644 --- a/install/installer/pkg/components/database/incluster/init/02-recreate-gitpod-db.sql +++ b/install/installer/pkg/components/database/incluster/init/02-recreate-gitpod-db.sql @@ -16,3 +16,11 @@ SET @statementStr = CONCAT('CREATE DATABASE ', @gitpodDB, ' CHARSET utf8mb4'); PREPARE statement FROM @statementStr; EXECUTE statement; + +SET +@statementStr = CONCAT('CREATE DATABASE IF NOT EXISTS ', 'openfga', ' CHARSET utf8mb4'); +PREPARE statement FROM @statementStr; +EXECUTE statement; + +-- Grant privileges +GRANT ALL ON `openfga`.* TO "__GITPOD_USERNAME__"@"%"; diff --git a/install/installer/pkg/components/openfga/deployment.go b/install/installer/pkg/components/openfga/deployment.go index 34c8263ec11d40..479300b504e023 100644 --- a/install/installer/pkg/components/openfga/deployment.go +++ b/install/installer/pkg/components/openfga/deployment.go @@ -62,6 +62,17 @@ func deployment(ctx *common.RenderContext) ([]runtime.Object, error) { }, Env: common.CustomizeEnvvar(ctx, Component, common.MergeEnv( common.DefaultEnv(&ctx.Config), + common.DatabaseEnv(&ctx.Config), + []corev1.EnvVar{ + { + Name: "OPENFGA_DATASTORE_ENGINE", + Value: "mysql", + }, + { + Name: "OPENFGA_DATASTORE_URI", + Value: "$(DB_USERNAME):$(DB_PASSWORD)@tcp($(DB_HOST):$(DB_PORT))/openfga?parseTime=true", + }, + }, )), Ports: []corev1.ContainerPort{ { diff --git a/yarn.lock b/yarn.lock index 9968e95a5d61de..59381f644a664a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2087,6 +2087,13 @@ "@octokit/webhooks-types" "4.12.0" aggregate-error "^3.1.0" +"@openfga/sdk@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@openfga/sdk/-/sdk-0.2.0.tgz#a64bee305500500128b57abbf747ca1b251d2a70" + integrity sha512-gdzf8V2wPU+qZRnD17L66xTGY9SSD33EH4e6ZCinA41hJn/36RPovB43uPVmt5dZlg18pqLP5roKSKzrZoBRjg== + dependencies: + axios "^0.27.2" + "@pmmmwh/react-refresh-webpack-plugin@0.4.3": version "0.4.3" resolved "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.3.tgz" @@ -4483,6 +4490,14 @@ axios@^0.26.1: dependencies: follow-redirects "^1.14.8" +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz" @@ -8533,6 +8548,11 @@ follow-redirects@^1.14.8: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== +follow-redirects@^1.14.9: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz"