diff --git a/.secrets.baseline b/.secrets.baseline index 88318266..97c685ed 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -730,44 +730,7 @@ "filename": "devplanetv2/dev-environments/qa-vpodc-helm/values/values.yaml", "hashed_secret": "4c7e5a7eedc322089e3a64d1911d6f823431162a", "is_verified": false, - "line_number": 1565 - } - ], - "devplanetv2/dev-environments/qaheal-helm/values/values.yaml": [ - { - "type": "Secret Keyword", - "filename": "devplanetv2/dev-environments/qaheal-helm/values/values.yaml", - "hashed_secret": "32b047428182a88c1c20444ccba2e366ebe855ae", - "is_verified": false, - "line_number": 39 - }, - { - "type": "Secret Keyword", - "filename": "devplanetv2/dev-environments/qaheal-helm/values/values.yaml", - "hashed_secret": "ae8eeca6d5b918f3954deee4985bb9b4eb94f19f", - "is_verified": false, - "line_number": 70 - }, - { - "type": "Secret Keyword", - "filename": "devplanetv2/dev-environments/qaheal-helm/values/values.yaml", - "hashed_secret": "5e2583efbf0a6c37d3486e442a2068560d9af734", - "is_verified": false, - "line_number": 71 - }, - { - "type": "Secret Keyword", - "filename": "devplanetv2/dev-environments/qaheal-helm/values/values.yaml", - "hashed_secret": "e6835b1721ceb220059b63c10c55a47c894356a6", - "is_verified": false, - "line_number": 123 - }, - { - "type": "Base64 High Entropy String", - "filename": "devplanetv2/dev-environments/qaheal-helm/values/values.yaml", - "hashed_secret": "a02e8400535b2a6df9421da29ac064ff8a36d8e7", - "is_verified": false, - "line_number": 156 + "line_number": 1573 } ], "devplanetv2/dev-environments/qamickey-helm/values/values.yaml": [ @@ -953,5 +916,5 @@ } ] }, - "generated_at": "2026-08-20T15:56:54Z" + "generated_at": "2026-08-20T15:25:28Z" } diff --git a/devplanetv2/dev-environments/qa-vpodc-helm/values/values.yaml b/devplanetv2/dev-environments/qa-vpodc-helm/values/values.yaml index 6911dd7e..b5b4a2a2 100644 --- a/devplanetv2/dev-environments/qa-vpodc-helm/values/values.yaml +++ b/devplanetv2/dev-environments/qa-vpodc-helm/values/values.yaml @@ -245,6 +245,15 @@ frontend-framework: image: repository: quay.io/cdis/vpodc-data-commons tag: feat-update_oncology_filters + env: + HATCHERY_URL: "http://hatchery-service" + HATCHERY_API: "/lw-workspace" + JUPYTER_GATEWAY_URL: "http://jupyter-enterprise-gateway:8888" + JUPYTER_ASSETS_ROOT_PATH: "/gen3/jupyter-workspaces/assets" + GEN3_ENDPOINT: "https://qa-vpodc.planx-pla.net" + KERNEL_SPEC_POLICY: "{}" + ENABLE_JEG: "true" + guppy: authFilterField: auth_resource_path @@ -1589,6 +1598,205 @@ revproxy: aws: group: devplanetv2 sslPolicy: ELBSecurityPolicy-TLS13-1-2-FIPS-2023-04 + additionalConfigs: + # NOTE: /search/* is not exposed directly by nginx; /guppy/* rewrites to + # /search/* on API Gateway, where the custom authorizer enforces access. + workspace-api.conf: |- + # Free tier is static JupyterLite assets rendered in-browser (WASM only). + # Do not gate these files on workspace launch authz checks. + location ^~ /api/workspace-assets/free/ { + if ($request_method = 'OPTIONS') { + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; + add_header Content-Length 0; + add_header Content-Type text/plain; + return 204; + } + set $upstream http://frontend-framework-service.$namespace.svc.cluster.local; + proxy_pass $upstream; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-URL-SCHEME https; + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; + client_max_body_size 0; + proxy_read_timeout 600s; + } + + location ^~ /workspace-api/workspace-assets/free/ { + if ($request_method = 'OPTIONS') { + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; + add_header Content-Length 0; + add_header Content-Type text/plain; + return 204; + } + set $upstream http://frontend-framework-service.$namespace.svc.cluster.local; + rewrite ^/workspace-api/(.*) /api/$1 break; + proxy_pass $upstream; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-URL-SCHEME https; + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; + client_max_body_size 0; + proxy_read_timeout 600s; + } + + # Remote JupyterLite assets served by workspace-proxy (no auth gate — static files). + location ^~ /api/workspace-assets/remote/ { + if ($request_method = 'OPTIONS') { + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; + add_header Content-Length 0; + add_header Content-Type text/plain; + return 204; + } + set $upstream http://frontend-framework-service.$namespace.svc.cluster.local; + proxy_pass $upstream; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-URL-SCHEME https; + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; + client_max_body_size 0; + proxy_read_timeout 600s; + } + + location ^~ /workspace-api/workspace-assets/remote/ { + if ($request_method = 'OPTIONS') { + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; + add_header Content-Length 0; + add_header Content-Type text/plain; + return 204; + } + set $upstream http://frontend-framework-service.$namespace.svc.cluster.local; + rewrite ^/workspace-api/(.*) /api/$1 break; + proxy_pass $upstream; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-URL-SCHEME https; + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS" always; + add_header Access-Control-Allow-Headers "Authorization, Content-Type" always; + client_max_body_size 0; + proxy_read_timeout 600s; + } + + # Workspace API routes (hatchery options/status/launch, gateway, kernel) are + # handled by the vectis Next.js app API routes — NOT workspace-proxy. + # Next.js rewrites /workspace-api/:path* → /api/:path* internally. + location ^~ /workspace-api/workspace/ { + set $authz_resource "/workspace"; + set $authz_method "access"; + set $authz_service "jupyterhub"; + auth_request /gen3-authz; + error_page 403 = @errorworkspace; + set $upstream http://frontend-framework-service.$namespace.svc.cluster.local; + proxy_pass $upstream; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-URL-SCHEME https; + client_max_body_size 0; + proxy_read_timeout 600s; + } + + # JupyterLab kernel tunnel (workspace-proxy handles per-user routing). + location ^~ /workspace-api/ { + set $authz_resource "/workspace"; + set $authz_method "access"; + set $authz_service "jupyterhub"; + auth_request /gen3-authz; + error_page 403 = @errorworkspace; + set $upstream http://workspace-proxy-service.$namespace.svc.cluster.local:8080; + rewrite ^/workspace-api/(.*) /api/$1 break; + proxy_pass $upstream; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-URL-SCHEME https; + client_max_body_size 0; + proxy_read_timeout 600s; + } + + ambassador-service.conf: |- + location = /lw-workspace/proxy { + return 308 $scheme://$http_host/lw-workspace/proxy/; + } + + location /lw-workspace/proxy/ { + set $authz_resource "/workspace"; + set $authz_method "access"; + set $authz_service "jupyterhub"; + auth_request_set $remoteUser $upstream_http_REMOTE_USER; + auth_request_set $saved_set_cookie $upstream_http_set_cookie; + auth_request /gen3-authz; + + if ($saved_set_cookie != "") { + add_header Set-Cookie $saved_set_cookie always; + } + add_header Cache-Control "no-store"; + + proxy_set_header REMOTE_USER $remoteUser; + error_page 403 = @errorworkspace; + + set $proxy_service "workspace-proxy"; + set $upstream http://workspace-proxy-service.$namespace.svc.cluster.local:8080; + rewrite ^/lw-workspace/proxy/$ /lab break; + rewrite ^/lw-workspace/proxy/(.*) /$1 break; + proxy_pass $upstream; + proxy_redirect http://$host/ https://$host/lw-workspace/proxy/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-URL-SCHEME https; + client_max_body_size 0; + proxy_read_timeout 36000s; + } + sheepdog: enabled: true image: @@ -1613,3 +1821,119 @@ wts: image: repository: 707767160287.dkr.ecr.us-east-1.amazonaws.com/gen3/workspace-token-service tag: "2025.10" + +workspace-proxy: + enabled: true + deploymentNamespace: qa-vpodc-helm + workspaceNamespace: jupyter-pods-qa-vpodc + kubernetesApiServerCIDRs: + - "10.100.0.1/32" # kubernetes ClusterIP (pre-DNAT) + - "172.27.140.0/22" # EKS private subnet (DHCP) + - "172.27.136.0/22" # EKS private subnet (DHCP) + - "172.27.132.0/22" # EKS private subnet (DHCP) + + replicaCount: 1 + + image: + repository: quay.io/cdis/multihead-workspace-proxy + tag: master + pullPolicy: Always + + listenAddr: ":8080" + jegKernelSpecPolicy: | + { + "allowedSpecs": [ + "python_kubernetes", + "python_tensorflow_kubernetes", + "python_tensorflow_gpu_kubernetes" + ] + } + + resources: + requests: + cpu: "50m" + memory: "64Mi" + limits: + cpu: "500m" + memory: "256Mi" + + networkPolicy: + enabled: true + +jeg: + enabled: true + workspaceNamespace: jupyter-pods-qa-vpodc + + replicaCount: 1 + + image: + repository: quay.io/cdis/gen3-vectis + tag: "qa-jegv2" + pullPolicy: Always + + env: + # CRITICAL: must match hatchery user-namespace and workspace-proxy WORKSPACE_NAMESPACE. + EG_NAMESPACE: jupyter-pods-qa-vpodc + EG_KERNEL_LAUNCH_TIMEOUT: "120" + EG_MAX_KERNELS_PER_USER: "2" + EG_CULL_IDLE_TIMEOUT: "14400" + EG_CULL_CONNECTED: "True" + EG_AUTH_TOKEN: "" + EG_DEFAULT_KERNEL_NAME: python3 + EG_LIST_KERNELS: "True" + EG_KERNEL_IMAGE_PULL_POLICY: IfNotPresent + EG_SHARED_NAMESPACE: "True" + EG_MIRROR_WORKING_DIRS: "False" + EG_KERNEL_SPEC_POLICY: | + { + "default_kernel_name": "python3", + "kernel_specs": { + "python3": { + "display_name": "Python (Micro — Local)", + "language": "python" + }, + "python_kubernetes": { + "display_name": "Python on Kubernetes (4Gi, 2 cpu)", + "language": "python", + "image_name": "elyra/kernel-py:latest", + "resources": { + "cpu": "2", + "memory": "4Gi", + "gpu_type": "none" + } + }, + "python_tensorflow_kubernetes": { + "display_name": "Python on Kubernetes with TensorFlow (4Gi, 1 cpu)", + "language": "python", + "image_name": "elyra/kernel-py-tensorflow:latest", + "resources": { + "cpu": "1", + "memory": "4Gi", + "gpu_type": "none" + } + }, + "python_tensorflow_gpu_kubernetes": { + "display_name": "Python on Kubernetes with TensorFlow with GPUs (8Gi, 2 cpu, gpu)", + "language": "python", + "image_name": "elyra/kernel-py-tensorflow:latest", + "resources": { + "cpu": "2", + "memory": "8Gi", + "gpu_type": "NVIDIA" + } + }, + "r_kubernetes": { + "display_name": "R on Kubernetes", + "language": "r", + "image_name": "elyra/kernel-r:latest" + } + } + } + + resources: + requests: + cpu: "100m" + memory: "256Mi" + limits: + cpu: "500m" + memory: "512Mi" \ No newline at end of file