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;
+ }
+}