@@ -162,7 +162,7 @@ import { IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol";
162
162
import { PartialProject } from "@gitpod/gitpod-protocol/lib/teams-projects-protocol" ;
163
163
import { ClientMetadata , traceClientMetadata } from "../websocket/websocket-connection-manager" ;
164
164
import { ConfigurationService } from "../config/configuration-service" ;
165
- import { ProjectEnvVar } from "@gitpod/gitpod-protocol/lib/protocol" ;
165
+ import { EnvVarWithValue , ProjectEnvVar } from "@gitpod/gitpod-protocol/lib/protocol" ;
166
166
import { InstallationAdminSettings , TelemetryData } from "@gitpod/gitpod-protocol" ;
167
167
import { Deferred } from "@gitpod/gitpod-protocol/lib/util/deferred" ;
168
168
import { InstallationAdminTelemetryDataProvider } from "../installation-admin/telemetry-data-provider" ;
@@ -200,6 +200,7 @@ import {
200
200
import { reportCentralizedPermsValidation } from "../prometheus-metrics" ;
201
201
import { RegionService } from "./region-service" ;
202
202
import { isWorkspaceRegion , WorkspaceRegion } from "@gitpod/gitpod-protocol/lib/workspace-cluster" ;
203
+ import { EnvVar } from "@gitpod/gitpod-protocol/src/protocol" ;
203
204
204
205
// shortcut
205
206
export const traceWI = ( ctx : TraceContext , wi : Omit < LogContext , "userId" > ) => TraceContext . setOWI ( ctx , wi ) ; // userId is already taken care of in WebsocketConnectionManager
@@ -741,8 +742,11 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
741
742
if ( workspace . deleted ) {
742
743
throw new ResponseError ( ErrorCodes . PERMISSION_DENIED , "Cannot (re-)start a deleted workspace." ) ;
743
744
}
744
- const userEnvVars = this . userDB . getEnvVars ( user . id ) ;
745
- const projectEnvVarsPromise = this . internalGetProjectEnvVars ( workspace . projectId ) ;
745
+ const envVarsPromise = this . resolveWorkspaceEnvVars ( user , {
746
+ repository : CommitContext . is ( workspace . context ) ? workspace . context . repository : undefined ,
747
+ projectId : workspace . projectId ,
748
+ includeCensored : false ,
749
+ } ) ;
746
750
const projectPromise = workspace . projectId
747
751
? this . projectDB . findProjectById ( workspace . projectId )
748
752
: Promise . resolve ( undefined ) ;
@@ -751,20 +755,66 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
751
755
752
756
options . region = await this . determineWorkspaceRegion ( workspace , options . region || "" ) ;
753
757
758
+ const { workspaceEnvVars, projectEnvVars } = await envVarsPromise ;
759
+
754
760
// at this point we're about to actually start a new workspace
755
761
const result = await this . workspaceStarter . startWorkspace (
756
762
ctx ,
757
763
workspace ,
758
764
user ,
759
765
await projectPromise ,
760
- await userEnvVars ,
761
- await projectEnvVarsPromise ,
766
+ workspaceEnvVars ,
767
+ projectEnvVars ,
762
768
options ,
763
769
) ;
764
770
traceWI ( ctx , { instanceId : result . instanceID } ) ;
765
771
return result ;
766
772
}
767
773
774
+ private async resolveWorkspaceEnvVars (
775
+ user : User ,
776
+ options : {
777
+ repository :
778
+ | {
779
+ owner : string ;
780
+ name : string ;
781
+ }
782
+ | undefined ;
783
+ projectId : string | undefined ;
784
+ includeCensored : boolean ;
785
+ } ,
786
+ ) : Promise < {
787
+ workspaceEnvVars : EnvVar [ ] ;
788
+ projectEnvVars : ProjectEnvVar [ ] ;
789
+ } > {
790
+ let userEnvVars = await this . userDB . getEnvVars ( user . id ) ;
791
+ if ( options . repository ) {
792
+ // this is a commit context, thus we can filter the env vars
793
+ userEnvVars = UserEnvVar . filter ( userEnvVars , options . repository . owner , options . repository . name ) ;
794
+ }
795
+ const workspaceEnvVars : Map < String , EnvVar > = new Map ( userEnvVars . map ( ( e ) => [ e . name , e ] ) ) ;
796
+
797
+ const projectEnvVars = await this . internalGetProjectEnvVars ( options . projectId ) ;
798
+ if ( projectEnvVars . length > 0 ) {
799
+ // Instead of using an access guard for Project environment variables, we let Project owners decide whether
800
+ // a variable should be:
801
+ // - exposed in all workspaces (even for non-Project members when the repository is public), or
802
+ // - censored from all workspaces (even for Project members)
803
+ const availablePrjEnvVars = projectEnvVars . filter (
804
+ ( variable ) => variable . censored === options . includeCensored ,
805
+ ) ;
806
+ const withValues = await this . projectDB . getProjectEnvironmentVariableValues ( availablePrjEnvVars ) ;
807
+ for ( const e of withValues ) {
808
+ workspaceEnvVars . set ( e . name , e ) ;
809
+ }
810
+ }
811
+
812
+ return {
813
+ workspaceEnvVars : [ ...workspaceEnvVars . values ( ) ] ,
814
+ projectEnvVars,
815
+ } ;
816
+ }
817
+
768
818
public async stopWorkspace ( ctx : TraceContext , workspaceId : string ) : Promise < void > {
769
819
traceAPIParams ( ctx , { workspaceId } ) ;
770
820
traceWI ( ctx , { workspaceId } ) ;
@@ -1087,7 +1137,6 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
1087
1137
const user = this . checkAndBlockUser ( "createWorkspace" , { options } ) ;
1088
1138
await this . checkTermsAcceptance ( ) ;
1089
1139
1090
- const envVars = this . userDB . getEnvVars ( user . id ) ;
1091
1140
logContext = { userId : user . id } ;
1092
1141
1093
1142
// Credit check runs in parallel with the other operations up until we start consuming resources.
@@ -1225,18 +1274,24 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
1225
1274
throw err ;
1226
1275
}
1227
1276
1228
- let projectEnvVarsPromise = this . internalGetProjectEnvVars ( workspace . projectId ) ;
1277
+ const envVarsPromise = this . resolveWorkspaceEnvVars ( user , {
1278
+ repository : CommitContext . is ( workspace . context ) ? workspace . context . repository : undefined ,
1279
+ projectId : workspace . projectId ,
1280
+ includeCensored : workspace . type === "prebuild" ,
1281
+ } ) ;
1229
1282
options . region = await this . determineWorkspaceRegion ( workspace , options . region || "" ) ;
1230
1283
1284
+ const { workspaceEnvVars, projectEnvVars } = await envVarsPromise ;
1285
+
1231
1286
logContext . workspaceId = workspace . id ;
1232
1287
traceWI ( ctx , { workspaceId : workspace . id } ) ;
1233
1288
const startWorkspaceResult = await this . workspaceStarter . startWorkspace (
1234
1289
ctx ,
1235
1290
workspace ,
1236
1291
user ,
1237
1292
project ,
1238
- await envVars ,
1239
- await projectEnvVarsPromise ,
1293
+ workspaceEnvVars ,
1294
+ projectEnvVars ,
1240
1295
options ,
1241
1296
) ;
1242
1297
ctx . span ?. log ( { event : "startWorkspaceComplete" , ...startWorkspaceResult } ) ;
@@ -1837,7 +1892,36 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
1837
1892
return [ ] ;
1838
1893
}
1839
1894
1895
+ async getWorkspaceEnvVars ( ctx : TraceContext , workspaceId : string ) : Promise < EnvVarWithValue [ ] > {
1896
+ const user = this . checkUser ( "getWorkspaceEnvVars" ) ;
1897
+ const workspace = await this . internalGetWorkspace ( workspaceId , this . workspaceDb . trace ( ctx ) ) ;
1898
+ await this . guardAccess ( { kind : "workspace" , subject : workspace } , "get" ) ;
1899
+
1900
+ if ( workspace . projectId ) {
1901
+ // TODO should we really leak errors to a user? or just return empty and log a warning? block a user?
1902
+ await this . guardProjectOperation ( user , workspace . projectId , "get" ) ;
1903
+ }
1904
+
1905
+ const result : EnvVarWithValue [ ] = [ ] ;
1906
+ const { workspaceEnvVars } = await this . resolveWorkspaceEnvVars ( user , {
1907
+ repository : undefined , // filter below with access guard
1908
+ projectId : workspace . projectId ,
1909
+ includeCensored : false , // for now workspace cannot get hidden, i.e. may change later if someone needs it for prebuild
1910
+ } ) ;
1911
+ for ( const value of workspaceEnvVars ) {
1912
+ if (
1913
+ "repositoryPattern" in value &&
1914
+ ! ( await this . resourceAccessGuard . canAccess ( { kind : "envVar" , subject : value } , "get" ) )
1915
+ ) {
1916
+ continue ;
1917
+ }
1918
+ result . push ( value ) ;
1919
+ }
1920
+ return result ;
1921
+ }
1922
+
1840
1923
// Get environment variables (filter by repository pattern precedence)
1924
+ // TODO do we really still need it, gitpod cli was the onyl client?
1841
1925
async getEnvVars ( ctx : TraceContext ) : Promise < UserEnvVarValue [ ] > {
1842
1926
const user = this . checkUser ( "getEnvVars" ) ;
1843
1927
const result = new Map < string , { value : UserEnvVar ; score : number } > ( ) ;
0 commit comments