Skip to content

Commit 9dd52e1

Browse files
authored
Rename allowed env configuration traits (#1137)
* Deprecate env-whitelist traits, send client-envs in kernelspec * Address changes in docs and deployment scripts * Update tests * Revert changes to drop wildcard support and send client_envs in kspec
1 parent edafec5 commit 9dd52e1

File tree

19 files changed

+104
-80
lines changed

19 files changed

+104
-80
lines changed

docs/source/developers/custom-images.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,6 @@ cp -r python_kubernetes python_myCustomKernel
164164
}
165165
```
166166

167-
- If using a whitelist (`EG_KERNEL_WHITELIST`), be sure to update it with the new kernel specification directory name (e.g., `python_myCustomKernel`) and restart/redeploy Enterprise Gateway.
167+
- If using kernel filtering (`EG_ALLOWED_KERNELS`), be sure to update it with the new kernel specification directory name (e.g., `python_myCustomKernel`) and restart/redeploy Enterprise Gateway.
168168
- Launch or refresh your Notebook session and confirm `My Custom Kernel` appears in the _new kernel_ drop-down.
169169
- Create a new notebook using `My Custom Kernel`.

docs/source/operators/config-cli.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ EnterpriseGatewayApp(EnterpriseGatewayConfigMixin, JupyterApp) options
121121
The full path to a certificate authority certificate for SSL/TLS client
122122
authentication. (EG_CLIENT_CA env var)
123123
Default: None
124+
--EnterpriseGatewayApp.client_envs=<list-item-1>...
125+
Environment variables allowed to be set when a client requests a
126+
new kernel. (EG_CLIENT_ENVS env var)
127+
Default: []
124128
--EnterpriseGatewayApp.conductor_endpoint=<Unicode>
125129
The http url for accessing the Conductor REST API. (EG_CONDUCTOR_ENDPOINT
126130
env var)
@@ -140,13 +144,10 @@ EnterpriseGatewayApp(EnterpriseGatewayConfigMixin, JupyterApp) options
140144
(EG_DYNAMIC_CONFIG_INTERVAL env var)
141145
Default: 0
142146
--EnterpriseGatewayApp.env_process_whitelist=<list-item-1>...
143-
Environment variables allowed to be inherited from the spawning process by
144-
the kernel. (EG_ENV_PROCESS_WHITELIST env var)
147+
DEPRECATED, use inherited_envs
145148
Default: []
146149
--EnterpriseGatewayApp.env_whitelist=<list-item-1>...
147-
Environment variables allowed to be set when a client requests a new kernel.
148-
Use '*' to allow all environment variables sent in the request.
149-
(EG_ENV_WHITELIST env var)
150+
DEPRECATED, use client_envs.
150151
Default: []
151152
--EnterpriseGatewayApp.expose_headers=<Unicode>
152153
Sets the Access-Control-Expose-Headers header. (EG_EXPOSE_HEADERS env var)
@@ -158,6 +159,10 @@ EnterpriseGatewayApp(EnterpriseGatewayConfigMixin, JupyterApp) options
158159
Indicates whether impersonation will be performed during kernel launch.
159160
(EG_IMPERSONATION_ENABLED env var)
160161
Default: False
162+
--EnterpriseGatewayApp.inherited_envs=<list-item-1>...
163+
Environment variables allowed to be inherited
164+
from the spawning process by the kernel. (EG_INHERITED_ENVS env var)
165+
Default: []
161166
--EnterpriseGatewayApp.ip=<Unicode>
162167
IP address on which to listen (EG_IP env var)
163168
Default: '127.0.0.1'

docs/source/operators/config-kernel-override.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ those same-named variables in the kernel.json `env` stanza.
3838

3939
Environment variables for which this can occur are any variables prefixed with `KERNEL_`
4040
as well as any variables
41-
listed in the `EnterpriseGatewayApp.env_whitelist` configurable trait (or via
42-
the `EG_ENV_WHITELIST` variable). Locally defined variables listed in `EG_PROCESS_ENV_WHITELIST`
41+
listed in the `EnterpriseGatewayApp.client_envs` configurable trait (or via
42+
the `EG_CLIENT_ENVS` variable). Likewise, environment variables of the Enterprise Gateway
43+
server process listed in the `EnterpriseGatewayApp.inherited_envs` configurable trait
44+
(or via the `EG_INHERITED_ENVS` variable)
4345
are also available for replacement in the kernel process' environment.
4446

4547
See [Kernel Environment Variables](../users/kernel-envs.md) in the Users documentation section for a complete set of recognized `KERNEL_` variables.

enterprise_gateway/enterprisegatewayapp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ def init_webapp(self) -> None:
236236
eg_expose_headers=self.expose_headers,
237237
eg_max_age=self.max_age,
238238
eg_max_kernels=self.max_kernels,
239-
eg_env_process_whitelist=self.env_process_whitelist,
240-
eg_env_whitelist=self.env_whitelist,
239+
eg_inherited_envs=self.inherited_envs,
240+
eg_client_envs=self.client_envs,
241241
eg_kernel_headers=self.kernel_headers,
242242
eg_list_kernels=self.list_kernels,
243243
eg_authorized_users=self.authorized_users,

enterprise_gateway/mixins.py

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -372,30 +372,49 @@ def list_kernels_default(self) -> bool:
372372
== "true"
373373
)
374374

375-
env_whitelist_env = "EG_ENV_WHITELIST"
376375
env_whitelist = ListTrait(
376+
config=True,
377+
help="""DEPRECATED, use client_envs.""",
378+
)
379+
380+
@observe("env_whitelist")
381+
def _update_env_whitelist(self, change):
382+
self.log.warning("env_whitelist is deprecated, use client_envs")
383+
self.client_envs = change["new"]
384+
385+
client_envs_env = "EG_CLIENT_ENVS"
386+
client_envs = ListTrait(
377387
config=True,
378388
help="""Environment variables allowed to be set when a client requests a
379-
new kernel. Use '*' to allow all environment variables sent in the request.
380-
(EG_ENV_WHITELIST env var)""",
389+
new kernel. (EG_CLIENT_ENVS env var)""",
381390
)
382391

383-
@default("env_whitelist")
384-
def env_whitelist_default(self) -> List[str]:
385-
return os.getenv(self.env_whitelist_env, os.getenv("KG_ENV_WHITELIST", "")).split(",")
392+
@default("client_envs")
393+
def client_envs_default(self):
394+
return os.getenv(self.client_envs_env, os.getenv("EG_ENV_WHITELIST", "")).split(",")
386395

387-
env_process_whitelist_env = "EG_ENV_PROCESS_WHITELIST"
388396
env_process_whitelist = ListTrait(
397+
config=True,
398+
help="""DEPRECATED, use inherited_envs""",
399+
)
400+
401+
@observe("env_process_whitelist")
402+
def _update_env_process_whitelist(self, change):
403+
self.log.warning("env_process_whitelist is deprecated, use inherited_envs")
404+
self.inherited_envs = change["new"]
405+
406+
inherited_envs_env = "EG_INHERITED_ENVS"
407+
inherited_envs = ListTrait(
389408
config=True,
390409
help="""Environment variables allowed to be inherited
391-
from the spawning process by the kernel. (EG_ENV_PROCESS_WHITELIST env var)""",
410+
from the spawning process by the kernel. (EG_INHERITED_ENVS env var)""",
392411
)
393412

394-
@default("env_process_whitelist")
395-
def env_process_whitelist_default(self) -> List[str]:
396-
return os.getenv(
397-
self.env_process_whitelist_env, os.getenv("KG_ENV_PROCESS_WHITELIST", "")
398-
).split(",")
413+
@default("inherited_envs")
414+
def inherited_envs_default(self) -> List[str]:
415+
return os.getenv(self.inherited_envs_env, os.getenv("EG_ENV_PROCESS_WHITELIST", "")).split(
416+
","
417+
)
399418

400419
kernel_headers_env = "EG_KERNEL_HEADERS"
401420
kernel_headers = ListTrait(

enterprise_gateway/services/api/swagger.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@
160160
},
161161
"env": {
162162
"type": "object",
163-
"description": "A dictionary of environment variables and values to include in the kernel process - subject to whitelisting.",
163+
"description": "A dictionary of environment variables and values to include in the kernel process - subject to filtering.",
164164
"additionalProperties": {
165165
"type": "string"
166166
}

enterprise_gateway/services/api/swagger.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ paths:
141141
type: object
142142
description: |
143143
A dictionary of environment variables and values to include in the
144-
kernel process - subject to whitelisting.
144+
kernel process - subject to filtering.
145145
additionalProperties:
146146
type: string
147147
responses:

enterprise_gateway/services/kernels/handlers.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ class MainKernelHandler(
2222
"""
2323

2424
@property
25-
def env_whitelist(self):
26-
return self.settings["eg_env_whitelist"]
25+
def client_envs(self):
26+
return self.settings["eg_client_envs"]
2727

2828
@property
29-
def env_process_whitelist(self):
30-
return self.settings["eg_env_process_whitelist"]
29+
def inherited_envs(self):
30+
return self.settings["eg_inherited_envs"]
3131

3232
async def post(self):
3333
"""Overrides the super class method to manage env in the request body.
@@ -54,24 +54,24 @@ async def post(self):
5454
# Start with the PATH from the current env. Do not provide the entire environment
5555
# which might contain server secrets that should not be passed to kernels.
5656
env = {"PATH": os.getenv("PATH", "")}
57-
# Whitelist environment variables from current process environment
57+
# Transfer inherited environment variables from current process
5858
env.update(
59-
{
60-
key: value
61-
for key, value in os.environ.items()
62-
if key in self.env_process_whitelist
63-
}
59+
{key: value for key, value in os.environ.items() if key in self.inherited_envs}
6460
)
65-
# Whitelist KERNEL_* args and those allowed by configuration from client. If all
66-
# envs are requested, just use the keys from the payload.
67-
env_whitelist = self.env_whitelist
68-
if env_whitelist == ["*"]:
69-
env_whitelist = model["env"].keys()
61+
# Allow all KERNEL_* envs and those specified in client_envs and set from client. If this EG
62+
# instance is configured to accept all envs in the payload (i.e., client_envs == '*'), go ahead
63+
# and add those keys to the "working" allowed_envs list, otherwise, just transfer the configured envs.
64+
allowed_envs: List[str]
65+
if self.client_envs == ["*"]:
66+
allowed_envs = model["env"].keys()
67+
else:
68+
allowed_envs = self.client_envs
69+
# Allow KERNEL_* args and those allowed by configuration.
7070
env.update(
7171
{
7272
key: value
7373
for key, value in model["env"].items()
74-
if key.startswith("KERNEL_") or key in env_whitelist
74+
if key.startswith("KERNEL_") or key in allowed_envs
7575
}
7676
)
7777

enterprise_gateway/services/kernels/remotemanager.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,8 @@ def _link_dependent_props(self):
427427
"port_range",
428428
"impersonation_enabled",
429429
"max_kernels_per_user",
430-
"env_whitelist",
431-
"env_process_whitelist",
430+
"client_envs",
431+
"inherited_envs",
432432
"yarn_endpoint",
433433
"alt_yarn_endpoint",
434434
"yarn_endpoint_security_enabled",
@@ -456,7 +456,7 @@ async def start_kernel(self, **kwargs):
456456

457457
def _capture_user_overrides(self, **kwargs):
458458
"""
459-
Make a copy of any whitelist or KERNEL_ env values provided by user. These will be injected
459+
Make a copy of any allowed or KERNEL_ env values provided by user. These will be injected
460460
back into the env after the kernelspec env has been applied. This enables defaulting behavior
461461
of the kernelspec env stanza that would have otherwise overridden the user-provided values.
462462
"""
@@ -470,8 +470,8 @@ def _capture_user_overrides(self, **kwargs):
470470
key: value
471471
for key, value in env.items()
472472
if key.startswith("KERNEL_")
473-
or key in self.env_process_whitelist
474-
or key in self.env_whitelist
473+
or key in self.inherited_envs
474+
or key in self.client_envs
475475
}
476476
)
477477

enterprise_gateway/services/kernelspecs/handlers.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def kernel_spec_cache(self) -> KernelSpecCache:
6969

7070
@web.authenticated
7171
async def get(self) -> None:
72-
ksm = self.kernel_spec_cache
72+
ksc = self.kernel_spec_cache
7373
km = self.kernel_manager
7474
model = {}
7575
model["default"] = km.default_kernel_name
@@ -82,7 +82,7 @@ async def get(self) -> None:
8282
if kernel_user:
8383
self.log.debug("Searching kernels for user '%s' " % kernel_user)
8484

85-
kspecs = await ensure_async(ksm.get_all_specs())
85+
kspecs = await ensure_async(ksc.get_all_specs())
8686

8787
list_kernels_found = []
8888
for kernel_name, kernel_info in kspecs.items():
@@ -122,14 +122,14 @@ def kernel_spec_cache(self) -> KernelSpecCache:
122122

123123
@web.authenticated
124124
async def get(self, kernel_name: str) -> None:
125-
ksm = self.kernel_spec_cache
125+
ksc = self.kernel_spec_cache
126126
kernel_name = url_unescape(kernel_name)
127127
kernel_user_filter = self.request.query_arguments.get("user")
128128
kernel_user = None
129129
if kernel_user_filter:
130130
kernel_user = kernel_user_filter[0].decode("utf-8")
131131
try:
132-
spec = await ensure_async(ksm.get_kernel_spec(kernel_name))
132+
spec = await ensure_async(ksc.get_kernel_spec(kernel_name))
133133
except KeyError:
134134
raise web.HTTPError(404, "Kernel spec %s not found" % kernel_name)
135135
if is_kernelspec_model(spec):
@@ -166,9 +166,9 @@ def initialize(self) -> None:
166166

167167
@web.authenticated
168168
async def get(self, kernel_name: str, path: str, include_body: bool = True) -> None:
169-
ksm = self.kernel_spec_cache
169+
ksc = self.kernel_spec_cache
170170
try:
171-
kernelspec = await ensure_async(ksm.get_kernel_spec(kernel_name))
171+
kernelspec = await ensure_async(ksc.get_kernel_spec(kernel_name))
172172
self.root = kernelspec.resource_dir
173173
except KeyError as e:
174174
raise web.HTTPError(404, "Kernel spec %s not found" % kernel_name) from e

enterprise_gateway/services/kernelspecs/kernelspec_cache.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class KernelSpecCache(SingletonConfigurable):
3333

3434
cache_enabled_env = "EG_KERNELSPEC_CACHE_ENABLED"
3535
cache_enabled = CBool(
36-
False,
36+
True,
3737
config=True,
3838
help="""Enable Kernel Specification caching. (EG_KERNELSPEC_CACHE_ENABLED env var)""",
3939
)
@@ -110,7 +110,7 @@ def get_item(self, kernel_name: str) -> Optional[KernelSpec]:
110110
)
111111
return kernelspec
112112

113-
def get_all_items(self) -> Optional[Dict[str, CacheItemType]]:
113+
def get_all_items(self) -> Dict[str, CacheItemType]:
114114
"""Retrieves all kernel specification from the cache.
115115
116116
If cache is disabled or no items are in the cache, an empty dictionary is returned;
@@ -135,12 +135,8 @@ def put_item(self, kernel_name: str, cache_item: Union[KernelSpec, CacheItemType
135135
If it determines the cache entry corresponds to a currently unwatched directory,
136136
that directory will be added to list of observed directories and scheduled accordingly.
137137
"""
138-
self.log.info(
139-
"KernelSpecCache: adding/updating kernelspec: {kernel_name}".format(
140-
kernel_name=kernel_name
141-
)
142-
)
143138
if self.cache_enabled:
139+
self.log.info(f"KernelSpecCache: adding/updating kernelspec: {kernel_name}")
144140
if type(cache_item) is KernelSpec:
145141
cache_item = KernelSpecCache.kernel_spec_to_cache_item(cache_item)
146142

@@ -159,9 +155,8 @@ def put_item(self, kernel_name: str, cache_item: Union[KernelSpec, CacheItemType
159155

160156
def put_all_items(self, kernelspecs: Dict[str, CacheItemType]) -> None:
161157
"""Adds or updates a dictionary of kernel specification in the cache."""
162-
if self.cache_enabled and kernelspecs:
163-
for kernel_name, cache_item in kernelspecs.items():
164-
self.put_item(kernel_name, cache_item)
158+
for kernel_name, cache_item in kernelspecs.items():
159+
self.put_item(kernel_name, cache_item)
165160

166161
def remove_item(self, kernel_name: str) -> Optional[CacheItemType]:
167162
"""Removes the cache item corresponding to kernel_name from the cache."""
@@ -212,7 +207,7 @@ def _initialize(self):
212207

213208
@staticmethod
214209
def kernel_spec_to_cache_item(kernelspec: KernelSpec) -> CacheItemType:
215-
"""Convets a KernelSpec instance to a CacheItemType for storage into the cache."""
210+
"""Converts a KernelSpec instance to a CacheItemType for storage into the cache."""
216211
cache_item = dict()
217212
cache_item["spec"] = kernelspec.to_dict()
218213
cache_item["resource_dir"] = kernelspec.resource_dir
@@ -221,7 +216,8 @@ def kernel_spec_to_cache_item(kernelspec: KernelSpec) -> CacheItemType:
221216
@staticmethod
222217
def cache_item_to_kernel_spec(cache_item: CacheItemType) -> KernelSpec:
223218
"""Converts a CacheItemType to a KernelSpec instance for user consumption."""
224-
return KernelSpec.from_resource_dir(cache_item["resource_dir"])
219+
kernel_spec = KernelSpec(resource_dir=cache_item["resource_dir"], **cache_item["spec"])
220+
return kernel_spec
225221

226222

227223
class KernelSpecChangeHandler(FileSystemEventHandler):

enterprise_gateway/services/processproxies/k8s.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ async def launch_process(
5151

5252
# Kubernetes relies on many internal env variables. Since EG is running in a k8s pod, we will
5353
# transfer its env to each launched kernel.
54-
kwargs["env"] = dict(
55-
os.environ, **kwargs["env"]
56-
) # FIXME: Should probably use process-whitelist in JKG #280
54+
kwargs["env"] = dict(os.environ, **kwargs["env"])
5755
self.kernel_pod_name = self._determine_kernel_pod_name(**kwargs)
5856
self.kernel_namespace = self._determine_kernel_namespace(
5957
**kwargs

enterprise_gateway/tests/test_handlers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ def setup_app(self):
2424
os.environ["JUPYTER_PATH"] = RESOURCES
2525

2626
# These are required for setup of test_kernel_defaults
27+
# Note: We still reference the DEPRECATED config parameter and environment variable so that
28+
# we can test client_envs and inherited_envs, respectively.
29+
self.app.env_whitelist = ["TEST_VAR", "OTHER_VAR1", "OTHER_VAR2"]
2730
os.environ["EG_ENV_PROCESS_WHITELIST"] = "PROCESS_VAR1,PROCESS_VAR2"
2831
os.environ["PROCESS_VAR1"] = "process_var1_override"
2932

30-
self.app.env_whitelist = ["TEST_VAR", "OTHER_VAR1", "OTHER_VAR2"]
31-
3233
def tearDown(self):
3334
"""Shuts down the app after test run."""
3435

enterprise_gateway/tests/test_kernelspec_cache.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ async def tests_get_modified_spec(kernel_spec_cache):
137137

138138
# Modify entry
139139
_modify_kernelspec(kspec.resource_dir, "test2")
140+
await asyncio.sleep(0.5) # sleep for a half-second to allow cache to update item
140141
kspec = await kernel_spec_cache.get_kernel_spec("test2")
141142
assert kspec.display_name == "test2 modified!"
142143

@@ -180,6 +181,7 @@ async def tests_remove_spec(kernel_spec_cache):
180181

181182
assert kernel_spec_cache.cache_misses == 0
182183
shutil.rmtree(kspec.resource_dir)
184+
await asyncio.sleep(0.5) # sleep for a half-second to allow cache to remove item
183185
with pytest.raises(NoSuchKernel):
184186
await kernel_spec_cache.get_kernel_spec("test2")
185187

etc/docker/docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ services:
2222
- "EG_DOCKER_NETWORK=${EG_DOCKER_NETWORK:-enterprise-gateway_enterprise-gateway}"
2323
- "EG_KERNEL_LAUNCH_TIMEOUT=${EG_KERNEL_LAUNCH_TIMEOUT:-60}"
2424
- "EG_CULL_IDLE_TIMEOUT=${EG_CULL_IDLE_TIMEOUT:-3600}"
25-
- "EG_KERNEL_WHITELIST=${EG_KERNEL_WHITELIST:-'r_docker','python_docker','python_tf_docker','python_tf_gpu_docker','scala_docker'}"
25+
# Use double-defaulting for B/C. Support for EG_KERNEL_WHITELIST will be removed in a future release
26+
- "EG_ALLOWED_KERNELS=${EG_ALLOWED_KERNELS:-${EG_KERNEL_WHITELIST:-'r_docker','python_docker','python_tf_docker','python_tf_gpu_docker','scala_docker'}}"
2627
- "EG_MIRROR_WORKING_DIRS=${EG_MIRROR_WORKING_DIRS:-False}"
2728
- "EG_RESPONSE_PORT=${EG_RESPONSE_PORT:-8877}"
2829
- "KG_PORT=${KG_PORT:-8888}"

0 commit comments

Comments
 (0)