diff --git a/components/server/ee/src/container-module.ts b/components/server/ee/src/container-module.ts index ecc239aaf8bad6..9c2c7851a16699 100644 --- a/components/server/ee/src/container-module.ts +++ b/components/server/ee/src/container-module.ts @@ -87,6 +87,7 @@ export const productionEEContainerModule = new ContainerModule((bind, unbind, is // various bind(EnvEE).toSelf().inSingletonScope(); rebind(Env).to(EnvEE).inSingletonScope(); + rebind(MessageBusIntegration).to(MessageBusIntegrationEE).inSingletonScope(); rebind(HostContainerMapping).to(HostContainerMappingEE).inSingletonScope(); bind(EMailDomainService).to(EMailDomainServiceImpl).inSingletonScope(); diff --git a/components/server/ee/src/env.ts b/components/server/ee/src/env.ts index c81ae2d5d0ddab..70357b461c5ed1 100644 --- a/components/server/ee/src/env.ts +++ b/components/server/ee/src/env.ts @@ -10,8 +10,6 @@ import { getEnvVar, filePathTelepresenceAware } from '@gitpod/gitpod-protocol/li import { readOptionsFromFile } from '@gitpod/gitpod-payment-endpoint/lib/chargebee'; import { Env } from '../../src/env'; - - @injectable() export class EnvEE extends Env { readonly chargebeeProviderOptions = readOptionsFromFile(filePathTelepresenceAware('/chargebee/providerOptions')); diff --git a/components/server/ee/src/prebuilds/github-app.ts b/components/server/ee/src/prebuilds/github-app.ts index 6b21d5aea2e3a2..566cf65fb55781 100644 --- a/components/server/ee/src/prebuilds/github-app.ts +++ b/components/server/ee/src/prebuilds/github-app.ts @@ -55,14 +55,15 @@ export class GithubApp { appId: env.githubAppAppID, privateKey: GithubApp.loadPrivateKey(env.githubAppCertPath), secret: env.githubAppWebhookSecret, - logLevel: env.githubAppLogLevel as Options["logLevel"] + logLevel: env.githubAppLogLevel as Options["logLevel"], + baseUrl: env.githubAppGHEHost, }) - }) + }); log.debug("Starting GitHub app integration", { appId: env.githubAppAppID, cert: env.githubAppCertPath, secret: env.githubAppWebhookSecret - }) + }); this.server.load(this.buildApp.bind(this)); } } diff --git a/components/server/src/auth/rate-limiter.ts b/components/server/src/auth/rate-limiter.ts index 394bc6634233f5..9c58e64836e248 100644 --- a/components/server/src/auth/rate-limiter.ts +++ b/components/server/src/auth/rate-limiter.ts @@ -24,12 +24,12 @@ type FunctionsConfig = { points: number, } } -type RateLimiterConfig = { +export type RateLimiterConfig = { groups: GroupsConfig, functions: FunctionsConfig, }; -function readConfig(): RateLimiterConfig { +function getConfig(config: RateLimiterConfig): RateLimiterConfig { const defaultGroups: GroupsConfig = { default: { points: 60000, // 1,000 calls per user per second @@ -167,11 +167,9 @@ function readConfig(): RateLimiterConfig { "trackEvent": { group: "default", points: 1 }, }; - const fromEnv = JSON.parse(process.env.RATE_LIMITER_CONFIG || "{}") - return { - groups: { ...defaultGroups, ...fromEnv.groups }, - functions: { ...defaultFunctions, ...fromEnv.functions } + groups: { ...defaultGroups, ...config.groups }, + functions: { ...defaultFunctions, ...config.functions } }; } @@ -184,9 +182,9 @@ export class UserRateLimiter { private static instance_: UserRateLimiter; - public static instance(): UserRateLimiter { + public static instance(config: RateLimiterConfig): UserRateLimiter { if (!UserRateLimiter.instance_) { - UserRateLimiter.instance_ = new UserRateLimiter(); + UserRateLimiter.instance_ = new UserRateLimiter(config); } return UserRateLimiter.instance_; } @@ -194,9 +192,8 @@ export class UserRateLimiter { private readonly config: RateLimiterConfig; private readonly limiters: { [key: string]: RateLimiterMemory }; - private constructor() { - this.config = readConfig(); - + private constructor(config: RateLimiterConfig) { + this.config = getConfig(config); this.limiters = {}; Object.keys(this.config.groups).forEach(group => { this.limiters[group] = new RateLimiterMemory({ diff --git a/components/server/src/code-sync/code-sync-service.ts b/components/server/src/code-sync/code-sync-service.ts index ae868e8b7b5784..70f4ea622f8093 100644 --- a/components/server/src/code-sync/code-sync-service.ts +++ b/components/server/src/code-sync/code-sync-service.ts @@ -21,12 +21,13 @@ import uuid = require('uuid'); import { accessCodeSyncStorage, UserRateLimiter } from '../auth/rate-limiter'; import { increaseApiCallUserCounter } from '../prometheus-metrics'; import { TheiaPluginService } from '../theia-plugin/theia-plugin-service'; +import { Env } from '../env'; // By default: 5 kind of resources * 20 revs * 1Mb = 100Mb max in the content service for user data. const defautltRevLimit = 20; // It should keep it aligned with client_max_body_size for /code-sync location. const defaultContentLimit = '1Mb'; -const codeSyncConfig: Partial<{ +export type CodeSyncConfig = Partial<{ revLimit: number contentLimit: number resources: { @@ -34,7 +35,7 @@ const codeSyncConfig: Partial<{ revLimit?: number } } -}> = JSON.parse(process.env.CODE_SYNC_CONFIG || "{}"); +}>; const objectPrefix = 'code-sync/'; function toObjectName(resource: ServerResource, rev: string): string { @@ -55,6 +56,9 @@ const userSettingsUri = 'user_storage:settings.json'; @injectable() export class CodeSyncService { + @inject(Env) + private readonly env: Env; + @inject(BearerAuth) private readonly auth: BearerAuth; @@ -71,6 +75,7 @@ export class CodeSyncService { private readonly userStorageResourcesDB: UserStorageResourcesDB; get apiRouter(): express.Router { + const config = this.env.codeSyncConfig; const router = express.Router(); router.use((_, res, next) => { // to correlate errors reported by users with errors logged by the server @@ -87,7 +92,7 @@ export class CodeSyncService { const id = req.user.id; increaseApiCallUserCounter(accessCodeSyncStorage, id); try { - await UserRateLimiter.instance().consume(id, accessCodeSyncStorage); + await UserRateLimiter.instance(this.env.rateLimiter).consume(id, accessCodeSyncStorage); } catch (e) { if (e instanceof Error) { throw e; @@ -207,7 +212,7 @@ export class CodeSyncService { res.send(content); }); router.post('/v1/resource/:resource', bodyParser.text({ - limit: codeSyncConfig?.contentLimit || defaultContentLimit + limit: config?.contentLimit || defaultContentLimit }), async (req, res) => { if (!User.is(req.user)) { res.sendStatus(204); @@ -222,7 +227,7 @@ export class CodeSyncService { if (latestRev === fromTheiaRev) { latestRev = undefined; } - const revLimit = resourceKey === 'machines' ? 1 : codeSyncConfig.resources?.[resourceKey]?.revLimit || codeSyncConfig?.revLimit || defautltRevLimit; + const revLimit = resourceKey === 'machines' ? 1 : config.resources?.[resourceKey]?.revLimit || config?.revLimit || defautltRevLimit; const userId = req.user.id; let oldObject: string | undefined; const contentType = req.headers['content-type'] || '*/*'; diff --git a/components/server/src/config.ts b/components/server/src/config.ts new file mode 100644 index 00000000000000..8dae50d8a4bf76 --- /dev/null +++ b/components/server/src/config.ts @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2020 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 { GitpodHostUrl } from '@gitpod/gitpod-protocol/lib/util/gitpod-host-url'; +import { AuthProviderParams } from './auth/auth-provider'; + +import { Branding, NamedWorkspaceFeatureFlag } from '@gitpod/gitpod-protocol'; + +import { RateLimiterConfig } from './auth/rate-limiter'; +import { Env } from './env'; +import { CodeSyncConfig } from './code-sync/code-sync-service'; +import { ChargebeeProviderOptions } from "@gitpod/gitpod-payment-endpoint/lib/chargebee"; +import { EnvEE } from '../ee/src/env'; + +export const Config = Symbol("Config"); +export interface Config { + version: string; + hostUrl: GitpodHostUrl; + + license: string | undefined; + trialLicensePrivateKey: string | undefined; + + heartbeat: { + intervalSeconds: number; + timeoutSeconds: number, + }; + + workspaceDefaults: { + ideVersion: string; + ideImageRepo: string; + ideImage: string; + ideImageAliases: { [index: string]: string }; + workspaceImage: string; + previewFeatureFlags: NamedWorkspaceFeatureFlag[]; + defaultFeatureFlags: NamedWorkspaceFeatureFlag[]; + }; + + session: { + maxAgeMs: number; + secret: string; + }; + + githubApp?: { + enabled: boolean; + appId: number; + webhookSecret: string; + authProviderId: string; + certPath: string; + marketplaceName: string; + logLevel?: string; + }; + + definitelyGpDisabled: boolean; + + workspaceGarbageCollection: { + disabled: boolean; + startDate: number; + chunkLimit: number; + minAgeDays: number; + minAgePrebuildDays: number; + contentRetentionPeriodDays: number; + contentChunkLimit: number; + }; + + enableLocalApp: boolean; + + authProviderConfigs: AuthProviderParams[]; + disableDynamicAuthProviderLogin: boolean; + + // TODO(gpl) Can we remove this? + brandingConfig: Branding; + + /** + * The maximum number of environment variables a user can have configured in their list at any given point in time. + * Note: This limit should be so high that no regular user ever reaches it. + */ + maxEnvvarPerUserCount: number; + + /** maxConcurrentPrebuildsPerRef is the maximum number of prebuilds we allow per ref type at any given time */ + maxConcurrentPrebuildsPerRef: number; + + incrementalPrebuilds: { + repositoryPassList: string[]; + commitHistory: number; + }; + + blockNewUsers: { + enabled: boolean; + passlist: string[]; + } + + makeNewUsersAdmin: boolean; + + /** this value - if present - overrides the default naming scheme for the GCloud bucket that Theia Plugins are stored in */ + theiaPluginsBucketNameOverride: string | undefined; + + /** defaultBaseImageRegistryWhitelist is the list of registryies users get acces to by default */ + defaultBaseImageRegistryWhitelist: string[]; + + // TODO(gpl): can we remove this? We never set the value anywhere it seems + insecureNoDomain: boolean; + + runDbDeleter: boolean; + + oauthServer: { + enabled: boolean; + jwtSecret: string; + } + + /** + * The configuration for the rate limiter we (mainly) use for the websocket API + */ + rateLimiter: RateLimiterConfig; + + /** + * The address content service clients connect to + * Example: content-service:8080 + */ + contentServiceAddress: string; + + /** + * TODO(gpl) Looks like this is not used anymore! Verify and remove + */ + serverProxyApiKey?: string; + + codeSyncConfig: CodeSyncConfig; + + /** + * Payment related options + */ + chargebeeProviderOptions?: ChargebeeProviderOptions; + enablePayment?: boolean; +} + +export namespace EnvConfig { + export function fromEnv(env: Env): Config { + return { + version: env.version, + hostUrl: env.hostUrl, + license: env.gitpodLicense, + trialLicensePrivateKey: env.trialLicensePrivateKey, + heartbeat: { + intervalSeconds: env.theiaHeartbeatInterval, + timeoutSeconds: env.workspaceUserTimeout, + }, + workspaceDefaults: { + ideVersion: env.theiaVersion, + ideImageRepo: env.theiaImageRepo, + ideImage: env.ideDefaultImage, + ideImageAliases: env.ideImageAliases, + workspaceImage: env.workspaceDefaultImage, + previewFeatureFlags: env.previewFeatureFlags, + defaultFeatureFlags: env.defaultFeatureFlags, + }, + session: { + maxAgeMs: env.sessionMaxAgeMs, + secret: env.sessionSecret, + }, + githubApp: { + enabled: env.githubAppEnabled, + appId: env.githubAppAppID, + webhookSecret: env.githubAppWebhookSecret, + authProviderId: env.githubAppAuthProviderId, + certPath: env.githubAppCertPath, + marketplaceName: env.githubAppMarketplaceName, + logLevel: env.githubAppLogLevel, + }, + definitelyGpDisabled: env.definitelyGpDisabled, + workspaceGarbageCollection: { + disabled: env.garbageCollectionDisabled, + startDate: env.garbageCollectionStartDate, + chunkLimit: env.garbageCollectionLimit, + minAgeDays: env.daysBeforeGarbageCollection, + minAgePrebuildDays: env.daysBeforeGarbageCollectingPrebuilds, + contentRetentionPeriodDays: env.workspaceDeletionRetentionPeriodDays, + contentChunkLimit: env.workspaceDeletionLimit, + }, + enableLocalApp: env.enableLocalApp, + authProviderConfigs: env.authProviderConfigs, + disableDynamicAuthProviderLogin: env.disableDynamicAuthProviderLogin, + brandingConfig: env.brandingConfig, + maxEnvvarPerUserCount: env.maxUserEnvvarCount, + maxConcurrentPrebuildsPerRef: env.maxConcurrentPrebuildsPerRef, + incrementalPrebuilds: { + repositoryPassList: env.incrementalPrebuildsRepositoryPassList, + commitHistory: env.incrementalPrebuildsCommitHistory, + }, + blockNewUsers: { + enabled: env.blockNewUsers, + passlist: env.blockNewUsersPassList, + }, + makeNewUsersAdmin: env.makeNewUsersAdmin, + theiaPluginsBucketNameOverride: env.theiaPluginsBucketNameOverride, + defaultBaseImageRegistryWhitelist: env.defaultBaseImageRegistryWhitelist, + insecureNoDomain: env.insecureNoDomain, + runDbDeleter: env.runDbDeleter, + oauthServer: { + enabled: env.enableOAuthServer, + jwtSecret: env.oauthServerJWTSecret, + }, + rateLimiter: env.rateLimiter, + contentServiceAddress: env.contentServiceAddress, + serverProxyApiKey: env.serverProxyApiKey, + codeSyncConfig: env.codeSyncConfig, + }; + } + export function fromEnvEE(env: EnvEE): Config { + const config = EnvConfig.fromEnv(env); + return { + ...config, + chargebeeProviderOptions: env.chargebeeProviderOptions, + enablePayment: env.enablePayment, + } + } +} \ No newline at end of file diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts index 151fb2017068d3..564f0000456e84 100644 --- a/components/server/src/container-module.ts +++ b/components/server/src/container-module.ts @@ -79,9 +79,14 @@ import { HeadlessLogController } from './workspace/headless-log-controller'; import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/analytics'; import { HeadlessLogServiceClient } from '@gitpod/content-service/lib/headless-log_grpc_pb'; import { ProjectsService } from './projects/projects-service'; +import { EnvConfig, Config } from './config'; export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => { bind(Env).toSelf().inSingletonScope(); + bind(Config).toDynamicValue(ctx => { + const env = ctx.container.get(Env); + return EnvConfig.fromEnv(env); + }).inSingletonScope(); bind(UserService).toSelf().inSingletonScope(); bind(UserDeletionService).toSelf().inSingletonScope(); @@ -124,7 +129,8 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(WebsocketConnectionManager).toDynamicValue(ctx => { const serverFactory = () => ctx.container.get>(GitpodServerImpl); const hostContextProvider = ctx.container.get(HostContextProvider); - return new WebsocketConnectionManager(serverFactory, hostContextProvider); + const env = ctx.container.get(Env); + return new WebsocketConnectionManager(serverFactory, hostContextProvider, env.rateLimiter); } ).inSingletonScope(); @@ -181,17 +187,26 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(TermsProvider).toSelf().inSingletonScope(); - const contentServiceAddress = process.env.CONTENT_SERVICE_ADDRESS || "content-service:8080"; - const contentServiceClient = new ContentServiceClient(contentServiceAddress, grpc.credentials.createInsecure()) - bind(ContentServiceClient).toConstantValue(contentServiceClient); - const blobServiceClient = new BlobServiceClient(contentServiceAddress, grpc.credentials.createInsecure()) - bind(BlobServiceClient).toConstantValue(blobServiceClient); - const workspaceServiceClient = new WorkspaceServiceClient(contentServiceAddress, grpc.credentials.createInsecure()) - bind(WorkspaceServiceClient).toConstantValue(workspaceServiceClient); - const idePluginServiceClient = new IDEPluginServiceClient(contentServiceAddress, grpc.credentials.createInsecure()) - bind(IDEPluginServiceClient).toConstantValue(idePluginServiceClient); - const headlessLogServiceClient = new HeadlessLogServiceClient(contentServiceAddress, grpc.credentials.createInsecure()) - bind(HeadlessLogServiceClient).toConstantValue(headlessLogServiceClient); + bind(ContentServiceClient).toDynamicValue(ctx => { + const env = ctx.container.get(Env); + return new ContentServiceClient(env.contentServiceAddress, grpc.credentials.createInsecure()); + }); + bind(BlobServiceClient).toDynamicValue(ctx => { + const env = ctx.container.get(Env); + return new BlobServiceClient(env.contentServiceAddress, grpc.credentials.createInsecure()); + }); + bind(WorkspaceServiceClient).toDynamicValue(ctx => { + const env = ctx.container.get(Env); + return new WorkspaceServiceClient(env.contentServiceAddress, grpc.credentials.createInsecure()); + }); + bind(IDEPluginServiceClient).toDynamicValue(ctx => { + const env = ctx.container.get(Env); + return new IDEPluginServiceClient(env.contentServiceAddress, grpc.credentials.createInsecure()); + }); + bind(HeadlessLogServiceClient).toDynamicValue(ctx => { + const env = ctx.container.get(Env); + return new HeadlessLogServiceClient(env.contentServiceAddress, grpc.credentials.createInsecure()); + }); bind(StorageClient).to(ContentServiceStorageClient).inSingletonScope(); diff --git a/components/server/src/env.ts b/components/server/src/env.ts index bf45be5ab126f4..59d405d9b6e318 100644 --- a/components/server/src/env.ts +++ b/components/server/src/env.ts @@ -10,19 +10,16 @@ import { GitpodHostUrl } from '@gitpod/gitpod-protocol/lib/util/gitpod-host-url' import { AbstractComponentEnv, getEnvVar } from '@gitpod/gitpod-protocol/lib/env'; import { AuthProviderParams, parseAuthProviderParamsFromEnv } from './auth/auth-provider'; -import * as fs from "fs"; import { Branding, NamedWorkspaceFeatureFlag, WorkspaceFeatureFlags } from '@gitpod/gitpod-protocol'; import { BrandingParser } from './branding-parser'; +import { RateLimiterConfig } from './auth/rate-limiter'; @injectable() export class Env extends AbstractComponentEnv { readonly serverVersion = process.env.SERVER_VERSION || 'dev'; - readonly hostUrl = new GitpodHostUrl(process.env.HOST_URL || 'https://gitpod.io'); - readonly localhostUrl?: GitpodHostUrl = process.env.LOCALHOST_URL ? new GitpodHostUrl(process.env.LOCALHOST_URL) : undefined; - readonly theiaPort = Number.parseInt(process.env.THEIA_PORT || '23000', 10) || 23000; get theiaHeartbeatInterval() { const envValue = process.env.THEIA_HEARTBEAT_INTERVAL; return envValue ? parseInt(envValue, 10) : (1 * 60 * 1000); @@ -34,7 +31,6 @@ export class Env extends AbstractComponentEnv { readonly theiaVersion = process.env.THEIA_VERSION || this.serverVersion; readonly theiaImageRepo = process.env.THEIA_IMAGE_REPO || 'unknown'; - readonly theiaMounted = process.env.THEIA_MOUNTED === "true"; readonly ideDefaultImage = `${this.theiaImageRepo}:${this.theiaVersion}`; readonly workspaceDefaultImage = process.env.WORKSPACE_DEFAULT_IMAGE || "gitpod/workspace-full:latest"; @@ -66,8 +62,6 @@ export class Env extends AbstractComponentEnv { return value; } - readonly gitpodRegion: string = process.env.GITPOD_REGION || 'unknown'; - readonly sessionMaxAgeMs: number = Number.parseInt(process.env.SESSION_MAX_AGE_MS || '259200000' /* 3 days */, 10); readonly githubAppEnabled: boolean = process.env.GITPOD_GITHUB_APP_ENABLED == "true"; @@ -77,6 +71,7 @@ export class Env extends AbstractComponentEnv { readonly githubAppCertPath: string = process.env.GITPOD_GITHUB_APP_CERT_PATH || "unknown"; readonly githubAppMarketplaceName: string = process.env.GITPOD_GITHUB_APP_MKT_NAME || "unknown"; readonly githubAppLogLevel?: string = process.env.LOG_LEVEL; + readonly githubAppGHEHost?: string = process.env.GHE_HOST; readonly definitelyGpDisabled: boolean = process.env.GITPOD_DEFINITELY_GP_DISABLED == "true"; @@ -132,28 +127,6 @@ export class Env extends AbstractComponentEnv { })() readonly incrementalPrebuildsCommitHistory: number = Number.parseInt(process.env.INCREMENTAL_PREBUILDS_COMMIT_HISTORY || '100', 10) || 100; - protected gitpodLayernameFromFilesystem: string | null | undefined; - protected readGitpodLayernameFromFilesystem(): string | undefined { - if (this.gitpodLayernameFromFilesystem === null) { - // we've tried reading in the past, but were not able to do so - return undefined; - } - if (this.gitpodLayernameFromFilesystem !== undefined) { - // we have read this name previously and it worked - return this.gitpodLayernameFromFilesystem; - } - - try { - this.gitpodLayernameFromFilesystem = fs.readFileSync("/gplayername.txt").toString().trim().split("\n")[0]; - } catch (err) { - console.debug('unable to read /gplayername.txt - this might be ok', err) - this.gitpodLayernameFromFilesystem = null; - return undefined; - } - - return this.gitpodLayernameFromFilesystem; - } - protected parseBool(name: string) { return getEnvVar(name, 'false') === 'true'; } @@ -188,12 +161,7 @@ export class Env extends AbstractComponentEnv { } })(); - /** defaults to: false */ - readonly portAccessForUsersOnly: boolean = this.parsePortAccessForUsersOnly(); - protected parsePortAccessForUsersOnly() { - return getEnvVar('PORT_ACCESS_FOR_USERS_ONLY', 'false') === 'true'; - } - + // TODO(gpl): can we remove this? readonly insecureNoDomain: boolean = getEnvVar('SERVE_INSECURE_NO_DOMAIN', 'false') === 'true'; readonly sessionSecret = this.parseSessionSecret(); @@ -207,4 +175,13 @@ export class Env extends AbstractComponentEnv { readonly oauthServerJWTSecret = getEnvVar("OAUTH_SERVER_JWT_SECRET"); readonly imageBuilderAddress = getEnvVar('IMAGE_BUILDER_SERVICE', "image-builder-mk3:8080"); + + readonly rateLimiter: RateLimiterConfig = JSON.parse(process.env.RATE_LIMITER_CONFIG || "{}"); + + readonly contentServiceAddress = process.env.CONTENT_SERVICE_ADDRESS || "content-service:8080"; + + /** TODO(gpl) Looks like this is not used anymore! Verify and remove */ + readonly serverProxyApiKey = process.env.SERVER_PROXY_APIKEY; + + readonly codeSyncConfig = JSON.parse(process.env.CODE_SYNC_CONFIG || "{}"); } diff --git a/components/server/src/server.ts b/components/server/src/server.ts index 28278396dc4535..10b140fb881b9f 100644 --- a/components/server/src/server.ts +++ b/components/server/src/server.ts @@ -41,12 +41,14 @@ import { CodeSyncService } from './code-sync/code-sync-service'; import { increaseHttpRequestCounter, observeHttpRequestDuration } from './prometheus-metrics'; import { OAuthController } from './oauth-server/oauth-controller'; import { HeadlessLogController } from './workspace/headless-log-controller'; +import { Config } from './config'; @injectable() export class Server { static readonly EVENT_ON_START = 'start'; @inject(Env) protected readonly env: Env; + @inject(Config) protected readonly config: Config; @inject(SessionHandlerProvider) protected sessionHandlerProvider: SessionHandlerProvider; @inject(Authenticator) protected authenticator: Authenticator; @inject(UserController) protected readonly userController: UserController; @@ -80,6 +82,7 @@ export class Server { public async init(app: express.Application) { log.info('Initializing'); + log.info('config', { config: JSON.stringify(this.config, undefined, 2) }); // metrics app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { diff --git a/components/server/src/theia-plugin/theia-plugin-controller.ts b/components/server/src/theia-plugin/theia-plugin-controller.ts index 51a46c0e49349b..34e2ff5472c2bf 100644 --- a/components/server/src/theia-plugin/theia-plugin-controller.ts +++ b/components/server/src/theia-plugin/theia-plugin-controller.ts @@ -20,8 +20,6 @@ export class TheiaPluginController { @inject(TheiaPluginDB) protected readonly pluginDB: TheiaPluginDB; @inject(TheiaPluginService) protected readonly pluginService: TheiaPluginService; - protected readonly SERVER_PROXY_APIKEY = process.env.SERVER_PROXY_APIKEY; - get apiRouter(): express.Router { const router = express.Router(); this.addPreflightHandler(router); @@ -36,7 +34,7 @@ export class TheiaPluginController { */ router.get("/preflight", async (req, res, next) => { const token = req.query.token || req.headers["token"] || "unauthorized"; - if (this.SERVER_PROXY_APIKEY != token) { + if (this.env.serverProxyApiKey != token) { log.warn("Unauthorized attempted to access the /plugins/preflight endpoint", req); res.sendStatus(401); return; @@ -67,7 +65,7 @@ export class TheiaPluginController { router.get("/checkin", async (req, res, next) => { const token = req.query.token || req.headers["token"] || "unauthorized"; - if (this.SERVER_PROXY_APIKEY != token) { + if (this.env.serverProxyApiKey != token) { log.warn("Unauthorized attempted to access the /plugins/checkin endpoint", req); res.sendStatus(401); return; diff --git a/components/server/src/theia-plugin/theia-plugin-service.ts b/components/server/src/theia-plugin/theia-plugin-service.ts index 4f440c30bfe47a..c95d981d594bb7 100644 --- a/components/server/src/theia-plugin/theia-plugin-service.ts +++ b/components/server/src/theia-plugin/theia-plugin-service.ts @@ -9,7 +9,6 @@ import { injectable, inject } from 'inversify'; import { ResolvePluginsParams, ResolvedPlugins, TheiaPlugin, PreparePluginUploadParams, InstallPluginsParams, UninstallPluginParams, ResolvedPluginKind } from '@gitpod/gitpod-protocol'; import { TheiaPluginDB, UserStorageResourcesDB } from "@gitpod/gitpod-db/lib"; import { Env } from '../env'; -import { GitpodHostUrl } from '@gitpod/gitpod-protocol/lib/util/gitpod-host-url'; import { log } from '@gitpod/gitpod-protocol/lib/util/logging'; import { ResponseError } from 'vscode-jsonrpc'; import { ErrorCodes } from '@gitpod/gitpod-protocol/lib/messaging/error'; @@ -152,7 +151,7 @@ export class TheiaPluginService { } protected getPublicPluginURL(pluginEntryId: string) { - return new GitpodHostUrl(process.env.HOST_URL) + return this.env.hostUrl .with({ pathname: '/plugins', search: `id=${pluginEntryId}` diff --git a/components/server/src/websocket-connection-manager.ts b/components/server/src/websocket-connection-manager.ts index 993522f7672174..745c3f70d3b724 100644 --- a/components/server/src/websocket-connection-manager.ts +++ b/components/server/src/websocket-connection-manager.ts @@ -14,7 +14,7 @@ import * as express from "express"; import { ErrorCodes as RPCErrorCodes, MessageConnection, ResponseError } from "vscode-jsonrpc"; import { AllAccessFunctionGuard, FunctionAccessGuard, WithFunctionAccessGuard } from "./auth/function-access"; import { HostContextProvider } from "./auth/host-context-provider"; -import { RateLimiter, UserRateLimiter } from "./auth/rate-limiter"; +import { RateLimiter, RateLimiterConfig, UserRateLimiter } from "./auth/rate-limiter"; import { CompositeResourceAccessGuard, OwnerResourceGuard, ResourceAccessGuard, SharedWorkspaceAccessGuard, WithResourceAccessGuard, WorkspaceLogAccessGuard } from "./auth/resource-access"; import { increaseApiCallCounter, increaseApiConnectionClosedCounter, increaseApiConnectionCounter, increaseApiCallUserCounter } from "./prometheus-metrics"; import { GitpodServerImpl } from "./workspace/gitpod-server-impl"; @@ -36,7 +36,8 @@ export class WebsocketConnectionManager, - protected readonly hostContextProvider: HostContextProvider) { + protected readonly hostContextProvider: HostContextProvider, + protected readonly rateLimiterConfig: RateLimiterConfig) { this.jsonRpcConnectionHandler = new GitpodJsonRpcConnectionHandler( this.path, this.createProxyTarget.bind(this), @@ -104,7 +105,7 @@ export class WebsocketConnectionManager UserRateLimiter.instance().consume(id, method), + consume: (method) => UserRateLimiter.instance(this.rateLimiterConfig).consume(id, method), } }