Skip to content

Commit 1c5792a

Browse files
AlexTugarevroboquat
authored andcommitted
[server] blocklist repositories
1 parent d1e9128 commit 1c5792a

File tree

4 files changed

+58
-8
lines changed

4 files changed

+58
-8
lines changed

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
232232

233233
const result = await this.eligibilityService.mayStartWorkspace(user, new Date(), runningInstances);
234234
if (!result.enoughCredits) {
235-
throw new ResponseError(ErrorCodes.NOT_ENOUGH_CREDIT, `Not enough monthly workspace hours. Please upgrade your account to get more hours for your workspaces.`);
235+
throw new ResponseError(
236+
ErrorCodes.NOT_ENOUGH_CREDIT,
237+
`Not enough monthly workspace hours. Please upgrade your account to get more hours for your workspaces.`,
238+
);
236239
}
237240
if (!!result.hitParallelWorkspaceLimit) {
238241
throw new ResponseError(
@@ -565,14 +568,13 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
565568

566569
await this.guardAdminAccess("adminBlockUser", { req }, Permission.ADMIN_USERS);
567570

568-
const target = await this.userDB.findUserById(req.id);
569-
if (!target) {
571+
let targetUser;
572+
try {
573+
targetUser = await this.userService.blockUser(req.id, req.blocked);
574+
} catch (error) {
570575
throw new ResponseError(ErrorCodes.NOT_FOUND, "not found");
571576
}
572577

573-
target.blocked = !!req.blocked;
574-
await this.userDB.storeUser(target);
575-
576578
const workspaceDb = this.workspaceDb.trace(ctx);
577579
const workspaces = await workspaceDb.findWorkspacesByUser(req.id);
578580
const isDefined = <T>(x: T | undefined): x is T => x !== undefined;
@@ -584,7 +586,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
584586

585587
// For some reason, returning the result of `this.userDB.storeUser(target)` does not work. The response never arrives the caller.
586588
// Returning `target` instead (which should be equivalent).
587-
return this.censorUser(target);
589+
return this.censorUser(targetUser);
588590
}
589591

590592
async adminDeleteUser(ctx: TraceContext, userId: string): Promise<void> {

components/server/src/config.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
1818
import { filePathTelepresenceAware } from "@gitpod/gitpod-protocol/lib/env";
1919

2020
export const Config = Symbol("Config");
21-
export type Config = Omit<ConfigSerialized, "hostUrl" | "chargebeeProviderOptionsFile" | "licenseFile"> & {
21+
export type Config = Omit<
22+
ConfigSerialized,
23+
"blockedRepositories" | "hostUrl" | "chargebeeProviderOptionsFile" | "licenseFile"
24+
> & {
2225
hostUrl: GitpodHostUrl;
2326
workspaceDefaults: WorkspaceDefaults;
2427
chargebeeProviderOptions?: ChargebeeProviderOptions;
2528
builtinAuthProvidersConfigured: boolean;
29+
blockedRepositories: { urlRegExp: RegExp; blockUser: boolean }[];
2630
};
2731

2832
export interface WorkspaceDefaults {
@@ -152,6 +156,12 @@ export interface ConfigSerialized {
152156
* Key '*' specifies the default rate limit for a cloneURL, unless overriden by a specific cloneURL.
153157
*/
154158
prebuildLimiter: { [cloneURL: string]: number } & { "*": number };
159+
160+
/**
161+
* List of repositories not allowed to be used for workspace starts.
162+
* `blockUser` attribute to control handling of the user's account.
163+
*/
164+
blockedRepositories?: { urlRegExp: string; blockUser: boolean }[];
155165
}
156166

157167
export namespace ConfigFile {
@@ -201,6 +211,15 @@ export namespace ConfigFile {
201211
if (licenseFile) {
202212
license = fs.readFileSync(filePathTelepresenceAware(licenseFile), "utf-8");
203213
}
214+
const blockedRepositories: { urlRegExp: RegExp; blockUser: boolean }[] = [];
215+
if (config.blockedRepositories) {
216+
for (const { blockUser, urlRegExp } of config.blockedRepositories) {
217+
blockedRepositories.push({
218+
blockUser,
219+
urlRegExp: new RegExp(urlRegExp),
220+
});
221+
}
222+
}
204223
return {
205224
...config,
206225
hostUrl,
@@ -214,6 +233,7 @@ export namespace ConfigFile {
214233
? new Date(config.workspaceGarbageCollection.startDate).getTime()
215234
: Date.now(),
216235
},
236+
blockedRepositories,
217237
};
218238
}
219239
}

components/server/src/user/user-service.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,16 @@ export class UserService {
269269
return false;
270270
}
271271

272+
async blockUser(targetUserId: string, block: boolean): Promise<User> {
273+
const target = await this.userDb.findUserById(targetUserId);
274+
if (!target) {
275+
throw new Error("Not found.");
276+
}
277+
278+
target.blocked = !!block;
279+
return await this.userDb.storeUser(target);
280+
}
281+
272282
async findUserForLogin(params: { candidate: Identity }) {
273283
let user = await this.userDb.findUserByIdentity(params.candidate);
274284
return user;

components/server/src/workspace/workspace-starter.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ export class WorkspaceStarter {
219219

220220
options = options || {};
221221
try {
222+
await this.checkBlockedRepository(user, workspace.contextURL);
223+
222224
// Some workspaces do not have an image source.
223225
// Workspaces without image source are not only legacy, but also happened due to what looks like a bug.
224226
// Whenever a such a workspace is re-started we'll give it an image source now. This is in line with how this thing used to work.
@@ -332,6 +334,22 @@ export class WorkspaceStarter {
332334
}
333335
}
334336

337+
protected async checkBlockedRepository(user: User, contextURL: string) {
338+
const hit = this.config.blockedRepositories.find((r) => !!contextURL && r.urlRegExp.test(contextURL));
339+
if (!hit) {
340+
return;
341+
}
342+
if (hit.blockUser) {
343+
try {
344+
await this.userService.blockUser(user.id, true);
345+
log.info({ userId: user.id }, "Blocked user.", { contextURL });
346+
} catch (error) {
347+
log.error({ userId: user.id }, "Failed to block user.", error, { contextURL });
348+
}
349+
}
350+
throw new Error(`${contextURL} is blocklisted on Gitpod.`);
351+
}
352+
335353
// Note: this function does not expect to be awaited for by its caller. This means that it takes care of error handling itself.
336354
protected async actuallyStartWorkspace(
337355
ctx: TraceContext,

0 commit comments

Comments
 (0)