We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Block access to cloud metadata endpoint by default
Currently, we expect users to do extra steps to secure their clusters from users accessing the cloud metadata endpoints (https://zero-to-jupyterhub.readthedocs.io/en/v0.5-doc/security.html#audit-cloud-metadata-server-security). IMO, we should instead do that by default and allow users to opt out of it. Most users won't actually be doing the blocking right now, and run insecure clusters...

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `images/hub/jupyterhub_config.py`
Content:
```
1 import os
2 import glob
3 from tornado.httpclient import AsyncHTTPClient
4 
5 from z2jh import get_config, get_secret
6 
7 # Configure JupyterHub to use the curl backend for making HTTP requests,
8 # rather than the pure-python implementations. The default one starts
9 # being too slow to make a large number of requests to the proxy API
10 # at the rate required.
11 AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
12 
13 c.JupyterHub.spawner_class = 'kubespawner.KubeSpawner'
14 
15 # Connect to a proxy running in a different pod
16 c.ConfigurableHTTPProxy.api_url = 'http://{}:{}'.format(os.environ['PROXY_API_SERVICE_HOST'], int(os.environ['PROXY_API_SERVICE_PORT']))
17 c.ConfigurableHTTPProxy.should_start = False
18 
19 # Do not shut down user pods when hub is restarted
20 c.JupyterHub.cleanup_servers = False
21 
22 # Check that the proxy has routes appropriately setup
23 # This isn't the best named setting :D
24 c.JupyterHub.last_activity_interval = 60
25 
26 # Max number of servers that can be spawning at any one time
27 c.JupyterHub.concurrent_spawn_limit = get_config('hub.concurrent-spawn-limit')
28 
29 active_server_limit = get_config('hub.active-server-limit', None)
30 
31 if active_server_limit is not None:
32     c.JupyterHub.active_server_limit = int(active_server_limit)
33 
34 c.JupyterHub.ip = os.environ['PROXY_PUBLIC_SERVICE_HOST']
35 c.JupyterHub.port = int(os.environ['PROXY_PUBLIC_SERVICE_PORT'])
36 
37 # the hub should listen on all interfaces, so the proxy can access it
38 c.JupyterHub.hub_ip = '0.0.0.0'
39 
40 c.KubeSpawner.namespace = os.environ.get('POD_NAMESPACE', 'default')
41 
42 c.KubeSpawner.start_timeout = get_config('singleuser.start-timeout')
43 
44 # Use env var for this, since we want hub to restart when this changes
45 c.KubeSpawner.singleuser_image_spec = os.environ['SINGLEUSER_IMAGE']
46 
47 c.KubeSpawner.singleuser_extra_labels = get_config('singleuser.extra-labels', {})
48 
49 c.KubeSpawner.singleuser_uid = get_config('singleuser.uid')
50 c.KubeSpawner.singleuser_fs_gid = get_config('singleuser.fs-gid')
51 
52 service_account_name = get_config('singleuser.service-account-name', None)
53 if service_account_name:
54     c.KubeSpawner.singleuser_service_account = service_account_name
55 
56 c.KubeSpawner.singleuser_node_selector = get_config('singleuser.node-selector')
57 # Configure dynamically provisioning pvc
58 storage_type = get_config('singleuser.storage.type')
59 if storage_type == 'dynamic':
60     c.KubeSpawner.pvc_name_template = 'claim-{username}{servername}'
61     c.KubeSpawner.user_storage_pvc_ensure = True
62     storage_class = get_config('singleuser.storage.dynamic.storage-class', None)
63     if storage_class:
64         c.KubeSpawner.user_storage_class = storage_class
65     c.KubeSpawner.user_storage_access_modes = ['ReadWriteOnce']
66     c.KubeSpawner.user_storage_capacity = get_config('singleuser.storage.capacity')
67 
68     # Add volumes to singleuser pods
69     c.KubeSpawner.volumes = [
70         {
71             'name': 'volume-{username}{servername}',
72             'persistentVolumeClaim': {
73                 'claimName': 'claim-{username}{servername}'
74             }
75         }
76     ]
77     c.KubeSpawner.volume_mounts = [
78         {
79             'mountPath': get_config('singleuser.storage.home_mount_path'),
80             'name': 'volume-{username}{servername}'
81         }
82     ]
83 elif storage_type == 'static':
84     pvc_claim_name = get_config('singleuser.storage.static.pvc-name')
85     c.KubeSpawner.volumes = [{
86         'name': 'home',
87         'persistentVolumeClaim': {
88             'claimName': pvc_claim_name
89         }
90     }]
91 
92     c.KubeSpawner.volume_mounts = [{
93         'mountPath': get_config('singleuser.storage.home_mount_path'),
94         'name': 'home',
95         'subPath': get_config('singleuser.storage.static.sub-path')
96     }]
97 
98 c.KubeSpawner.volumes.extend(get_config('singleuser.storage.extra-volumes', []))
99 c.KubeSpawner.volume_mounts.extend(get_config('singleuser.storage.extra-volume-mounts', []))
100 
101 lifecycle_hooks = get_config('singleuser.lifecycle-hooks')
102 if lifecycle_hooks:
103     c.KubeSpawner.singleuser_lifecycle_hooks = lifecycle_hooks
104 
105 init_containers = get_config('singleuser.init-containers')
106 if init_containers:
107     c.KubeSpawner.singleuser_init_containers = init_containers
108 
109 # Gives spawned containers access to the API of the hub
110 c.KubeSpawner.hub_connect_ip = os.environ['HUB_SERVICE_HOST']
111 c.KubeSpawner.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])
112 
113 c.JupyterHub.hub_connect_ip = os.environ['HUB_SERVICE_HOST']
114 c.JupyterHub.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])
115 
116 c.KubeSpawner.mem_limit = get_config('singleuser.memory.limit')
117 c.KubeSpawner.mem_guarantee = get_config('singleuser.memory.guarantee')
118 c.KubeSpawner.cpu_limit = get_config('singleuser.cpu.limit')
119 c.KubeSpawner.cpu_guarantee = get_config('singleuser.cpu.guarantee')
120 
121 # Allow switching authenticators easily
122 auth_type = get_config('auth.type')
123 email_domain = 'local'
124 
125 if auth_type == 'google':
126     c.JupyterHub.authenticator_class = 'oauthenticator.GoogleOAuthenticator'
127     c.GoogleOAuthenticator.client_id = get_config('auth.google.client-id')
128     c.GoogleOAuthenticator.client_secret = get_config('auth.google.client-secret')
129     c.GoogleOAuthenticator.oauth_callback_url = get_config('auth.google.callback-url')
130     c.GoogleOAuthenticator.hosted_domain = get_config('auth.google.hosted-domain')
131     c.GoogleOAuthenticator.login_service = get_config('auth.google.login-service')
132     email_domain = get_config('auth.google.hosted-domain')
133 elif auth_type == 'github':
134     c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'
135     c.GitHubOAuthenticator.oauth_callback_url = get_config('auth.github.callback-url')
136     c.GitHubOAuthenticator.client_id = get_config('auth.github.client-id')
137     c.GitHubOAuthenticator.client_secret = get_config('auth.github.client-secret')
138 elif auth_type == 'cilogon':
139     c.JupyterHub.authenticator_class = 'oauthenticator.CILogonOAuthenticator'
140     c.CILogonOAuthenticator.oauth_callback_url = get_config('auth.cilogon.callback-url')
141     c.CILogonOAuthenticator.client_id = get_config('auth.cilogon.client-id')
142     c.CILogonOAuthenticator.client_secret = get_config('auth.cilogon.client-secret')
143 elif auth_type == 'gitlab':
144     c.JupyterHub.authenticator_class = 'oauthenticator.gitlab.GitLabOAuthenticator'
145     c.GitLabOAuthenticator.oauth_callback_url = get_config('auth.gitlab.callback-url')
146     c.GitLabOAuthenticator.client_id = get_config('auth.gitlab.client-id')
147     c.GitLabOAuthenticator.client_secret = get_config('auth.gitlab.client-secret')
148 elif auth_type == 'mediawiki':
149     c.JupyterHub.authenticator_class = 'oauthenticator.mediawiki.MWOAuthenticator'
150     c.MWOAuthenticator.client_id = get_config('auth.mediawiki.client-id')
151     c.MWOAuthenticator.client_secret = get_config('auth.mediawiki.client-secret')
152     c.MWOAuthenticator.index_url = get_config('auth.mediawiki.index-url')
153 elif auth_type == 'globus':
154     c.JupyterHub.authenticator_class = 'oauthenticator.globus.GlobusOAuthenticator'
155     c.GlobusOAuthenticator.oauth_callback_url = get_config('auth.globus.callback-url')
156     c.GlobusOAuthenticator.client_id = get_config('auth.globus.client-id')
157     c.GlobusOAuthenticator.client_secret = get_config('auth.globus.client-secret')
158     c.GlobusOAuthenticator.identity_provider = get_config('auth.globus.identity-provider', '')
159 elif auth_type == 'hmac':
160     c.JupyterHub.authenticator_class = 'hmacauthenticator.HMACAuthenticator'
161     c.HMACAuthenticator.secret_key = bytes.fromhex(get_config('auth.hmac.secret-key'))
162 elif auth_type == 'dummy':
163     c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'
164     c.DummyAuthenticator.password = get_config('auth.dummy.password', None)
165 elif auth_type == 'tmp':
166     c.JupyterHub.authenticator_class = 'tmpauthenticator.TmpAuthenticator'
167 elif auth_type == 'lti':
168     c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator'
169     c.LTIAuthenticator.consumers = get_config('auth.lti.consumers')
170 elif auth_type == 'custom':
171     # full_class_name looks like "myauthenticator.MyAuthenticator".
172     # To create a docker image with this class availabe, you can just have the
173     # following Dockerifle:
174     #   FROM jupyterhub/k8s-hub:v0.4
175     #   RUN pip3 install myauthenticator
176     full_class_name = get_config('auth.custom.class-name')
177     c.JupyterHub.authenticator_class = full_class_name
178     auth_class_name = full_class_name.rsplit('.', 1)[-1]
179     auth_config = c[auth_class_name]
180     auth_config.update(get_config('auth.custom.config') or {})
181 else:
182     raise ValueError("Unhandled auth type: %r" % auth_type)
183 
184 c.Authenticator.enable_auth_state = get_config('auth.state.enabled', False)
185 
186 def generate_user_email(spawner):
187     """
188     Used as the EMAIL environment variable
189     """
190     return '{username}@{domain}'.format(
191         username=spawner.user.name, domain=email_domain
192     )
193 
194 def generate_user_name(spawner):
195     """
196     Used as GIT_AUTHOR_NAME and GIT_COMMITTER_NAME environment variables
197     """
198     return spawner.user.name
199 
200 c.KubeSpawner.environment = {
201     'EMAIL': generate_user_email,
202     # git requires these committer attributes
203     'GIT_AUTHOR_NAME': generate_user_name,
204     'GIT_COMMITTER_NAME': generate_user_name
205 }
206 
207 c.KubeSpawner.environment.update(get_config('singleuser.extra-env', {}))
208 
209 # Enable admins to access user servers
210 c.JupyterHub.admin_access = get_config('auth.admin.access')
211 c.Authenticator.admin_users = get_config('auth.admin.users', [])
212 c.Authenticator.whitelist = get_config('auth.whitelist.users', [])
213 
214 c.JupyterHub.base_url = get_config('hub.base_url')
215 
216 c.JupyterHub.services = []
217 
218 if get_config('cull.enabled', False):
219     cull_timeout = get_config('cull.timeout')
220     cull_every = get_config('cull.every')
221     cull_cmd = [
222         '/usr/local/bin/cull_idle_servers.py',
223         '--timeout=%s' % cull_timeout,
224         '--cull-every=%s' % cull_every,
225         '--url=http://127.0.0.1:8081' + c.JupyterHub.base_url + 'hub/api'
226     ]
227     if get_config('cull.users'):
228         cull_cmd.append('--cull-users')
229     c.JupyterHub.services.append({
230         'name': 'cull-idle',
231         'admin': True,
232         'command': cull_cmd,
233     })
234 
235 for name, service in get_config('hub.services', {}).items():
236     api_token = get_secret('services.token.%s' % name)
237     # jupyterhub.services is a list of dicts, but
238     # in the helm chart it is a dict of dicts for easier merged-config
239     service.setdefault('name', name)
240     if api_token:
241         service['api_token'] = api_token
242     c.JupyterHub.services.append(service)
243 
244 
245 c.JupyterHub.db_url = get_config('hub.db_url')
246 
247 cmd = get_config('singleuser.cmd', None)
248 if cmd:
249     c.Spawner.cmd = cmd
250 
251 default_url = get_config('singleuser.default-url', None)
252 if default_url:
253     c.Spawner.default_url = default_url
254 
255 scheduler_strategy = get_config('singleuser.scheduler-strategy', 'spread')
256 
257 if scheduler_strategy == 'pack':
258     # FIXME: Support setting affinity directly in KubeSpawner
259     c.KubeSpawner.singleuser_extra_pod_config = {
260         'affinity': {
261             'podAffinity': {
262                 'preferredDuringSchedulingIgnoredDuringExecution': [{
263                     'weight': 100,
264                     'podAffinityTerm': {
265                         'labelSelector': {
266                             'matchExpressions': [{
267                                 'key': 'component',
268                                 'operator': 'In',
269                                 'values': ['singleuser-server']
270                             }]
271                         },
272                         'topologyKey': 'kubernetes.io/hostname'
273                     }
274                 }],
275             }
276         }
277     }
278 else:
279     # Set default to {} so subconfigs can easily update it
280     c.KubeSpawner.singleuser_extra_pod_config = {}
281 
282 extra_configs = sorted(glob.glob('/etc/jupyterhub/config/hub.extra-config.*.py'))
283 for ec in extra_configs:
284     load_subconfig(ec)
285
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/images/hub/jupyterhub_config.py b/images/hub/jupyterhub_config.py
--- a/images/hub/jupyterhub_config.py
+++ b/images/hub/jupyterhub_config.py
@@ -1,6 +1,7 @@
 import os
 import glob
 from tornado.httpclient import AsyncHTTPClient
+from kubernetes import client
 
 from z2jh import get_config, get_secret
 
@@ -104,7 +105,7 @@
 
 init_containers = get_config('singleuser.init-containers')
 if init_containers:
-    c.KubeSpawner.singleuser_init_containers = init_containers
+    c.KubeSpawner.singleuser_init_containers.extend(init_containers)
 
 # Gives spawned containers access to the API of the hub
 c.KubeSpawner.hub_connect_ip = os.environ['HUB_SERVICE_HOST']
@@ -252,6 +253,30 @@
 if default_url:
     c.Spawner.default_url = default_url
 
+cloud_metadata = get_config('singleuser.cloud-metadata', {})
+
+if not cloud_metadata.get('enabled', False):
+    # Use iptables to block access to cloud metadata by default
+    network_tools_image_name = get_config('singleuser.network-tools.image.name')
+    network_tools_image_tag = get_config('singleuser.network-tools.image.tag')
+    ip_block_container = client.V1Container(
+        name="block-cloud-metadata",
+        image=f"{network_tools_image_name}:{network_tools_image_tag}",
+        command=[
+            'iptables',
+            '-A', 'OUTPUT',
+            '-d', cloud_metadata.get('ip', '169.254.169.254'),
+            '-j', 'DROP'
+        ],
+        security_context=client.V1SecurityContext(
+            privileged=True,
+            run_as_user=0,
+            capabilities=client.V1Capabilities(add=['NET_ADMIN'])
+        )
+    )
+
+    c.KubeSpawner.singleuser_init_containers.append(ip_block_container)
+
 scheduler_strategy = get_config('singleuser.scheduler-strategy', 'spread')
 
 if scheduler_strategy == 'pack':

{"golden_diff": "diff --git a/images/hub/jupyterhub_config.py b/images/hub/jupyterhub_config.py\n--- a/images/hub/jupyterhub_config.py\n+++ b/images/hub/jupyterhub_config.py\n@@ -1,6 +1,7 @@\n import os\n import glob\n from tornado.httpclient import AsyncHTTPClient\n+from kubernetes import client\n \n from z2jh import get_config, get_secret\n \n@@ -104,7 +105,7 @@\n \n init_containers = get_config('singleuser.init-containers')\n if init_containers:\n-    c.KubeSpawner.singleuser_init_containers = init_containers\n+    c.KubeSpawner.singleuser_init_containers.extend(init_containers)\n \n # Gives spawned containers access to the API of the hub\n c.KubeSpawner.hub_connect_ip = os.environ['HUB_SERVICE_HOST']\n@@ -252,6 +253,30 @@\n if default_url:\n     c.Spawner.default_url = default_url\n \n+cloud_metadata = get_config('singleuser.cloud-metadata', {})\n+\n+if not cloud_metadata.get('enabled', False):\n+    # Use iptables to block access to cloud metadata by default\n+    network_tools_image_name = get_config('singleuser.network-tools.image.name')\n+    network_tools_image_tag = get_config('singleuser.network-tools.image.tag')\n+    ip_block_container = client.V1Container(\n+        name=\"block-cloud-metadata\",\n+        image=f\"{network_tools_image_name}:{network_tools_image_tag}\",\n+        command=[\n+            'iptables',\n+            '-A', 'OUTPUT',\n+            '-d', cloud_metadata.get('ip', '169.254.169.254'),\n+            '-j', 'DROP'\n+        ],\n+        security_context=client.V1SecurityContext(\n+            privileged=True,\n+            run_as_user=0,\n+            capabilities=client.V1Capabilities(add=['NET_ADMIN'])\n+        )\n+    )\n+\n+    c.KubeSpawner.singleuser_init_containers.append(ip_block_container)\n+\n scheduler_strategy = get_config('singleuser.scheduler-strategy', 'spread')\n \n if scheduler_strategy == 'pack':\n", "issue": "Block access to cloud metadata endpoint by default\nCurrently, we expect users to do extra steps to secure their clusters from users accessing the cloud metadata endpoints (https://zero-to-jupyterhub.readthedocs.io/en/v0.5-doc/security.html#audit-cloud-metadata-server-security). IMO, we should instead do that by default and allow users to opt out of it. Most users won't actually be doing the blocking right now, and run insecure clusters...\n", "before_files": [{"content": "import os\nimport glob\nfrom tornado.httpclient import AsyncHTTPClient\n\nfrom z2jh import get_config, get_secret\n\n# Configure JupyterHub to use the curl backend for making HTTP requests,\n# rather than the pure-python implementations. The default one starts\n# being too slow to make a large number of requests to the proxy API\n# at the rate required.\nAsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")\n\nc.JupyterHub.spawner_class = 'kubespawner.KubeSpawner'\n\n# Connect to a proxy running in a different pod\nc.ConfigurableHTTPProxy.api_url = 'http://{}:{}'.format(os.environ['PROXY_API_SERVICE_HOST'], int(os.environ['PROXY_API_SERVICE_PORT']))\nc.ConfigurableHTTPProxy.should_start = False\n\n# Do not shut down user pods when hub is restarted\nc.JupyterHub.cleanup_servers = False\n\n# Check that the proxy has routes appropriately setup\n# This isn't the best named setting :D\nc.JupyterHub.last_activity_interval = 60\n\n# Max number of servers that can be spawning at any one time\nc.JupyterHub.concurrent_spawn_limit = get_config('hub.concurrent-spawn-limit')\n\nactive_server_limit = get_config('hub.active-server-limit', None)\n\nif active_server_limit is not None:\n    c.JupyterHub.active_server_limit = int(active_server_limit)\n\nc.JupyterHub.ip = os.environ['PROXY_PUBLIC_SERVICE_HOST']\nc.JupyterHub.port = int(os.environ['PROXY_PUBLIC_SERVICE_PORT'])\n\n# the hub should listen on all interfaces, so the proxy can access it\nc.JupyterHub.hub_ip = '0.0.0.0'\n\nc.KubeSpawner.namespace = os.environ.get('POD_NAMESPACE', 'default')\n\nc.KubeSpawner.start_timeout = get_config('singleuser.start-timeout')\n\n# Use env var for this, since we want hub to restart when this changes\nc.KubeSpawner.singleuser_image_spec = os.environ['SINGLEUSER_IMAGE']\n\nc.KubeSpawner.singleuser_extra_labels = get_config('singleuser.extra-labels', {})\n\nc.KubeSpawner.singleuser_uid = get_config('singleuser.uid')\nc.KubeSpawner.singleuser_fs_gid = get_config('singleuser.fs-gid')\n\nservice_account_name = get_config('singleuser.service-account-name', None)\nif service_account_name:\n    c.KubeSpawner.singleuser_service_account = service_account_name\n\nc.KubeSpawner.singleuser_node_selector = get_config('singleuser.node-selector')\n# Configure dynamically provisioning pvc\nstorage_type = get_config('singleuser.storage.type')\nif storage_type == 'dynamic':\n    c.KubeSpawner.pvc_name_template = 'claim-{username}{servername}'\n    c.KubeSpawner.user_storage_pvc_ensure = True\n    storage_class = get_config('singleuser.storage.dynamic.storage-class', None)\n    if storage_class:\n        c.KubeSpawner.user_storage_class = storage_class\n    c.KubeSpawner.user_storage_access_modes = ['ReadWriteOnce']\n    c.KubeSpawner.user_storage_capacity = get_config('singleuser.storage.capacity')\n\n    # Add volumes to singleuser pods\n    c.KubeSpawner.volumes = [\n        {\n            'name': 'volume-{username}{servername}',\n            'persistentVolumeClaim': {\n                'claimName': 'claim-{username}{servername}'\n            }\n        }\n    ]\n    c.KubeSpawner.volume_mounts = [\n        {\n            'mountPath': get_config('singleuser.storage.home_mount_path'),\n            'name': 'volume-{username}{servername}'\n        }\n    ]\nelif storage_type == 'static':\n    pvc_claim_name = get_config('singleuser.storage.static.pvc-name')\n    c.KubeSpawner.volumes = [{\n        'name': 'home',\n        'persistentVolumeClaim': {\n            'claimName': pvc_claim_name\n        }\n    }]\n\n    c.KubeSpawner.volume_mounts = [{\n        'mountPath': get_config('singleuser.storage.home_mount_path'),\n        'name': 'home',\n        'subPath': get_config('singleuser.storage.static.sub-path')\n    }]\n\nc.KubeSpawner.volumes.extend(get_config('singleuser.storage.extra-volumes', []))\nc.KubeSpawner.volume_mounts.extend(get_config('singleuser.storage.extra-volume-mounts', []))\n\nlifecycle_hooks = get_config('singleuser.lifecycle-hooks')\nif lifecycle_hooks:\n    c.KubeSpawner.singleuser_lifecycle_hooks = lifecycle_hooks\n\ninit_containers = get_config('singleuser.init-containers')\nif init_containers:\n    c.KubeSpawner.singleuser_init_containers = init_containers\n\n# Gives spawned containers access to the API of the hub\nc.KubeSpawner.hub_connect_ip = os.environ['HUB_SERVICE_HOST']\nc.KubeSpawner.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])\n\nc.JupyterHub.hub_connect_ip = os.environ['HUB_SERVICE_HOST']\nc.JupyterHub.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])\n\nc.KubeSpawner.mem_limit = get_config('singleuser.memory.limit')\nc.KubeSpawner.mem_guarantee = get_config('singleuser.memory.guarantee')\nc.KubeSpawner.cpu_limit = get_config('singleuser.cpu.limit')\nc.KubeSpawner.cpu_guarantee = get_config('singleuser.cpu.guarantee')\n\n# Allow switching authenticators easily\nauth_type = get_config('auth.type')\nemail_domain = 'local'\n\nif auth_type == 'google':\n    c.JupyterHub.authenticator_class = 'oauthenticator.GoogleOAuthenticator'\n    c.GoogleOAuthenticator.client_id = get_config('auth.google.client-id')\n    c.GoogleOAuthenticator.client_secret = get_config('auth.google.client-secret')\n    c.GoogleOAuthenticator.oauth_callback_url = get_config('auth.google.callback-url')\n    c.GoogleOAuthenticator.hosted_domain = get_config('auth.google.hosted-domain')\n    c.GoogleOAuthenticator.login_service = get_config('auth.google.login-service')\n    email_domain = get_config('auth.google.hosted-domain')\nelif auth_type == 'github':\n    c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'\n    c.GitHubOAuthenticator.oauth_callback_url = get_config('auth.github.callback-url')\n    c.GitHubOAuthenticator.client_id = get_config('auth.github.client-id')\n    c.GitHubOAuthenticator.client_secret = get_config('auth.github.client-secret')\nelif auth_type == 'cilogon':\n    c.JupyterHub.authenticator_class = 'oauthenticator.CILogonOAuthenticator'\n    c.CILogonOAuthenticator.oauth_callback_url = get_config('auth.cilogon.callback-url')\n    c.CILogonOAuthenticator.client_id = get_config('auth.cilogon.client-id')\n    c.CILogonOAuthenticator.client_secret = get_config('auth.cilogon.client-secret')\nelif auth_type == 'gitlab':\n    c.JupyterHub.authenticator_class = 'oauthenticator.gitlab.GitLabOAuthenticator'\n    c.GitLabOAuthenticator.oauth_callback_url = get_config('auth.gitlab.callback-url')\n    c.GitLabOAuthenticator.client_id = get_config('auth.gitlab.client-id')\n    c.GitLabOAuthenticator.client_secret = get_config('auth.gitlab.client-secret')\nelif auth_type == 'mediawiki':\n    c.JupyterHub.authenticator_class = 'oauthenticator.mediawiki.MWOAuthenticator'\n    c.MWOAuthenticator.client_id = get_config('auth.mediawiki.client-id')\n    c.MWOAuthenticator.client_secret = get_config('auth.mediawiki.client-secret')\n    c.MWOAuthenticator.index_url = get_config('auth.mediawiki.index-url')\nelif auth_type == 'globus':\n    c.JupyterHub.authenticator_class = 'oauthenticator.globus.GlobusOAuthenticator'\n    c.GlobusOAuthenticator.oauth_callback_url = get_config('auth.globus.callback-url')\n    c.GlobusOAuthenticator.client_id = get_config('auth.globus.client-id')\n    c.GlobusOAuthenticator.client_secret = get_config('auth.globus.client-secret')\n    c.GlobusOAuthenticator.identity_provider = get_config('auth.globus.identity-provider', '')\nelif auth_type == 'hmac':\n    c.JupyterHub.authenticator_class = 'hmacauthenticator.HMACAuthenticator'\n    c.HMACAuthenticator.secret_key = bytes.fromhex(get_config('auth.hmac.secret-key'))\nelif auth_type == 'dummy':\n    c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'\n    c.DummyAuthenticator.password = get_config('auth.dummy.password', None)\nelif auth_type == 'tmp':\n    c.JupyterHub.authenticator_class = 'tmpauthenticator.TmpAuthenticator'\nelif auth_type == 'lti':\n    c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator'\n    c.LTIAuthenticator.consumers = get_config('auth.lti.consumers')\nelif auth_type == 'custom':\n    # full_class_name looks like \"myauthenticator.MyAuthenticator\".\n    # To create a docker image with this class availabe, you can just have the\n    # following Dockerifle:\n    #   FROM jupyterhub/k8s-hub:v0.4\n    #   RUN pip3 install myauthenticator\n    full_class_name = get_config('auth.custom.class-name')\n    c.JupyterHub.authenticator_class = full_class_name\n    auth_class_name = full_class_name.rsplit('.', 1)[-1]\n    auth_config = c[auth_class_name]\n    auth_config.update(get_config('auth.custom.config') or {})\nelse:\n    raise ValueError(\"Unhandled auth type: %r\" % auth_type)\n\nc.Authenticator.enable_auth_state = get_config('auth.state.enabled', False)\n\ndef generate_user_email(spawner):\n    \"\"\"\n    Used as the EMAIL environment variable\n    \"\"\"\n    return '{username}@{domain}'.format(\n        username=spawner.user.name, domain=email_domain\n    )\n\ndef generate_user_name(spawner):\n    \"\"\"\n    Used as GIT_AUTHOR_NAME and GIT_COMMITTER_NAME environment variables\n    \"\"\"\n    return spawner.user.name\n\nc.KubeSpawner.environment = {\n    'EMAIL': generate_user_email,\n    # git requires these committer attributes\n    'GIT_AUTHOR_NAME': generate_user_name,\n    'GIT_COMMITTER_NAME': generate_user_name\n}\n\nc.KubeSpawner.environment.update(get_config('singleuser.extra-env', {}))\n\n# Enable admins to access user servers\nc.JupyterHub.admin_access = get_config('auth.admin.access')\nc.Authenticator.admin_users = get_config('auth.admin.users', [])\nc.Authenticator.whitelist = get_config('auth.whitelist.users', [])\n\nc.JupyterHub.base_url = get_config('hub.base_url')\n\nc.JupyterHub.services = []\n\nif get_config('cull.enabled', False):\n    cull_timeout = get_config('cull.timeout')\n    cull_every = get_config('cull.every')\n    cull_cmd = [\n        '/usr/local/bin/cull_idle_servers.py',\n        '--timeout=%s' % cull_timeout,\n        '--cull-every=%s' % cull_every,\n        '--url=http://127.0.0.1:8081' + c.JupyterHub.base_url + 'hub/api'\n    ]\n    if get_config('cull.users'):\n        cull_cmd.append('--cull-users')\n    c.JupyterHub.services.append({\n        'name': 'cull-idle',\n        'admin': True,\n        'command': cull_cmd,\n    })\n\nfor name, service in get_config('hub.services', {}).items():\n    api_token = get_secret('services.token.%s' % name)\n    # jupyterhub.services is a list of dicts, but\n    # in the helm chart it is a dict of dicts for easier merged-config\n    service.setdefault('name', name)\n    if api_token:\n        service['api_token'] = api_token\n    c.JupyterHub.services.append(service)\n\n\nc.JupyterHub.db_url = get_config('hub.db_url')\n\ncmd = get_config('singleuser.cmd', None)\nif cmd:\n    c.Spawner.cmd = cmd\n\ndefault_url = get_config('singleuser.default-url', None)\nif default_url:\n    c.Spawner.default_url = default_url\n\nscheduler_strategy = get_config('singleuser.scheduler-strategy', 'spread')\n\nif scheduler_strategy == 'pack':\n    # FIXME: Support setting affinity directly in KubeSpawner\n    c.KubeSpawner.singleuser_extra_pod_config = {\n        'affinity': {\n            'podAffinity': {\n                'preferredDuringSchedulingIgnoredDuringExecution': [{\n                    'weight': 100,\n                    'podAffinityTerm': {\n                        'labelSelector': {\n                            'matchExpressions': [{\n                                'key': 'component',\n                                'operator': 'In',\n                                'values': ['singleuser-server']\n                            }]\n                        },\n                        'topologyKey': 'kubernetes.io/hostname'\n                    }\n                }],\n            }\n        }\n    }\nelse:\n    # Set default to {} so subconfigs can easily update it\n    c.KubeSpawner.singleuser_extra_pod_config = {}\n\nextra_configs = sorted(glob.glob('/etc/jupyterhub/config/hub.extra-config.*.py'))\nfor ec in extra_configs:\n    load_subconfig(ec)\n", "path": "images/hub/jupyterhub_config.py"}], "after_files": [{"content": "import os\nimport glob\nfrom tornado.httpclient import AsyncHTTPClient\nfrom kubernetes import client\n\nfrom z2jh import get_config, get_secret\n\n# Configure JupyterHub to use the curl backend for making HTTP requests,\n# rather than the pure-python implementations. The default one starts\n# being too slow to make a large number of requests to the proxy API\n# at the rate required.\nAsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")\n\nc.JupyterHub.spawner_class = 'kubespawner.KubeSpawner'\n\n# Connect to a proxy running in a different pod\nc.ConfigurableHTTPProxy.api_url = 'http://{}:{}'.format(os.environ['PROXY_API_SERVICE_HOST'], int(os.environ['PROXY_API_SERVICE_PORT']))\nc.ConfigurableHTTPProxy.should_start = False\n\n# Do not shut down user pods when hub is restarted\nc.JupyterHub.cleanup_servers = False\n\n# Check that the proxy has routes appropriately setup\n# This isn't the best named setting :D\nc.JupyterHub.last_activity_interval = 60\n\n# Max number of servers that can be spawning at any one time\nc.JupyterHub.concurrent_spawn_limit = get_config('hub.concurrent-spawn-limit')\n\nactive_server_limit = get_config('hub.active-server-limit', None)\n\nif active_server_limit is not None:\n    c.JupyterHub.active_server_limit = int(active_server_limit)\n\nc.JupyterHub.ip = os.environ['PROXY_PUBLIC_SERVICE_HOST']\nc.JupyterHub.port = int(os.environ['PROXY_PUBLIC_SERVICE_PORT'])\n\n# the hub should listen on all interfaces, so the proxy can access it\nc.JupyterHub.hub_ip = '0.0.0.0'\n\nc.KubeSpawner.namespace = os.environ.get('POD_NAMESPACE', 'default')\n\nc.KubeSpawner.start_timeout = get_config('singleuser.start-timeout')\n\n# Use env var for this, since we want hub to restart when this changes\nc.KubeSpawner.singleuser_image_spec = os.environ['SINGLEUSER_IMAGE']\n\nc.KubeSpawner.singleuser_extra_labels = get_config('singleuser.extra-labels', {})\n\nc.KubeSpawner.singleuser_uid = get_config('singleuser.uid')\nc.KubeSpawner.singleuser_fs_gid = get_config('singleuser.fs-gid')\n\nservice_account_name = get_config('singleuser.service-account-name', None)\nif service_account_name:\n    c.KubeSpawner.singleuser_service_account = service_account_name\n\nc.KubeSpawner.singleuser_node_selector = get_config('singleuser.node-selector')\n# Configure dynamically provisioning pvc\nstorage_type = get_config('singleuser.storage.type')\nif storage_type == 'dynamic':\n    c.KubeSpawner.pvc_name_template = 'claim-{username}{servername}'\n    c.KubeSpawner.user_storage_pvc_ensure = True\n    storage_class = get_config('singleuser.storage.dynamic.storage-class', None)\n    if storage_class:\n        c.KubeSpawner.user_storage_class = storage_class\n    c.KubeSpawner.user_storage_access_modes = ['ReadWriteOnce']\n    c.KubeSpawner.user_storage_capacity = get_config('singleuser.storage.capacity')\n\n    # Add volumes to singleuser pods\n    c.KubeSpawner.volumes = [\n        {\n            'name': 'volume-{username}{servername}',\n            'persistentVolumeClaim': {\n                'claimName': 'claim-{username}{servername}'\n            }\n        }\n    ]\n    c.KubeSpawner.volume_mounts = [\n        {\n            'mountPath': get_config('singleuser.storage.home_mount_path'),\n            'name': 'volume-{username}{servername}'\n        }\n    ]\nelif storage_type == 'static':\n    pvc_claim_name = get_config('singleuser.storage.static.pvc-name')\n    c.KubeSpawner.volumes = [{\n        'name': 'home',\n        'persistentVolumeClaim': {\n            'claimName': pvc_claim_name\n        }\n    }]\n\n    c.KubeSpawner.volume_mounts = [{\n        'mountPath': get_config('singleuser.storage.home_mount_path'),\n        'name': 'home',\n        'subPath': get_config('singleuser.storage.static.sub-path')\n    }]\n\nc.KubeSpawner.volumes.extend(get_config('singleuser.storage.extra-volumes', []))\nc.KubeSpawner.volume_mounts.extend(get_config('singleuser.storage.extra-volume-mounts', []))\n\nlifecycle_hooks = get_config('singleuser.lifecycle-hooks')\nif lifecycle_hooks:\n    c.KubeSpawner.singleuser_lifecycle_hooks = lifecycle_hooks\n\ninit_containers = get_config('singleuser.init-containers')\nif init_containers:\n    c.KubeSpawner.singleuser_init_containers.extend(init_containers)\n\n# Gives spawned containers access to the API of the hub\nc.KubeSpawner.hub_connect_ip = os.environ['HUB_SERVICE_HOST']\nc.KubeSpawner.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])\n\nc.JupyterHub.hub_connect_ip = os.environ['HUB_SERVICE_HOST']\nc.JupyterHub.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])\n\nc.KubeSpawner.mem_limit = get_config('singleuser.memory.limit')\nc.KubeSpawner.mem_guarantee = get_config('singleuser.memory.guarantee')\nc.KubeSpawner.cpu_limit = get_config('singleuser.cpu.limit')\nc.KubeSpawner.cpu_guarantee = get_config('singleuser.cpu.guarantee')\n\n# Allow switching authenticators easily\nauth_type = get_config('auth.type')\nemail_domain = 'local'\n\nif auth_type == 'google':\n    c.JupyterHub.authenticator_class = 'oauthenticator.GoogleOAuthenticator'\n    c.GoogleOAuthenticator.client_id = get_config('auth.google.client-id')\n    c.GoogleOAuthenticator.client_secret = get_config('auth.google.client-secret')\n    c.GoogleOAuthenticator.oauth_callback_url = get_config('auth.google.callback-url')\n    c.GoogleOAuthenticator.hosted_domain = get_config('auth.google.hosted-domain')\n    c.GoogleOAuthenticator.login_service = get_config('auth.google.login-service')\n    email_domain = get_config('auth.google.hosted-domain')\nelif auth_type == 'github':\n    c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'\n    c.GitHubOAuthenticator.oauth_callback_url = get_config('auth.github.callback-url')\n    c.GitHubOAuthenticator.client_id = get_config('auth.github.client-id')\n    c.GitHubOAuthenticator.client_secret = get_config('auth.github.client-secret')\nelif auth_type == 'cilogon':\n    c.JupyterHub.authenticator_class = 'oauthenticator.CILogonOAuthenticator'\n    c.CILogonOAuthenticator.oauth_callback_url = get_config('auth.cilogon.callback-url')\n    c.CILogonOAuthenticator.client_id = get_config('auth.cilogon.client-id')\n    c.CILogonOAuthenticator.client_secret = get_config('auth.cilogon.client-secret')\nelif auth_type == 'gitlab':\n    c.JupyterHub.authenticator_class = 'oauthenticator.gitlab.GitLabOAuthenticator'\n    c.GitLabOAuthenticator.oauth_callback_url = get_config('auth.gitlab.callback-url')\n    c.GitLabOAuthenticator.client_id = get_config('auth.gitlab.client-id')\n    c.GitLabOAuthenticator.client_secret = get_config('auth.gitlab.client-secret')\nelif auth_type == 'mediawiki':\n    c.JupyterHub.authenticator_class = 'oauthenticator.mediawiki.MWOAuthenticator'\n    c.MWOAuthenticator.client_id = get_config('auth.mediawiki.client-id')\n    c.MWOAuthenticator.client_secret = get_config('auth.mediawiki.client-secret')\n    c.MWOAuthenticator.index_url = get_config('auth.mediawiki.index-url')\nelif auth_type == 'globus':\n    c.JupyterHub.authenticator_class = 'oauthenticator.globus.GlobusOAuthenticator'\n    c.GlobusOAuthenticator.oauth_callback_url = get_config('auth.globus.callback-url')\n    c.GlobusOAuthenticator.client_id = get_config('auth.globus.client-id')\n    c.GlobusOAuthenticator.client_secret = get_config('auth.globus.client-secret')\n    c.GlobusOAuthenticator.identity_provider = get_config('auth.globus.identity-provider', '')\nelif auth_type == 'hmac':\n    c.JupyterHub.authenticator_class = 'hmacauthenticator.HMACAuthenticator'\n    c.HMACAuthenticator.secret_key = bytes.fromhex(get_config('auth.hmac.secret-key'))\nelif auth_type == 'dummy':\n    c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'\n    c.DummyAuthenticator.password = get_config('auth.dummy.password', None)\nelif auth_type == 'tmp':\n    c.JupyterHub.authenticator_class = 'tmpauthenticator.TmpAuthenticator'\nelif auth_type == 'lti':\n    c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator'\n    c.LTIAuthenticator.consumers = get_config('auth.lti.consumers')\nelif auth_type == 'custom':\n    # full_class_name looks like \"myauthenticator.MyAuthenticator\".\n    # To create a docker image with this class availabe, you can just have the\n    # following Dockerifle:\n    #   FROM jupyterhub/k8s-hub:v0.4\n    #   RUN pip3 install myauthenticator\n    full_class_name = get_config('auth.custom.class-name')\n    c.JupyterHub.authenticator_class = full_class_name\n    auth_class_name = full_class_name.rsplit('.', 1)[-1]\n    auth_config = c[auth_class_name]\n    auth_config.update(get_config('auth.custom.config') or {})\nelse:\n    raise ValueError(\"Unhandled auth type: %r\" % auth_type)\n\nc.Authenticator.enable_auth_state = get_config('auth.state.enabled', False)\n\ndef generate_user_email(spawner):\n    \"\"\"\n    Used as the EMAIL environment variable\n    \"\"\"\n    return '{username}@{domain}'.format(\n        username=spawner.user.name, domain=email_domain\n    )\n\ndef generate_user_name(spawner):\n    \"\"\"\n    Used as GIT_AUTHOR_NAME and GIT_COMMITTER_NAME environment variables\n    \"\"\"\n    return spawner.user.name\n\nc.KubeSpawner.environment = {\n    'EMAIL': generate_user_email,\n    # git requires these committer attributes\n    'GIT_AUTHOR_NAME': generate_user_name,\n    'GIT_COMMITTER_NAME': generate_user_name\n}\n\nc.KubeSpawner.environment.update(get_config('singleuser.extra-env', {}))\n\n# Enable admins to access user servers\nc.JupyterHub.admin_access = get_config('auth.admin.access')\nc.Authenticator.admin_users = get_config('auth.admin.users', [])\nc.Authenticator.whitelist = get_config('auth.whitelist.users', [])\n\nc.JupyterHub.base_url = get_config('hub.base_url')\n\nc.JupyterHub.services = []\n\nif get_config('cull.enabled', False):\n    cull_timeout = get_config('cull.timeout')\n    cull_every = get_config('cull.every')\n    cull_cmd = [\n        '/usr/local/bin/cull_idle_servers.py',\n        '--timeout=%s' % cull_timeout,\n        '--cull-every=%s' % cull_every,\n        '--url=http://127.0.0.1:8081' + c.JupyterHub.base_url + 'hub/api'\n    ]\n    if get_config('cull.users'):\n        cull_cmd.append('--cull-users')\n    c.JupyterHub.services.append({\n        'name': 'cull-idle',\n        'admin': True,\n        'command': cull_cmd,\n    })\n\nfor name, service in get_config('hub.services', {}).items():\n    api_token = get_secret('services.token.%s' % name)\n    # jupyterhub.services is a list of dicts, but\n    # in the helm chart it is a dict of dicts for easier merged-config\n    service.setdefault('name', name)\n    if api_token:\n        service['api_token'] = api_token\n    c.JupyterHub.services.append(service)\n\n\nc.JupyterHub.db_url = get_config('hub.db_url')\n\ncmd = get_config('singleuser.cmd', None)\nif cmd:\n    c.Spawner.cmd = cmd\n\ndefault_url = get_config('singleuser.default-url', None)\nif default_url:\n    c.Spawner.default_url = default_url\n\ncloud_metadata = get_config('singleuser.cloud-metadata', {})\n\nif not cloud_metadata.get('enabled', False):\n    # Use iptables to block access to cloud metadata by default\n    network_tools_image_name = get_config('singleuser.network-tools.image.name')\n    network_tools_image_tag = get_config('singleuser.network-tools.image.tag')\n    ip_block_container = client.V1Container(\n        name=\"block-cloud-metadata\",\n        image=f\"{network_tools_image_name}:{network_tools_image_tag}\",\n        command=[\n            'iptables',\n            '-A', 'OUTPUT',\n            '-d', cloud_metadata.get('ip', '169.254.169.254'),\n            '-j', 'DROP'\n        ],\n        security_context=client.V1SecurityContext(\n            privileged=True,\n            run_as_user=0,\n            capabilities=client.V1Capabilities(add=['NET_ADMIN'])\n        )\n    )\n\n    c.KubeSpawner.singleuser_init_containers.append(ip_block_container)\n\nscheduler_strategy = get_config('singleuser.scheduler-strategy', 'spread')\n\nif scheduler_strategy == 'pack':\n    # FIXME: Support setting affinity directly in KubeSpawner\n    c.KubeSpawner.singleuser_extra_pod_config = {\n        'affinity': {\n            'podAffinity': {\n                'preferredDuringSchedulingIgnoredDuringExecution': [{\n                    'weight': 100,\n                    'podAffinityTerm': {\n                        'labelSelector': {\n                            'matchExpressions': [{\n                                'key': 'component',\n                                'operator': 'In',\n                                'values': ['singleuser-server']\n                            }]\n                        },\n                        'topologyKey': 'kubernetes.io/hostname'\n                    }\n                }],\n            }\n        }\n    }\nelse:\n    # Set default to {} so subconfigs can easily update it\n    c.KubeSpawner.singleuser_extra_pod_config = {}\n\nextra_configs = sorted(glob.glob('/etc/jupyterhub/config/hub.extra-config.*.py'))\nfor ec in extra_configs:\n    load_subconfig(ec)\n", "path": "images/hub/jupyterhub_config.py"}]}