diff --git a/components/dashboard/src/projects/Prebuild.tsx b/components/dashboard/src/projects/Prebuild.tsx index 60eff4c659581d..4214a72d3ee855 100644 --- a/components/dashboard/src/projects/Prebuild.tsx +++ b/components/dashboard/src/projects/Prebuild.tsx @@ -181,7 +181,9 @@ export default function () { ) : prebuild?.status === "available" ? ( diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts index ab0fa2d8f92207..86804d66a438c7 100644 --- a/components/gitpod-protocol/src/protocol.ts +++ b/components/gitpod-protocol/src/protocol.ts @@ -1200,6 +1200,16 @@ export namespace AdditionalContentContext { } } +export interface OpenPrebuildContext extends WorkspaceContext { + openPrebuilID: string; +} + +export namespace OpenPrebuildContext { + export function is(ctx: any): ctx is OpenPrebuildContext { + return "openPrebuildID" in ctx; + } +} + export interface CommitContext extends WorkspaceContext, GitCheckoutInfo { /** @deprecated Moved to .repository.cloneUrl, left here for backwards-compatibility for old workspace contextes in the DB */ cloneUrl?: string; diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts index f9babdfbd78735..667245abc94609 100644 --- a/components/server/ee/src/workspace/gitpod-server-impl.ts +++ b/components/server/ee/src/workspace/gitpod-server-impl.ts @@ -47,6 +47,7 @@ import { TeamMemberRole, WORKSPACE_TIMEOUT_DEFAULT_SHORT, PrebuildEvent, + OpenPrebuildContext, } from "@gitpod/gitpod-protocol"; import { ResponseError } from "vscode-jsonrpc"; import { @@ -963,9 +964,19 @@ export class GitpodServerEEImpl extends GitpodServerImpl { const logCtx: LogContext = { userId: user.id }; const cloneUrl = context.repository.cloneUrl; - const prebuiltWorkspace = await this.workspaceDb - .trace(ctx) - .findPrebuiltWorkspaceByCommit(cloneUrl, commitSHAs); + let prebuiltWorkspace: PrebuiltWorkspace | undefined; + if (OpenPrebuildContext.is(context)) { + prebuiltWorkspace = await this.workspaceDb.trace(ctx).findPrebuildByID(context.openPrebuilID); + if (prebuiltWorkspace?.cloneURL !== cloneUrl) { + // prevent users from opening arbitrary prebuilds this way - they must match the clone URL so that the resource guards are correct. + return; + } + } else { + prebuiltWorkspace = await this.workspaceDb + .trace(ctx) + .findPrebuiltWorkspaceByCommit(cloneUrl, commitSHAs); + } + const logPayload = { mode, cloneUrl, commit: commitSHAs, prebuiltWorkspace }; log.debug(logCtx, "Looking for prebuilt workspace: ", logPayload); if (!prebuiltWorkspace) { @@ -994,7 +1005,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl { const makeResult = (instanceID: string): WorkspaceCreationResult => { return { runningWorkspacePrebuild: { - prebuildID: prebuiltWorkspace.id, + prebuildID: prebuiltWorkspace!.id, workspaceID, instanceID, starting: "queued", diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts index f0fa281810528d..cb6e1a1fb01d0a 100644 --- a/components/server/src/container-module.ts +++ b/components/server/src/container-module.ts @@ -113,6 +113,7 @@ import { LivenessController } from "./liveness/liveness-controller"; import { IDEServiceClient, IDEServiceDefinition } from "@gitpod/ide-service-api/lib/ide.pb"; import { prometheusClientMiddleware } from "@gitpod/gitpod-protocol/lib/util/nice-grpc"; import { UsageService } from "./user/usage-service"; +import { OpenPrebuildPrefixContextParser } from "./workspace/open-prebuild-prefix-context-parser"; export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => { bind(Config).toConstantValue(ConfigFile.fromFile()); @@ -189,6 +190,7 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(IPrefixContextParser).to(EnvvarPrefixParser).inSingletonScope(); bind(IPrefixContextParser).to(ImageBuildPrefixContextParser).inSingletonScope(); bind(IPrefixContextParser).to(AdditionalContentPrefixContextParser).inSingletonScope(); + bind(IPrefixContextParser).to(OpenPrebuildPrefixContextParser).inSingletonScope(); bind(GitTokenScopeGuesser).toSelf().inSingletonScope(); bind(GitTokenValidator).toSelf().inSingletonScope(); diff --git a/components/server/src/workspace/open-prebuild-prefix-context-parser.ts b/components/server/src/workspace/open-prebuild-prefix-context-parser.ts new file mode 100644 index 00000000000000..89131e4f399429 --- /dev/null +++ b/components/server/src/workspace/open-prebuild-prefix-context-parser.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2021 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 { User, WorkspaceContext } from "@gitpod/gitpod-protocol"; +import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; +import { OpenPrebuildContext } from "@gitpod/gitpod-protocol/src/protocol"; +import { inject, injectable } from "inversify"; +import { Config } from "../config"; +import { IPrefixContextParser } from "./context-parser"; + +@injectable() +export class OpenPrebuildPrefixContextParser implements IPrefixContextParser { + @inject(Config) protected readonly config: Config; + static PREFIX = /^\/?open-prebuild\/([^\/]*)\//; + + findPrefix(user: User, context: string): string | undefined { + const result = OpenPrebuildPrefixContextParser.PREFIX.exec(context); + if (!result) { + return undefined; + } + return result[0]; + } + + public async handle(user: User, prefix: string, context: WorkspaceContext): Promise { + const match = OpenPrebuildPrefixContextParser.PREFIX.exec(prefix); + if (!match) { + log.error("Could not parse prefix " + prefix); + return context; + } + + (context as OpenPrebuildContext).openPrebuilID = match[1]; + return context; + } +}