Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ description: A Helm chart for deploying HeLx to Kubernetes.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
version: 4.5.9
version: 4.5.10

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A Helm chart for deploying HeLx to Kubernetes.

![Version: 4.5.9](https://img.shields.io/badge/Version-4.5.9-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.6.5](https://img.shields.io/badge/AppVersion-3.6.5-informational?style=flat-square)
![Version: 4.5.10](https://img.shields.io/badge/Version-4.5.10-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.6.5](https://img.shields.io/badge/AppVersion-3.6.5-informational?style=flat-square)

HeLx puts the most advanced analytical scientific models at investigator’s finger tips using equally advanced cloud native, container orchestrated, distributed computing systems. HeLx can be applied in many domains. Its ability to empower researchers to leverage advanced analytical tools without installation or other infrastructure concerns has broad reaching benefits.

Expand Down
2 changes: 1 addition & 1 deletion charts/resty/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: resty
version: "1.0.5"
version: "1.0.6"
appVersion: "2.0.1"
9 changes: 7 additions & 2 deletions charts/resty/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# resty

![Version: 1.0.5](https://img.shields.io/badge/Version-1.0.5-informational?style=flat-square) ![AppVersion: 2.0.1](https://img.shields.io/badge/AppVersion-2.0.1-informational?style=flat-square)
![Version: 1.0.6](https://img.shields.io/badge/Version-1.0.6-informational?style=flat-square) ![AppVersion: 2.0.1](https://img.shields.io/badge/AppVersion-2.0.1-informational?style=flat-square)

A Helm chart for Kubernetes

Expand All @@ -11,6 +11,11 @@ A Helm chart for Kubernetes
| DEV_PHASE.dev | bool | `false` | Set the DEV_PHASE.dev True, if Appstore/Tycho running locally. Else, set it to False |
| airflow.authenticate | bool | `true` | |
| artifactCache | object | `{"authenticate":false,"enabled":false,"port":8080,"serviceName":"artifact-cache"}` | Optional /artifact route to an in-cluster artifact-cache service (replaces the ambassador Mapping for /artifact). Enable in envs that serve the helx-apps registry/specs from an artifact cache (e.g. air-gapped OpenShift). Off by default. Set authenticate=true to require the auth_request gate on /artifact. |
| authCache | object | `{"enabled":true,"negativeTtl":2,"sessionCookieName":"sessionid","size":"10m","ttl":30}` | Caches appstore's authorization + routing decision for /private requests in memory, keyed by session cookie and app/user/guid, so loading an app costs one appstore call per ttl instead of one per request. |
| authCache.negativeTtl | int | `2` | Seconds a failed decision (403/404/5xx) is reused, so a burst of requests behind a failing check doesn't each re-ask appstore. 0 disables. |
| authCache.sessionCookieName | string | `"sessionid"` | appstore's Django session cookie (SESSION_COOKIE_NAME). |
| authCache.size | string | `"10m"` | Shared memory for cached decisions; least-recently-used entries are evicted when full. |
| authCache.ttl | int | `30` | Seconds a successful decision is reused. Bounds how long access outlives a logout or an app shutdown. Keep it well below appstore's DJANGO_SESSION_IDLE_TIMEOUT (default 300): only cache misses reach appstore and refresh a session's idle timer. |
| basicAuth | object | `{"enabled":false,"password":"defaultPassword","username":"defaultUser"}` | Creates a basicAuth scheme preventing un-authenticated access to the whole site. |
| basicAuth.password | string | `"defaultPassword"` | Password, make sure to override. |
| basicAuth.username | string | `"defaultUser"` | Username , make sure to override. |
Expand Down Expand Up @@ -38,7 +43,7 @@ A Helm chart for Kubernetes
| ingress.ingressClassName | string | `nil` | Set to use a specific ingress class other than the default. |
| ingress.tls.enabled | bool | `true` | Values inserted into the TLS block come from SSL.nginxTLSSecret and service.serverName for backward compatibility |
| nameOverride | string | `""` | |
| replicaCount | int | `1` | |
| replicaCount | int | `1` | Number of resty pods. Each pod keeps its own authCache. |
| resources.limits.cpu | string | `"100m"` | |
| resources.limits.memory | string | `"128Mi"` | |
| resources.requests.cpu | string | `"50m"` | |
Expand Down
117 changes: 96 additions & 21 deletions charts/resty/templates/nginx-default-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ data:
# Override with resty.dnsResolver if your cluster DNS differs.
resolver {{ .Values.dnsResolver | default "kube-dns.kube-system.svc.cluster.local" }} valid=10s ipv6=off;

{{- if .Values.authCache.enabled }}

lua_shared_dict private_route_cache {{ .Values.authCache.size }};
lua_shared_dict private_route_locks 1m;
{{- end }}

{{- $appsNamespace := .Values.global.apps_namespace | default .Release.Namespace }}
{{- $dnsSuffix := .Values.global.cluster_dns_suffix | default "svc.cluster.local" }}
{{- /* Use FQDNs: any proxy_pass containing a variable (e.g. $request_uri)
Expand Down Expand Up @@ -107,9 +113,8 @@ data:
# backend. appstore is authoritative: it verifies the user OWNS this
# system (a check Ambassador never did -- it set bypass_auth: true) and
# returns the backend Service host, its real port, and the per-app
# rewrite rule. We then proxy over cluster DNS. This subrequest fires
# at the same frequency Ambassador's auth_request already did, so it
# adds no round-trip versus the old flow.
# rewrite rule. We then proxy over cluster DNS. With authCache enabled,
# each decision is reused for authCache.ttl seconds per session + system.
#####################################################
location ~ ^/private/(?<app>[^/]+)/(?<user>[^/]+)/(?<guid>[^/]+)(?<rest>/.*)?$ {
client_max_body_size 2000M;
Expand All @@ -124,26 +129,96 @@ data:
# dev: appstore/tycho run locally, no in-cluster resolution.
{{- else }}
access_by_lua_block {
-- Ask appstore to authorize + resolve this /private URL. Cookies
-- are inherited by the subrequest; pass the original path along.
local res = ngx.location.capture(
"/appstore-resolve",
{ args = { uri = ngx.var.request_uri } }
)
if res.status ~= 200 then
-- 403 (not owner), 404 (not ready), 5xx (resolve failed)
ngx.exit(res.status)
local function resolve()
-- Ask appstore to authorize + resolve this /private URL. Cookies
-- are inherited by the subrequest. Pass $uri, not $request_uri:
-- the backend Service is built from the location captures, which
-- come from the normalized $uri, so appstore must authorize that
-- same path. A raw "/../" path would otherwise be authorized as
-- one system and proxied to another.
local res = ngx.location.capture(
"/appstore-resolve",
{ args = { uri = ngx.var.uri } }
)
if res.status ~= 200 then
-- 403 (not owner), 404 (not ready), 5xx (resolve failed)
return { status = res.status }
end
local h = res.header
if not h["X-Backend-Port"] then
ngx.log(ngx.ERR, "private resolve: missing backend port")
return { status = 502 }
end
return {
status = 200,
port = h["X-Backend-Port"],
rewrite = h["X-Rewrite"] or "",
prefix = h["X-Prefix"] or "",
remote_user = h["REMOTE_USER"] or "",
access_token = h["ACCESS_TOKEN"] or "",
}
end

{{- if .Values.authCache.enabled }}

-- Django reads the last session cookie, nginx's $cookie_* the first.
-- Only cache when there is exactly one, so the cache key is the
-- session appstore actually authenticated.
local function session_id()
local cookies = ngx.var.http_cookie
if not cookies then return nil end
local sid
for pair in cookies:gmatch("[^;]+") do
local name, value = pair:match("^%s*(.-)%s*=%s*(.-)%s*$")
if name == {{ .Values.authCache.sessionCookieName | quote }} then
if sid then return nil end
sid = value
end
end
if sid ~= "" then return sid end
end

local h = res.header
local port = h["X-Backend-Port"]
local rewrite = h["X-Rewrite"] or ""
local prefix = h["X-Prefix"] or ""
local route
local sid = session_id()
if sid then
local cjson = require "cjson.safe"
local cache = ngx.shared.private_route_cache
local key = ngx.md5(sid) .. "/" .. ngx.var.app .. "/" .. ngx.var.user .. "/" .. ngx.var.guid
local function lookup()
local v = cache:get(key)
return v and cjson.decode(v)
end

if not port then
ngx.log(ngx.ERR, "private resolve: missing backend port")
ngx.exit(502)
route = lookup()
if not route then
-- Concurrent misses on one key share a single appstore call.
local lock = require("resty.lock"):new("private_route_locks", { timeout = 30, exptime = 35 })
local locked = lock and lock:lock(key)
route = lookup()
if not route then
route = resolve()
local ttl = route.status == 200 and {{ .Values.authCache.ttl | float64 }} or {{ .Values.authCache.negativeTtl | float64 }}
if ttl > 0 then
cache:set(key, cjson.encode(route), ttl)
end
end
if locked then lock:unlock() end
end
else
route = resolve()
end
{{- else }}

local route = resolve()
{{- end }}

if route.status ~= 200 then
ngx.exit(route.status)
end

local port = route.port
local rewrite = route.rewrite
local prefix = route.prefix

-- The backend Service name is deterministic from the URL path:
-- {app}-{guid} (Tycho System.name = f"{app_id}-{identifier}").
Expand Down Expand Up @@ -183,8 +258,8 @@ data:
end

-- Inject identity downstream, as Ambassador's REMOTE_USER header did.
ngx.req.set_header("REMOTE_USER", h["REMOTE_USER"] or "")
ngx.req.set_header("Authorization", h["ACCESS_TOKEN"] or "")
ngx.req.set_header("REMOTE_USER", route.remote_user)
ngx.req.set_header("Authorization", route.access_token)
}
{{- end }}

Expand Down
2 changes: 1 addition & 1 deletion charts/resty/templates/nginx-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ kind: Deployment
metadata:
name: {{ include "resty.fullname" . }}
spec:
replicas: 1
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "resty.selectorLabels" . | nindent 6 }}
Expand Down
20 changes: 20 additions & 0 deletions charts/resty/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

# -- Number of resty pods. Each pod keeps its own authCache.
replicaCount: 1

image:
Expand Down Expand Up @@ -84,6 +85,25 @@ external_http_host: false
airflow:
authenticate: true

# -- Caches appstore's authorization + routing decision for /private requests
# in memory, keyed by session cookie and app/user/guid, so loading an app costs
# one appstore call per ttl instead of one per request.
authCache:
enabled: true
# -- Seconds a successful decision is reused. Bounds how long access outlives
# a logout or an app shutdown. Keep it well below appstore's
# DJANGO_SESSION_IDLE_TIMEOUT (default 300): only cache misses reach appstore
# and refresh a session's idle timer.
ttl: 30
# -- Seconds a failed decision (403/404/5xx) is reused, so a burst of
# requests behind a failing check doesn't each re-ask appstore. 0 disables.
negativeTtl: 2
# -- Shared memory for cached decisions; least-recently-used entries are
# evicted when full.
size: 10m
# -- appstore's Django session cookie (SESSION_COOKIE_NAME).
sessionCookieName: sessionid

workerConnections: 1024
stubStatus:
enabled: false
Expand Down
Loading