From e67aef89d9984ad4c0e65b11d47ff07033caa24c Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 08:23:08 -0500 Subject: [PATCH 01/17] feat: add optional authProxy component for custom LLM provider/gateway auth --- charts/langsmith/templates/_helpers.tpl | 8 + .../templates/auth-proxy/config-map.yaml | 167 ++++++++++++++++++ .../templates/auth-proxy/deployment.yaml | 134 ++++++++++++++ .../langsmith/templates/auth-proxy/hpa.yaml | 41 +++++ .../langsmith/templates/auth-proxy/pdb.yaml | 28 +++ .../templates/auth-proxy/service-account.yaml | 18 ++ .../templates/auth-proxy/service.yaml | 33 ++++ charts/langsmith/values.yaml | 118 +++++++++++++ 8 files changed, 547 insertions(+) create mode 100644 charts/langsmith/templates/auth-proxy/config-map.yaml create mode 100644 charts/langsmith/templates/auth-proxy/deployment.yaml create mode 100644 charts/langsmith/templates/auth-proxy/hpa.yaml create mode 100644 charts/langsmith/templates/auth-proxy/pdb.yaml create mode 100644 charts/langsmith/templates/auth-proxy/service-account.yaml create mode 100644 charts/langsmith/templates/auth-proxy/service.yaml diff --git a/charts/langsmith/templates/_helpers.tpl b/charts/langsmith/templates/_helpers.tpl index 85a80d11..66c78713 100644 --- a/charts/langsmith/templates/_helpers.tpl +++ b/charts/langsmith/templates/_helpers.tpl @@ -740,6 +740,14 @@ Strip protocol (http://, https://, etc.) from hostname {{- include "agentBuilderOAuthEnvVars" . }} {{- end -}} +{{- define "authProxy.serviceAccountName" -}} +{{- if .Values.authProxy.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.authProxy.name) .Values.authProxy.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.authProxy.serviceAccount.name }} +{{- end -}} +{{- end -}} + {{- define "agentBuilderTriggerServerEnvVars" -}} - name: "PORT" value: "{{ .Values.agentBuilderTriggerServer.containerPort }}" diff --git a/charts/langsmith/templates/auth-proxy/config-map.yaml b/charts/langsmith/templates/auth-proxy/config-map.yaml new file mode 100644 index 00000000..e6db8e62 --- /dev/null +++ b/charts/langsmith/templates/auth-proxy/config-map.yaml @@ -0,0 +1,167 @@ +{{- if .Values.authProxy.enabled }} +{{- $upstreamUrl := urlParse .Values.authProxy.upstream -}} +{{- $upstreamScheme := $upstreamUrl.scheme -}} +{{- $upstreamHost := $upstreamUrl.host -}} +{{- $hostParts := splitList ":" $upstreamHost -}} +{{- $upstreamHostname := index $hostParts 0 -}} +{{- $upstreamPort := "" -}} +{{- if gt (len $hostParts) 1 -}} + {{- $upstreamPort = index $hostParts 1 -}} +{{- else -}} + {{- if eq $upstreamScheme "https" -}} + {{- $upstreamPort = "443" -}} + {{- else -}} + {{- $upstreamPort = "80" -}} + {{- end -}} +{{- end -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + namespace: {{ .Values.namespace | default .Release.Namespace }} + labels: + {{- include "langsmith.labels" . | nindent 4 }} + annotations: + {{- include "langsmith.annotations" . | nindent 4 }} +data: + envoy.yaml: | + static_resources: + listeners: + - name: listener_0 + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: upstream + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: upstream_provider + host_rewrite_literal: {{ $upstreamHostname }} + timeout: 0s + idle_timeout: {{ .Values.authProxy.streamIdleTimeout }} + http_filters: + - name: envoy.filters.http.health_check + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck + pass_through_mode: false + headers: + - name: ":path" + string_match: + exact: "/healthz" + - name: envoy.filters.http.jwt_authn + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication + providers: + langsmith_jwt: + issuer: {{ .Values.authProxy.jwtIssuer }} + {{- if .Values.authProxy.jwtAudiences }} + audiences: + {{- range .Values.authProxy.jwtAudiences }} + - {{ . | quote }} + {{- end }} + {{- end }} + local_jwks: + inline_string: '{{ .Values.authProxy.jwksJson }}' + from_headers: + - name: X-LangSmith-LLM-Auth + forward: true + rules: + - match: + prefix: "/" + requires: + provider_name: langsmith_jwt + {{- if .Values.authProxy.extAuthz.enabled }} + - name: envoy.filters.http.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + failure_mode_allow: false + http_service: + server_uri: + uri: {{ .Values.authProxy.extAuthz.serviceUrl }} + cluster: ext_authz_service + timeout: {{ .Values.authProxy.extAuthz.timeout }} + path_prefix: "/check" + authorization_request: + allowed_headers: + patterns: + - exact: "x-langsmith-llm-auth" + - prefix: "x-" + authorization_response: + allowed_upstream_headers: + patterns: + - exact: "authorization" + - exact: "x-langsmith-llm-auth" + - prefix: "x-forwarded-" + allowed_client_headers: + patterns: + - exact: "www-authenticate" + - prefix: "x-" + {{- if .Values.authProxy.extAuthz.sendBody }} + with_request_body: + max_request_bytes: {{ .Values.authProxy.extAuthz.maxRequestBytes }} + allow_partial_message: false + {{- end }} + {{- end }} + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + + clusters: + - name: upstream_provider + connect_timeout: 5s + type: LOGICAL_DNS + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: upstream_provider + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: {{ $upstreamHostname }} + port_value: {{ $upstreamPort }} + {{- if eq $upstreamScheme "https" }} + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + sni: {{ $upstreamHostname }} + {{- end }} + {{- if .Values.authProxy.extAuthz.enabled }} + {{- $extAuthzUrl := urlParse .Values.authProxy.extAuthz.serviceUrl }} + {{- $extAuthzHost := $extAuthzUrl.host }} + {{- $extAuthzParts := splitList ":" $extAuthzHost }} + {{- $extAuthzHostname := index $extAuthzParts 0 }} + {{- $extAuthzPort := "" }} + {{- if gt (len $extAuthzParts) 1 }} + {{- $extAuthzPort = index $extAuthzParts 1 }} + {{- else }} + {{- $extAuthzPort = "80" }} + {{- end }} + - name: ext_authz_service + connect_timeout: 5s + type: STRICT_DNS + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: ext_authz_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: {{ $extAuthzHostname }} + port_value: {{ $extAuthzPort }} + {{- end }} +{{- end }} diff --git a/charts/langsmith/templates/auth-proxy/deployment.yaml b/charts/langsmith/templates/auth-proxy/deployment.yaml new file mode 100644 index 00000000..3864234e --- /dev/null +++ b/charts/langsmith/templates/auth-proxy/deployment.yaml @@ -0,0 +1,134 @@ +{{- if .Values.authProxy.enabled }} +{{- $volumes := .Values.authProxy.deployment.volumes -}} +{{- $volumeMounts := .Values.authProxy.deployment.volumeMounts -}} +{{- if .Values.authProxy.rollout.enabled }} +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +{{- else }} +apiVersion: apps/v1 +kind: Deployment +{{- end }} +metadata: + name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + namespace: {{ .Values.namespace | default .Release.Namespace }} + labels: + {{- include "langsmith.labels" . | nindent 4 }} + {{- with.Values.authProxy.deployment.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- include "langsmith.annotations" . | nindent 4 }} + {{- with.Values.authProxy.deployment.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.authProxy.autoscaling.hpa.enabled }} + replicas: {{ .Values.authProxy.deployment.replicas }} + {{- end }} + selector: + matchLabels: + {{- include "langsmith.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + template: + metadata: + annotations: + {{- include "langsmith.commonPodAnnotations" . | nindent 8 }} + {{- with .Values.authProxy.deployment.annotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- with.Values.authProxy.deployment.labels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "langsmith.labels" . | nindent 8 }} + app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + spec: + terminationGracePeriodSeconds: {{ .Values.authProxy.deployment.terminationGracePeriodSeconds }} + {{- with .Values.images.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- include "langsmith.podSecurityContext" (dict "Values" .Values "componentSecurityContext" .Values.authProxy.deployment.podSecurityContext) | nindent 8 }} + serviceAccountName: {{ include "authProxy.serviceAccountName" . }} + {{- with .Values.authProxy.deployment.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.authProxy.name }} + {{- with.Values.authProxy.deployment.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.authProxy.deployment.extraEnv }} + env: + {{ toYaml . | nindent 12 }} + {{- end }} + image: {{ include "langsmith.image" (dict "Values" .Values "Chart" .Chart "component" "authProxyImage") | quote }} + imagePullPolicy: {{ .Values.images.authProxyImage.pullPolicy }} + ports: + - name: envoy + containerPort: {{ .Values.authProxy.containerPort }} + protocol: TCP + {{- with .Values.authProxy.deployment.startupProbe }} + startupProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.authProxy.deployment.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.authProxy.deployment.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.authProxy.deployment.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.authProxy.deployment.securityContext | nindent 12 }} + volumeMounts: + {{- with $volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + - mountPath: /etc/envoy/envoy.yaml + name: envoy-conf + subPath: envoy.yaml + readOnly: true + {{- with .Values.authProxy.deployment.extraContainerConfig }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.authProxy.deployment.sidecars }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.authProxy.deployment.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.authProxy.deployment.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.authProxy.deployment.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.authProxy.deployment.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + {{- with $volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: envoy-conf + configMap: + name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + items: + - key: envoy.yaml + path: envoy.yaml +{{- if .Values.authProxy.rollout.enabled }} + strategy: + {{- toYaml .Values.authProxy.rollout.strategy | nindent 4 }} +{{- end }} +{{- end }} diff --git a/charts/langsmith/templates/auth-proxy/hpa.yaml b/charts/langsmith/templates/auth-proxy/hpa.yaml new file mode 100644 index 00000000..0f7b5704 --- /dev/null +++ b/charts/langsmith/templates/auth-proxy/hpa.yaml @@ -0,0 +1,41 @@ +{{- if and .Values.authProxy.enabled .Values.authProxy.autoscaling.hpa.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + namespace: {{ .Values.namespace | default .Release.Namespace }} + labels: + {{- include "langsmith.labels" . | nindent 4 }} +spec: + scaleTargetRef: + {{- if .Values.authProxy.rollout.enabled }} + apiVersion: argoproj.io/v1alpha1 + kind: Rollout + {{- else }} + apiVersion: apps/v1 + kind: Deployment + {{- end }} + name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + minReplicas: {{ .Values.authProxy.autoscaling.hpa.minReplicas }} + maxReplicas: {{ .Values.authProxy.autoscaling.hpa.maxReplicas }} + metrics: + {{- if .Values.authProxy.autoscaling.hpa.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.authProxy.autoscaling.hpa.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.authProxy.autoscaling.hpa.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.authProxy.autoscaling.hpa.targetMemoryUtilizationPercentage }} + {{- end }} + {{- if .Values.authProxy.autoscaling.hpa.additionalMetrics }} + {{- toYaml .Values.authProxy.autoscaling.hpa.additionalMetrics | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/langsmith/templates/auth-proxy/pdb.yaml b/charts/langsmith/templates/auth-proxy/pdb.yaml new file mode 100644 index 00000000..c8d7ff08 --- /dev/null +++ b/charts/langsmith/templates/auth-proxy/pdb.yaml @@ -0,0 +1,28 @@ +{{- if and .Values.authProxy.enabled .Values.authProxy.pdb.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + namespace: {{ .Values.namespace | default .Release.Namespace }} + labels: + {{- include "langsmith.labels" . | nindent 4 }} + {{- with .Values.authProxy.pdb.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- include "langsmith.annotations" . | nindent 4 }} + {{- with .Values.authProxy.pdb.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- include "langsmith.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + {{- if .Values.authProxy.pdb.minAvailable }} + minAvailable: {{ .Values.authProxy.pdb.minAvailable }} + {{- end }} + {{- if .Values.authProxy.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.authProxy.pdb.maxUnavailable }} + {{- end }} +{{- end }} diff --git a/charts/langsmith/templates/auth-proxy/service-account.yaml b/charts/langsmith/templates/auth-proxy/service-account.yaml new file mode 100644 index 00000000..a1855d03 --- /dev/null +++ b/charts/langsmith/templates/auth-proxy/service-account.yaml @@ -0,0 +1,18 @@ +{{- if and .Values.authProxy.enabled .Values.authProxy.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "authProxy.serviceAccountName" . }} + namespace: {{ .Values.namespace | default .Release.Namespace }} + labels: + {{- include "langsmith.labels" . | nindent 4 }} + {{- with.Values.authProxy.deployment.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- include "langsmith.annotations" . | nindent 4 }} + {{- with.Values.authProxy.serviceAccount.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.authProxy.serviceAccount.automountServiceAccountToken | default true }} +{{- end }} diff --git a/charts/langsmith/templates/auth-proxy/service.yaml b/charts/langsmith/templates/auth-proxy/service.yaml new file mode 100644 index 00000000..250b10b2 --- /dev/null +++ b/charts/langsmith/templates/auth-proxy/service.yaml @@ -0,0 +1,33 @@ +{{- if .Values.authProxy.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + namespace: {{ .Values.namespace | default .Release.Namespace }} + labels: + {{- include "langsmith.labels" . | nindent 4 }} + {{- with.Values.authProxy.service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + annotations: + {{- include "langsmith.annotations" . | nindent 4 }} + {{- with.Values.authProxy.service.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.authProxy.service.type }} + {{- with .Values.authProxy.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- toYaml . | nindent 4 }} + {{- end }} + loadBalancerIP: {{ .Values.authProxy.service.loadBalancerIP }} + ports: + - name: envoy + port: {{ .Values.authProxy.service.port }} + targetPort: envoy + protocol: TCP + selector: + {{- include "langsmith.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} +{{- end }} diff --git a/charts/langsmith/values.yaml b/charts/langsmith/values.yaml index e0ccd251..525a68fe 100644 --- a/charts/langsmith/values.yaml +++ b/charts/langsmith/values.yaml @@ -86,6 +86,10 @@ images: repository: "docker.io/langchain/agent-builder-trigger-server" pullPolicy: IfNotPresent tag: "0.13.14" + authProxyImage: + repository: "docker.io/envoyproxy/envoy" + pullPolicy: IfNotPresent + tag: "v1.37-latest" agentBuilderImage: repository: "docker.io/langchain/agent-builder-deep-agent" pullPolicy: IfNotPresent @@ -2161,3 +2165,117 @@ agentBuilderTriggerServer: labels: {} annotations: {} automountServiceAccountToken: true + +# Auth Proxy - Optional Envoy-based proxy for validating LangSmith-signed JWTs +# and optionally calling an external auth service before forwarding to an upstream LLM provider or gateway. +authProxy: + enabled: false + name: "auth-proxy" + containerPort: 10000 + # -- Upstream LLM provider URL (e.g. https://api.openai.com) + upstream: "https://api.openai.com" + # -- JWT issuer claim to validate + jwtIssuer: "langsmith" + # -- JWT audience claims to validate. Must match audiences in the signed JWT. + jwtAudiences: [] + # -- JWKS JSON string containing the public keys for JWT validation. + # Generate with the LangSmith JWKS tooling and paste the full JSON here. + jwksJson: "" + # -- Idle timeout for streaming responses (e.g. SSE from LLM providers) + streamIdleTimeout: "300s" + # External authorization service configuration (for injecting LLM provider auth headers) + extAuthz: + enabled: false + # -- HTTP service URL for ext_authz (e.g. http://my-auth-service:8080) + serviceUrl: "" + # -- Timeout for ext_authz requests + timeout: "10s" + # -- Whether to send the request body to ext_authz + sendBody: false + # -- Maximum request body bytes to buffer for ext_authz + maxRequestBytes: 8192 + # -- ArgoCD Rollouts configuration. If enabled, will create a Rollout resource instead of a Deployment. See https://argo-rollouts.readthedocs.io/ + rollout: + enabled: false + # -- Rollout strategy configuration. See https://argo-rollouts.readthedocs.io/en/stable/features/specification/ + strategy: + canary: + steps: + - setWeight: 100 + deployment: + replicas: 1 + labels: {} + annotations: {} + podSecurityContext: {} + securityContext: {} + resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + command: + - "envoy" + - "-c" + - "/etc/envoy/envoy.yaml" + startupProbe: + httpGet: + path: /healthz + port: 10000 + failureThreshold: 6 + periodSeconds: 10 + timeoutSeconds: 1 + livenessProbe: + httpGet: + path: /healthz + port: 10000 + failureThreshold: 6 + periodSeconds: 10 + timeoutSeconds: 1 + readinessProbe: + httpGet: + path: /healthz + port: 10000 + failureThreshold: 6 + periodSeconds: 10 + timeoutSeconds: 1 + extraContainerConfig: {} + extraEnv: [] + sidecars: [] + initContainers: [] + nodeSelector: {} + tolerations: [] + topologySpreadConstraints: [] + affinity: {} + volumes: [] + volumeMounts: [] + terminationGracePeriodSeconds: 30 + # Autoscaling configuration. + autoscaling: + # HPA-specific configuration + hpa: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 50 + targetMemoryUtilizationPercentage: 80 + additionalMetrics: [] + pdb: + enabled: false + minAvailable: 1 + labels: {} + annotations: {} + service: + type: ClusterIP + port: 10000 + labels: {} + annotations: {} + loadBalancerSourceRanges: [] + loadBalancerIP: "" + serviceAccount: + create: true + name: "" + labels: {} + annotations: {} + automountServiceAccountToken: true From 7b44de8a7e4a762698157209f82f54f20a344851 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 08:59:19 -0500 Subject: [PATCH 02/17] add ingress --- .../templates/auth-proxy/ingress.yaml | 40 +++++++++++++++++++ charts/langsmith/values.yaml | 19 +++++++++ 2 files changed, 59 insertions(+) create mode 100644 charts/langsmith/templates/auth-proxy/ingress.yaml diff --git a/charts/langsmith/templates/auth-proxy/ingress.yaml b/charts/langsmith/templates/auth-proxy/ingress.yaml new file mode 100644 index 00000000..663a0921 --- /dev/null +++ b/charts/langsmith/templates/auth-proxy/ingress.yaml @@ -0,0 +1,40 @@ +{{- if and .Values.authProxy.enabled .Values.authProxy.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + namespace: {{ .Values.namespace | default .Release.Namespace }} + annotations: + {{- include "langsmith.annotations" . | nindent 4 }} + {{- with .Values.authProxy.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "langsmith.labels" . | nindent 4 }} + {{- with .Values.authProxy.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.authProxy.ingress.ingressClassName }} + ingressClassName: {{ . }} + {{- end }} + {{- with .Values.authProxy.ingress.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.authProxy.ingress.hosts }} + - host: {{ .host }} + http: + paths: + {{- range .paths }} + - path: {{ .path | default "/" }} + pathType: {{ .pathType | default "Prefix" }} + backend: + service: + name: {{ include "langsmith.fullname" $ }}-{{ $.Values.authProxy.name }} + port: + number: {{ $.Values.authProxy.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/langsmith/values.yaml b/charts/langsmith/values.yaml index 525a68fe..0d15c002 100644 --- a/charts/langsmith/values.yaml +++ b/charts/langsmith/values.yaml @@ -2168,6 +2168,7 @@ agentBuilderTriggerServer: # Auth Proxy - Optional Envoy-based proxy for validating LangSmith-signed JWTs # and optionally calling an external auth service before forwarding to an upstream LLM provider or gateway. +# Separate ingress from the main LangSmith app — different hostname, streaming-optimized. authProxy: enabled: false name: "auth-proxy" @@ -2194,6 +2195,24 @@ authProxy: sendBody: false # -- Maximum request body bytes to buffer for ext_authz maxRequestBytes: 8192 + ingress: + enabled: false + ingressClassName: "" + labels: {} + # -- Annotations for streaming support. Defaults shown are for nginx ingress controller. + annotations: {} + # nginx.ingress.kubernetes.io/proxy-buffering: "off" + # nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + # nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + hosts: [] + # - host: llm-proxy.example.com + # paths: + # - path: / + # pathType: Prefix + tls: [] + # - secretName: llm-proxy-tls + # hosts: + # - llm-proxy.example.com # -- ArgoCD Rollouts configuration. If enabled, will create a Rollout resource instead of a Deployment. See https://argo-rollouts.readthedocs.io/ rollout: enabled: false From f35dfe50a09471b38721a8113390469b2f61d65b Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 11:39:44 -0500 Subject: [PATCH 03/17] add chart test --- charts/langsmith/ci/auth-proxy-values.yaml | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 charts/langsmith/ci/auth-proxy-values.yaml diff --git a/charts/langsmith/ci/auth-proxy-values.yaml b/charts/langsmith/ci/auth-proxy-values.yaml new file mode 100644 index 00000000..e764b4dd --- /dev/null +++ b/charts/langsmith/ci/auth-proxy-values.yaml @@ -0,0 +1,106 @@ +# CI values for testing auth proxy in isolation. +# Enables only the auth-proxy component with ingress. All other pods disabled. + +# Minimal config to satisfy chart requirements +config: + authType: "mixed" + basicAuth: + enabled: true + initialOrgAdminEmail: "test@test.com" + initialOrgAdminPassword: "TestLangSmith123!" + jwtSecret: "test-jwt-secret" + +# Disable all databases (pretend external) +postgres: + external: + enabled: true + host: "fake-postgres" + port: "5432" + user: "test" + password: "test" + database: "test" + +redis: + external: + enabled: true + host: "fake-redis" + +clickhouse: + external: + enabled: true + host: "fake-clickhouse" + +# Disable all application services +backend: + deployment: + replicas: 0 + +frontend: + deployment: + replicas: 0 + +platformBackend: + deployment: + replicas: 0 + +playground: + deployment: + replicas: 0 + +queue: + deployment: + replicas: 0 + +hostBackend: + deployment: + replicas: 0 + +aceBackend: + deployment: + replicas: 0 + +listener: + deployment: + replicas: 0 + +operator: + enabled: false + +ingestQueue: + enabled: false + +agentBuilderToolServer: + enabled: false + +agentBuilderTriggerServer: + enabled: false + +# Enable auth proxy with ingress +authProxy: + enabled: true + upstream: "https://api.openai.com" + jwtIssuer: "langsmith" + jwtAudiences: + - "test-audience" + jwksJson: '{"kty": "RSA","use": "sig","alg": "RS256","kid": "f98e5ea5-2ee6-4141-b1fd-9a3ecb6648fd","d": "Ntcd3fjgYh1ytShRgfgEScbc1t_9H6mNZ5nkyjUJ9WpMUmBk9MltimV0qMDRWs85695c30YD-Uf5VMvgYszSQZZo3iNWX8bfKEffqYboN2zNyhvomB1dboyUXz4I3B4-7Zrxgdamd1adOPg7Rxedck8a3oJwE9FzpypCg67-mQjTnZ8RTTtu5ekvoXYsrR30qI_lWUGiA9aL6pCbTEQOjBombLNkOlwl2Hh7FORSvM3ViEMop7rMDvMAWRPcBcpJgwHhQTBhBx1QMi01DmdX7kXnnsTgrU4bxX9zgIXtBV7Fhlk1bvIVqOTT7M3JMbQG_MXLXjRbvj7bAHta1FRE9Q","n": "uqHU2bRgvKIBe88_ikr3MLdTa4W55gv3DjVFuB6hZxaJIbOzGXE3-FRf7cfqg0Exysow5uuXUUTtq_zaE3AZLvEOt3CmQ3su_OxHPsytTHwLcc74NCL7hozv1uAQTMWAof4_KvyYIYOX5_wRgwoahQJPDSvbQpZvjdxUR7muVps65idF6lZrvoRYQiyuyMzozyFYAqiOI9VIud3Z9S2gSsHRhExPf8UD-HKiTKPUOlWLCwiU9FWWRgYse0jPwzU6j2lXu38aJjJd43ROH7OrcWp4fdLY-pjLQb6rz-RshTgXPkvZxTmfLVSqUkHr0xkM-Rb4T1CmvV4DuXNDBkfkgw","e": "AQAB"}' + streamIdleTimeout: "300s" + extAuthz: + enabled: true + serviceUrl: "http://auth-service:8080" + timeout: "10s" + ingress: + enabled: true + ingressClassName: "nginx" + annotations: + nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + hosts: + - host: llm-proxy.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: llm-proxy-tls + hosts: + - llm-proxy.example.com From 954ba70878c13f4938eeb21c520e9af7ac912700 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 14:43:42 -0500 Subject: [PATCH 04/17] separate chart --- charts/langsmith-auth-proxy/Chart.yaml | 9 + .../ci/auth-proxy-values.yaml | 78 +- .../templates/_helpers.tpl | 107 +++ .../templates/auth-proxy/config-map.yaml | 0 .../templates/auth-proxy/deployment.yaml | 0 .../templates/auth-proxy/hpa.yaml | 0 .../templates/auth-proxy/ingress.yaml | 0 .../templates/auth-proxy/pdb.yaml | 0 .../templates/auth-proxy/service-account.yaml | 0 .../templates/auth-proxy/service.yaml | 0 .../templates/http_route.yaml | 38 + .../templates/ingress.yaml | 40 + charts/langsmith-auth-proxy/values.yaml | 183 +++++ charts/langsmith/templates/_helpers.tpl | 765 ------------------ charts/langsmith/values.yaml | 133 --- 15 files changed, 378 insertions(+), 975 deletions(-) create mode 100644 charts/langsmith-auth-proxy/Chart.yaml rename charts/{langsmith => langsmith-auth-proxy}/ci/auth-proxy-values.yaml (55%) create mode 100644 charts/langsmith-auth-proxy/templates/_helpers.tpl rename charts/{langsmith => langsmith-auth-proxy}/templates/auth-proxy/config-map.yaml (100%) rename charts/{langsmith => langsmith-auth-proxy}/templates/auth-proxy/deployment.yaml (100%) rename charts/{langsmith => langsmith-auth-proxy}/templates/auth-proxy/hpa.yaml (100%) rename charts/{langsmith => langsmith-auth-proxy}/templates/auth-proxy/ingress.yaml (100%) rename charts/{langsmith => langsmith-auth-proxy}/templates/auth-proxy/pdb.yaml (100%) rename charts/{langsmith => langsmith-auth-proxy}/templates/auth-proxy/service-account.yaml (100%) rename charts/{langsmith => langsmith-auth-proxy}/templates/auth-proxy/service.yaml (100%) create mode 100644 charts/langsmith-auth-proxy/templates/http_route.yaml create mode 100644 charts/langsmith-auth-proxy/templates/ingress.yaml create mode 100644 charts/langsmith-auth-proxy/values.yaml delete mode 100644 charts/langsmith/templates/_helpers.tpl diff --git a/charts/langsmith-auth-proxy/Chart.yaml b/charts/langsmith-auth-proxy/Chart.yaml new file mode 100644 index 00000000..2aad963c --- /dev/null +++ b/charts/langsmith-auth-proxy/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: langsmith-auth-proxy +maintainers: + - name: Brian + email: brian@langchain.dev +description: Helm chart to deploy the langsmith auth-proxy application. +type: application +version: 0.0.1 +appVersion: "1.37.0" diff --git a/charts/langsmith/ci/auth-proxy-values.yaml b/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml similarity index 55% rename from charts/langsmith/ci/auth-proxy-values.yaml rename to charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml index e764b4dd..9a40f261 100644 --- a/charts/langsmith/ci/auth-proxy-values.yaml +++ b/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml @@ -1,81 +1,5 @@ -# CI values for testing auth proxy in isolation. -# Enables only the auth-proxy component with ingress. All other pods disabled. +# CI values for testing auth proxy chart. -# Minimal config to satisfy chart requirements -config: - authType: "mixed" - basicAuth: - enabled: true - initialOrgAdminEmail: "test@test.com" - initialOrgAdminPassword: "TestLangSmith123!" - jwtSecret: "test-jwt-secret" - -# Disable all databases (pretend external) -postgres: - external: - enabled: true - host: "fake-postgres" - port: "5432" - user: "test" - password: "test" - database: "test" - -redis: - external: - enabled: true - host: "fake-redis" - -clickhouse: - external: - enabled: true - host: "fake-clickhouse" - -# Disable all application services -backend: - deployment: - replicas: 0 - -frontend: - deployment: - replicas: 0 - -platformBackend: - deployment: - replicas: 0 - -playground: - deployment: - replicas: 0 - -queue: - deployment: - replicas: 0 - -hostBackend: - deployment: - replicas: 0 - -aceBackend: - deployment: - replicas: 0 - -listener: - deployment: - replicas: 0 - -operator: - enabled: false - -ingestQueue: - enabled: false - -agentBuilderToolServer: - enabled: false - -agentBuilderTriggerServer: - enabled: false - -# Enable auth proxy with ingress authProxy: enabled: true upstream: "https://api.openai.com" diff --git a/charts/langsmith-auth-proxy/templates/_helpers.tpl b/charts/langsmith-auth-proxy/templates/_helpers.tpl new file mode 100644 index 00000000..173c1ea8 --- /dev/null +++ b/charts/langsmith-auth-proxy/templates/_helpers.tpl @@ -0,0 +1,107 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "langsmith.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "langsmith.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "langsmith.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "langsmith.labels" -}} +{{- if .Values.commonLabels }} +{{ toYaml .Values.commonLabels }} +{{- end }} +helm.sh/chart: {{ include "langsmith.chart" . }} +{{ include "langsmith.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Common annotations +*/}} +{{- define "langsmith.annotations" -}} +{{- if .Values.commonAnnotations }} +{{ toYaml .Values.commonAnnotations }} +{{- end }} +helm.sh/chart: {{ include "langsmith.chart" . }} +{{ include "langsmith.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Common pod annotations +*/}} +{{- define "langsmith.commonPodAnnotations" -}} +{{- if .Values.commonPodAnnotations }} +{{ toYaml .Values.commonPodAnnotations }} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "langsmith.selectorLabels" -}} +app.kubernetes.io/name: {{ include "langsmith.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Template for merging commonPodSecurityContext with component-specific podSecurityContext. +Component-specific values take precedence over common values. +*/}} +{{- define "langsmith.podSecurityContext" -}} +{{- $merged := merge .componentSecurityContext .Values.commonPodSecurityContext -}} +{{- toYaml $merged -}} +{{- end -}} + +{{/* +Creates the image reference used for deployments. If registry is specified, concatenate it, along with a '/'. +*/}} +{{- define "langsmith.image" -}} +{{- $imageConfig := index .Values.images .component -}} +{{- if .Values.images.registry -}} +{{ .Values.images.registry }}/{{ $imageConfig.repository }}:{{ $imageConfig.tag | default .Chart.AppVersion }} +{{- else -}} +{{ $imageConfig.repository }}:{{ $imageConfig.tag | default .Chart.AppVersion }} +{{- end -}} +{{- end -}} + +{{- define "authProxy.serviceAccountName" -}} +{{- if .Values.authProxy.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.authProxy.name) .Values.authProxy.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.authProxy.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/charts/langsmith/templates/auth-proxy/config-map.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml similarity index 100% rename from charts/langsmith/templates/auth-proxy/config-map.yaml rename to charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml diff --git a/charts/langsmith/templates/auth-proxy/deployment.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/deployment.yaml similarity index 100% rename from charts/langsmith/templates/auth-proxy/deployment.yaml rename to charts/langsmith-auth-proxy/templates/auth-proxy/deployment.yaml diff --git a/charts/langsmith/templates/auth-proxy/hpa.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/hpa.yaml similarity index 100% rename from charts/langsmith/templates/auth-proxy/hpa.yaml rename to charts/langsmith-auth-proxy/templates/auth-proxy/hpa.yaml diff --git a/charts/langsmith/templates/auth-proxy/ingress.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/ingress.yaml similarity index 100% rename from charts/langsmith/templates/auth-proxy/ingress.yaml rename to charts/langsmith-auth-proxy/templates/auth-proxy/ingress.yaml diff --git a/charts/langsmith/templates/auth-proxy/pdb.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/pdb.yaml similarity index 100% rename from charts/langsmith/templates/auth-proxy/pdb.yaml rename to charts/langsmith-auth-proxy/templates/auth-proxy/pdb.yaml diff --git a/charts/langsmith/templates/auth-proxy/service-account.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/service-account.yaml similarity index 100% rename from charts/langsmith/templates/auth-proxy/service-account.yaml rename to charts/langsmith-auth-proxy/templates/auth-proxy/service-account.yaml diff --git a/charts/langsmith/templates/auth-proxy/service.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/service.yaml similarity index 100% rename from charts/langsmith/templates/auth-proxy/service.yaml rename to charts/langsmith-auth-proxy/templates/auth-proxy/service.yaml diff --git a/charts/langsmith-auth-proxy/templates/http_route.yaml b/charts/langsmith-auth-proxy/templates/http_route.yaml new file mode 100644 index 00000000..2f8c781e --- /dev/null +++ b/charts/langsmith-auth-proxy/templates/http_route.yaml @@ -0,0 +1,38 @@ +{{- if .Values.gateway.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "langsmith.fullname" . }} + namespace: {{ .Values.namespace | default .Release.Namespace }} + annotations: + {{- include "langsmith.annotations" . | nindent 4 }} + {{- with .Values.gateway.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "langsmith.labels" . | nindent 4 }} + {{- with .Values.gateway.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + - name: {{ .Values.gateway.name }} + {{- if .Values.gateway.namespace }} + namespace: {{ .Values.gateway.namespace }} + {{- end }} + {{- if .Values.gateway.sectionName }} + sectionName: {{ .Values.gateway.sectionName }} + {{- end }} + {{- with .Values.gateway.hostnames }} + hostnames: + {{- toYaml . | nindent 2 }} + {{- end }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + port: {{ .Values.authProxy.service.port }} +{{- end }} diff --git a/charts/langsmith-auth-proxy/templates/ingress.yaml b/charts/langsmith-auth-proxy/templates/ingress.yaml new file mode 100644 index 00000000..ba16c53c --- /dev/null +++ b/charts/langsmith-auth-proxy/templates/ingress.yaml @@ -0,0 +1,40 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "langsmith.fullname" . }}-ingress + namespace: {{ .Values.namespace | default .Release.Namespace }} + annotations: + {{- include "langsmith.annotations" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "langsmith.labels" . | nindent 4 }} + {{- with .Values.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.ingressClassName }} + ingressClassName: {{ . }} + {{- end }} + {{- with .Values.ingress.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host }} + http: + paths: + {{- range .paths }} + - path: {{ .path | default "/" }} + pathType: {{ .pathType | default "Prefix" }} + backend: + service: + name: {{ include "langsmith.fullname" $ }}-{{ $.Values.authProxy.name }} + port: + number: {{ $.Values.authProxy.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/langsmith-auth-proxy/values.yaml b/charts/langsmith-auth-proxy/values.yaml new file mode 100644 index 00000000..c656a192 --- /dev/null +++ b/charts/langsmith-auth-proxy/values.yaml @@ -0,0 +1,183 @@ +# -- Provide a name in place of `langsmith-auth-proxy` +nameOverride: "" +# -- String to fully override `"langsmith.fullname"` +fullnameOverride: "" +# -- Namespace to install the chart into. If not set, will use the namespace of the current context. +namespace: "" +# -- Annotations that will be applied to all resources created by the chart +commonAnnotations: {} +# -- Annotations that will be applied to all pods created by the chart +commonPodAnnotations: {} +# -- Labels that will be applied to all resources created by the chart +commonLabels: {} +# -- Common pod security context applied to all pods. Component-specific podSecurityContext values will be merged on top of this (component values take precedence). +commonPodSecurityContext: {} + +images: + # -- If supplied, all children .repository values will be prepended with this registry name + `/` + registry: "" + imagePullSecrets: [] + authProxyImage: + repository: "docker.io/envoyproxy/envoy" + pullPolicy: IfNotPresent + tag: "v1.37-latest" + +# Auth Proxy - Envoy-based proxy for validating LangSmith-signed JWTs +# and optionally calling an external auth service before forwarding to an upstream LLM provider or gateway. +# Separate ingress from the main LangSmith app — different hostname, streaming-optimized. +authProxy: + enabled: true + name: "auth-proxy" + containerPort: 10000 + # -- Upstream LLM provider URL (e.g. https://api.openai.com) + upstream: "https://api.openai.com" + # -- JWT issuer claim to validate + jwtIssuer: "langsmith" + # -- JWT audience claims to validate. Must match audiences in the signed JWT. + jwtAudiences: [] + # -- JWKS JSON string containing the public keys for JWT validation. + # Generate with the LangSmith JWKS tooling and paste the full JSON here. + jwksJson: "" + # -- Idle timeout for streaming responses (e.g. SSE from LLM providers) + streamIdleTimeout: "300s" + # External authorization service configuration (for injecting LLM provider auth headers) + extAuthz: + enabled: false + # -- HTTP service URL for ext_authz (e.g. http://my-auth-service:8080) + serviceUrl: "" + # -- Timeout for ext_authz requests + timeout: "10s" + # -- Whether to send the request body to ext_authz + sendBody: false + # -- Maximum request body bytes to buffer for ext_authz + maxRequestBytes: 8192 + ingress: + enabled: false + ingressClassName: "" + labels: {} + # -- Annotations for streaming support. Defaults shown are for nginx ingress controller. + annotations: {} + # nginx.ingress.kubernetes.io/proxy-buffering: "off" + # nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + # nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + hosts: [] + # - host: llm-proxy.example.com + # paths: + # - path: / + # pathType: Prefix + tls: [] + # - secretName: llm-proxy-tls + # hosts: + # - llm-proxy.example.com + # -- ArgoCD Rollouts configuration. If enabled, will create a Rollout resource instead of a Deployment. See https://argo-rollouts.readthedocs.io/ + rollout: + enabled: false + # -- Rollout strategy configuration. See https://argo-rollouts.readthedocs.io/en/stable/features/specification/ + strategy: + canary: + steps: + - setWeight: 100 + deployment: + replicas: 1 + labels: {} + annotations: {} + podSecurityContext: {} + securityContext: {} + resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + command: + - "envoy" + - "-c" + - "/etc/envoy/envoy.yaml" + startupProbe: + httpGet: + path: /healthz + port: 10000 + failureThreshold: 6 + periodSeconds: 10 + timeoutSeconds: 1 + livenessProbe: + httpGet: + path: /healthz + port: 10000 + failureThreshold: 6 + periodSeconds: 10 + timeoutSeconds: 1 + readinessProbe: + httpGet: + path: /healthz + port: 10000 + failureThreshold: 6 + periodSeconds: 10 + timeoutSeconds: 1 + extraContainerConfig: {} + extraEnv: [] + sidecars: [] + initContainers: [] + nodeSelector: {} + tolerations: [] + topologySpreadConstraints: [] + affinity: {} + volumes: [] + volumeMounts: [] + terminationGracePeriodSeconds: 30 + # Autoscaling configuration. + autoscaling: + # HPA-specific configuration + hpa: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 50 + targetMemoryUtilizationPercentage: 80 + additionalMetrics: [] + pdb: + enabled: false + minAvailable: 1 + labels: {} + annotations: {} + service: + type: ClusterIP + port: 10000 + labels: {} + annotations: {} + loadBalancerSourceRanges: [] + loadBalancerIP: "" + serviceAccount: + create: true + name: "" + labels: {} + annotations: {} + automountServiceAccountToken: true + +# -- Top-level Ingress configuration (alternative to authProxy.ingress) +ingress: + enabled: false + ingressClassName: "" + labels: {} + annotations: {} + hosts: [] + # - host: llm-proxy.example.com + # paths: + # - path: / + # pathType: Prefix + tls: [] + +# -- Gateway API HTTPRoute configuration +gateway: + enabled: false + # -- Name of the Gateway resource to attach to + name: "" + # -- Namespace of the Gateway resource (if different from chart namespace) + namespace: "" + # -- SectionName of the Gateway listener to attach to + sectionName: "" + labels: {} + annotations: {} + # -- Hostnames to match on + hostnames: [] diff --git a/charts/langsmith/templates/_helpers.tpl b/charts/langsmith/templates/_helpers.tpl deleted file mode 100644 index 66c78713..00000000 --- a/charts/langsmith/templates/_helpers.tpl +++ /dev/null @@ -1,765 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "langsmith.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "langsmith.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "langsmith.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "langsmith.labels" -}} -{{- if .Values.commonLabels }} -{{ toYaml .Values.commonLabels }} -{{- end }} -helm.sh/chart: {{ include "langsmith.chart" . }} -{{ include "langsmith.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Common annotations -*/}} -{{- define "langsmith.annotations" -}} -{{- if .Values.commonAnnotations }} -{{ toYaml .Values.commonAnnotations }} -{{- end }} -helm.sh/chart: {{ include "langsmith.chart" . }} -{{ include "langsmith.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Common pod annotations -*/}} -{{- define "langsmith.commonPodAnnotations" -}} -{{- if .Values.commonPodAnnotations }} -{{ toYaml .Values.commonPodAnnotations }} -{{- end }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "langsmith.selectorLabels" -}} -app.kubernetes.io/name: {{ include "langsmith.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Name of the secret containing the secrets for this chart. This can be overridden by a secrets file created by -the user or some other secret provisioning mechanism -*/}} -{{- define "langsmith.secretsName" -}} -{{- if .Values.config.existingSecretName }} -{{- .Values.config.existingSecretName }} -{{- else }} -{{- include "langsmith.fullname" . }}-secrets -{{- end }} -{{- end }} - -{{/* -Name of the secret containing the secrets for postgres. This can be overridden by a secrets file created by -the user or some other secret provisioning mechanism -*/}} -{{- define "langsmith.postgresSecretsName" -}} -{{- if .Values.postgres.external.existingSecretName }} -{{- .Values.postgres.external.existingSecretName }} -{{- else }} -{{- include "langsmith.fullname" . }}-postgres -{{- end }} -{{- end }} - -{{/* -Name of the secret containing the secrets for redis. This can be overridden by a secrets file created by -the user or some other secret provisioning mechanism -*/}} -{{- define "langsmith.redisSecretsName" -}} -{{- if .Values.redis.external.existingSecretName }} -{{- .Values.redis.external.existingSecretName }} -{{- else }} -{{- include "langsmith.fullname" . }}-redis -{{- end }} -{{- end }} - -{{/* -Name of the secret containing the secrets for clickhouse. This can be overridden by a secrets file created by -the user or some other secret provisioning mechanism -*/}} -{{- define "langsmith.clickhouseSecretsName" -}} -{{- if .Values.clickhouse.external.existingSecretName }} -{{- .Values.clickhouse.external.existingSecretName }} -{{- else }} -{{- include "langsmith.fullname" . }}-clickhouse -{{- end }} -{{- end }} - -{{/* Include these env vars if they aren't defined in .Values.commonEnv */}} -{{- define "langsmith.conditionalEnvVars" -}} -- name: X_SERVICE_AUTH_JWT_SECRET - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: api_key_salt - optional: {{ .Values.config.disableSecretCreation }} -{{- end }} -{{- define "langsmith.conditionalEnvVarsResolved" -}} - {{- $commonEnvKeys := list -}} - {{- range $i, $commonEnvVar := .Values.commonEnv -}} - {{- $commonEnvKeys = append $commonEnvKeys $commonEnvVar.name -}} - {{- end -}} - - {{- $resolvedEnvVars := list -}} - {{- range $i, $envVar := include "langsmith.conditionalEnvVars" . | fromYamlArray }} - {{- if not (has $envVar.name $commonEnvKeys) }} - {{- $resolvedEnvVars = append $resolvedEnvVars $envVar -}} - {{- end }} - {{- end }} - - {{- if gt (len $resolvedEnvVars) 0 -}} - {{ $resolvedEnvVars | toYaml }} - {{- end -}} -{{- end }} - - -{{/* -Template for merging commonPodSecurityContext with component-specific podSecurityContext. -Component-specific values take precedence over common values. -Usage: {{ include "langsmith.podSecurityContext" (dict "Values" .Values "componentSecurityContext" .Values.backend.deployment.podSecurityContext) }} -*/}} -{{- define "langsmith.podSecurityContext" -}} -{{- $merged := merge .componentSecurityContext .Values.commonPodSecurityContext -}} -{{- toYaml $merged -}} -{{- end -}} - -{{/* -Template containing common environment variables that are used by several services. -*/}} -{{- define "langsmith.commonEnv" -}} -- name: POSTGRES_DATABASE_URI - valueFrom: - secretKeyRef: - name: {{ include "langsmith.postgresSecretsName" . }} - key: {{ .Values.postgres.external.connectionUrlSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -{{- if .Values.postgres.external.enabled }} -- name: POSTGRES_SCHEMA - value: {{ .Values.postgres.external.schema | quote }} -- name: POSTGRES_TLS - value: {{ .Values.postgres.external.customTls | quote }} -{{- if .Values.postgres.external.clientCert.secretName }} -- name: POSTGRES_TLS_CLIENT_CERT_PATH - value: /etc/postgres/certs/client.crt -- name: POSTGRES_TLS_CLIENT_KEY_PATH - value: /etc/postgres/certs/client.key -{{- end }} -{{- end }} -{{- if .Values.config.hostname }} -- name: LANGSMITH_URL - value: {{ include "langsmith.hostnameWithoutProtocol" . }}{{- with .Values.config.basePath }}/{{ . }}{{- end }} -- name: HOST_BACKEND_ENDPOINT_PUBLIC - value: {{ .Values.config.hostname }}/api-host -{{- end }} -- name: REDIS_CLUSTER_ENABLED - value: {{ .Values.redis.external.cluster.enabled | quote }} -{{- if .Values.redis.external.cluster.enabled }} -- name: REDIS_CLUSTER_DATABASE_URIS - valueFrom: - secretKeyRef: - name: {{ include "langsmith.redisSecretsName" . }} - key: {{ .Values.redis.external.cluster.nodeUrisSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -- name: REDIS_CLUSTER_PASSWORD - valueFrom: - secretKeyRef: - name: {{ include "langsmith.redisSecretsName" . }} - key: {{ .Values.redis.external.cluster.passwordSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -- name: REDIS_CLUSTER_USE_SSL_CONNECTION - value: {{ .Values.redis.external.cluster.tlsEnabled | quote }} -{{- else }} -- name: REDIS_DATABASE_URI - valueFrom: - secretKeyRef: - name: {{ include "langsmith.redisSecretsName" . }} - key: {{ .Values.redis.external.connectionUrlSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -{{- end }} -{{- if .Values.redis.external.clientCert.secretName }} -- name: REDIS_TLS_CLIENT_CERT_PATH - value: /etc/redis/certs/client.crt -- name: REDIS_TLS_CLIENT_KEY_PATH - value: /etc/redis/certs/client.key -{{- end }} -{{- if .Values.postgres.external.iamAuthProvider }} -- name: POSTGRES_IAM_AUTH_PROVIDER - value: {{ .Values.postgres.external.iamAuthProvider | quote }} -{{- end }} -{{- if .Values.redis.external.iamAuthProvider }} -- name: REDIS_IAM_AUTH_PROVIDER - value: {{ .Values.redis.external.iamAuthProvider | quote }} -{{- end }} -- name: CLICKHOUSE_HYBRID - value: {{ .Values.clickhouse.external.hybrid | quote }} -- name: CLICKHOUSE_DB - valueFrom: - secretKeyRef: - name: {{ include "langsmith.clickhouseSecretsName" . }} - key: {{ .Values.clickhouse.external.databaseSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -- name: CLICKHOUSE_HOST - valueFrom: - secretKeyRef: - name: {{ include "langsmith.clickhouseSecretsName" . }} - key: {{ .Values.clickhouse.external.hostSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -- name: CLICKHOUSE_PORT - valueFrom: - secretKeyRef: - name: {{ include "langsmith.clickhouseSecretsName" . }} - key: {{ .Values.clickhouse.external.portSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -- name: CLICKHOUSE_NATIVE_PORT - valueFrom: - secretKeyRef: - name: {{ include "langsmith.clickhouseSecretsName" . }} - key: {{ .Values.clickhouse.external.nativePortSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -- name: CLICKHOUSE_USER - valueFrom: - secretKeyRef: - name: {{ include "langsmith.clickhouseSecretsName" . }} - key: {{ .Values.clickhouse.external.userSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -- name: CLICKHOUSE_PASSWORD - valueFrom: - secretKeyRef: - name: {{ include "langsmith.clickhouseSecretsName" . }} - key: {{ .Values.clickhouse.external.passwordSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -- name: CLICKHOUSE_TLS - valueFrom: - secretKeyRef: - name: {{ include "langsmith.clickhouseSecretsName" . }} - key: {{ .Values.clickhouse.external.tlsSecretKey }} - optional: {{ .Values.config.disableSecretCreation }} -{{- if .Values.clickhouse.external.clientCert.secretName }} -- name: CLICKHOUSE_TLS_CLIENT_CERT_PATH - value: /etc/clickhouse/certs/client.crt -- name: CLICKHOUSE_TLS_CLIENT_KEY_PATH - value: /etc/clickhouse/certs/client.key -{{- end }} -- name: CLICKHOUSE_CLUSTER - value: {{ .Values.clickhouse.external.cluster | quote }} -- name: LOG_LEVEL - value: {{ .Values.config.logLevel | quote }} -{{- if .Values.config.oauth.enabled }} -- name: OAUTH_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: oauth_client_id - optional: {{ .Values.config.disableSecretCreation }} -- name: OAUTH_ISSUER_URL - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: oauth_issuer_url - optional: {{ .Values.config.disableSecretCreation }} -{{- if eq .Values.config.authType "mixed" }} -- name: OAUTH_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: oauth_client_secret - optional: {{ .Values.config.disableSecretCreation }} -{{- end }} -{{- end }} -- name: LANGSMITH_LICENSE_KEY - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: langsmith_license_key - optional: {{ .Values.config.disableSecretCreation }} -- name: API_KEY_SALT - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: api_key_salt - optional: {{ .Values.config.disableSecretCreation }} -- name: BASIC_AUTH_ENABLED - value: {{ .Values.config.basicAuth.enabled | quote }} -{{- if .Values.config.basicAuth.enabled }} -- name: BASIC_AUTH_JWT_SECRET - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: jwt_secret - optional: {{ .Values.config.disableSecretCreation }} -{{- end }} -- name: FF_ORG_CREATION_DISABLED - value: {{ .Values.config.userOrgCreationDisabled | quote }} -- name: FF_PERSONAL_ORGS_DISABLED - value: {{ .Values.config.personalOrgsDisabled | quote }} -{{- if .Values.config.ttl.enabled }} -- name: FF_TRACE_TIERS_ENABLED - value: {{ .Values.config.ttl.enabled | quote }} -- name: FF_UPGRADE_TRACE_TIER_ENABLED - value: "true" -- name: TRACE_TIER_TTL_DURATION_SEC_MAP - value: "{ \"longlived\": {{ .Values.config.ttl.ttl_period_seconds.longlived }}, \"shortlived\": {{ .Values.config.ttl.ttl_period_seconds.shortlived }} }" -{{- end }} -{{- if .Values.config.workspaceScopeOrgInvitesEnabled }} -- name: FF_WORKSPACE_SCOPE_ORG_INVITES_ENABLED - value: {{ .Values.config.workspaceScopeOrgInvitesEnabled | quote }} -{{- end }} -{{- if .Values.config.blobStorage.enabled }} -- name: FF_S3_STORAGE_ENABLED - value: {{ .Values.config.blobStorage.enabled | quote }} -- name: FF_BLOB_STORAGE_ENABLED - value: {{ .Values.config.blobStorage.enabled | quote }} -- name: BLOB_STORAGE_ENGINE - value: {{ .Values.config.blobStorage.engine | quote }} -- name: MIN_BLOB_STORAGE_SIZE_KB - value: {{ ternary 0 .Values.config.blobStorage.minBlobStorageSizeKb .Values.clickhouse.external.hybrid | quote }} -{{- if (or (eq .Values.config.blobStorage.engine "S3") (eq .Values.config.blobStorage.engine "s3")) }} -- name: S3_BUCKET_NAME - value: {{ .Values.config.blobStorage.bucketName | quote }} -- name: S3_RUN_MANIFEST_BUCKET_NAME - value: {{ .Values.config.blobStorage.bucketName | quote }} -- name: S3_API_URL - value: {{ .Values.config.blobStorage.apiURL | quote }} -- name: S3_ACCESS_KEY - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: blob_storage_access_key - optional: true -- name: S3_ACCESS_KEY_SECRET - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: blob_storage_access_key_secret - optional: true -- name: S3_USE_PATH_STYLE - value: {{ .Values.config.blobStorage.s3UsePathStyle | quote }} -{{- if .Values.config.blobStorage.kmsEncryptionEnabled }} -- name: S3_KMS_ENCRYPTION_ENABLED - value: {{ .Values.config.blobStorage.kmsEncryptionEnabled | quote }} -{{- if .Values.config.blobStorage.kmsKeyArn }} -- name: S3_KMS_KEY_ARN - value: {{ .Values.config.blobStorage.kmsKeyArn | quote }} -{{- end }} -{{- end }} -{{- end }} -{{- if (or (eq .Values.config.blobStorage.engine "Azure") (eq .Values.config.blobStorage.engine "azure")) }} -- name: AZURE_STORAGE_ACCOUNT_NAME - value: {{ .Values.config.blobStorage.azureStorageAccountName | quote }} -- name: AZURE_STORAGE_CONTAINER_NAME - value: {{ .Values.config.blobStorage.azureStorageContainerName | quote }} -- name: AZURE_STORAGE_SERVICE_URL_OVERRIDE - value: {{ .Values.config.blobStorage.azureStorageServiceUrlOverride | quote }} -- name: AZURE_STORAGE_ACCOUNT_KEY - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: azure_storage_account_key - optional: true -- name: AZURE_STORAGE_CONNECTION_STRING - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: azure_storage_connection_string - optional: true -{{- end }} -{{- end }} -- name: FF_CH_SEARCH_ENABLED - value: {{ ternary "false" .Values.config.blobStorage.chSearchEnabled .Values.clickhouse.external.hybrid | quote }} -{{ include "langsmith.conditionalEnvVarsResolved" . }} -- name: REDIS_RUNS_EXPIRY_SECONDS - value: {{ .Values.config.settings.redisRunsExpirySeconds | quote }} -{{- if .Values.config.deployment.enabled }} -- name: LANGGRAPH_CLOUD_LICENSE_KEY - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: langsmith_license_key - optional: {{ .Values.config.disableSecretCreation }} -- name: HOST_QUEUE - value: "host" -- name: HOST_WORKER_RECONCILIATION_CRON_ENABLED - value: "true" -{{- if .Values.config.basePath }} -- name: HOST_LANGCHAIN_API_ENDPOINT - value: "http://{{ include "langsmith.fullname" . }}-{{ .Values.frontend.name }}.{{ .Values.namespace | default .Release.Namespace }}:{{ .Values.frontend.service.httpPort }}/{{ .Values.config.basePath}}/api/v1" -{{- else }} -- name: HOST_LANGCHAIN_API_ENDPOINT - value: "http://{{ include "langsmith.fullname" . }}-{{ .Values.frontend.name }}.{{ .Values.namespace | default .Release.Namespace }}:{{ .Values.frontend.service.httpPort }}/api/v1" -{{- end }} -- name: HOSTED_K8S_ROOT_DOMAIN - value: {{ include "langsmith.hostnameWithoutProtocol" . | quote }} -- name: HOSTED_K8S_SHARED_INGRESS - value: "true" -{{- end }} -- name: ENABLE_LGP_DEPLOYMENT_HEALTH_CHECK - value: {{ .Values.config.deployment.ingressHealthCheckEnabled | quote }} -{{- if and .Values.config.customCa.secretName .Values.config.customCa.secretKey }} -- name: SSL_CERT_FILE - value: /etc/ssl/certs/custom-ca-certificates.crt -{{- end }} -{{- if .Values.ingestQueue.enabled }} -- name: GO_QUEUE_ENABLED_ALL - value: "true" -- name: GO_FEEDBACK_QUEUE_ENABLED_ALL - value: "true" -- name: FF_PERSIST_BATCHED_RUNS_SUCCESS_LOGGING - value: "true" -{{- end }} -{{- if .Values.config.agentBuilder.enabled }} -- name: AGENT_BUILDER_ENCRYPTION_KEY - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: agent_builder_encryption_key -{{- end }} -{{- if .Values.config.insights.enabled }} -- name: CLIO_ENCRYPTION_KEY - valueFrom: - secretKeyRef: - name: {{ include "langsmith.secretsName" . }} - key: insights_encryption_key - optional: false -{{- end }} -{{- end }} - - -{{- define "aceBackend.serviceAccountName" -}} -{{- if .Values.aceBackend.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.aceBackend.name) .Values.aceBackend.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.aceBackend.serviceAccount.name }} -{{- end -}} -{{- end -}} - - -{{- define "backend.serviceAccountName" -}} -{{- if .Values.backend.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.backend.name) .Values.backend.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.backend.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "clickhouse.serviceAccountName" -}} -{{- if .Values.clickhouse.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.clickhouse.name) .Values.clickhouse.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.clickhouse.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "frontend.serviceAccountName" -}} -{{- if .Values.frontend.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.frontend.name) .Values.frontend.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.frontend.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "hostBackend.serviceAccountName" -}} -{{- if .Values.hostBackend.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.hostBackend.name) .Values.hostBackend.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.hostBackend.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "listener.serviceAccountName" -}} -{{- if .Values.listener.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.listener.name) .Values.listener.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.listener.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "operator.serviceAccountName" -}} -{{- if .Values.operator.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.operator.name) .Values.operator.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.operator.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "platformBackend.serviceAccountName" -}} -{{- if .Values.platformBackend.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.platformBackend.name) .Values.platformBackend.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.platformBackend.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "playground.serviceAccountName" -}} -{{- if .Values.playground.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.playground.name) .Values.playground.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.playground.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "postgres.serviceAccountName" -}} -{{- if .Values.postgres.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.postgres.name) .Values.postgres.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.postgres.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "queue.serviceAccountName" -}} -{{- if .Values.queue.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.queue.name) .Values.queue.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.queue.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "ingestQueue.serviceAccountName" -}} -{{- if .Values.ingestQueue.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.ingestQueue.name) .Values.ingestQueue.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.ingestQueue.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "redis.serviceAccountName" -}} -{{- if .Values.redis.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.redis.name) .Values.redis.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.redis.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "agentBuilderToolServer.serviceAccountName" -}} -{{- if .Values.agentBuilderToolServer.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.agentBuilderToolServer.name) .Values.agentBuilderToolServer.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.agentBuilderToolServer.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "agentBuilderTriggerServer.serviceAccountName" -}} -{{- if .Values.agentBuilderTriggerServer.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.agentBuilderTriggerServer.name) .Values.agentBuilderTriggerServer.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.agentBuilderTriggerServer.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "agentBootstrap.serviceAccountName" -}} -{{- if .Values.backend.agentBootstrap.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) "agent-bootstrap") .Values.backend.agentBootstrap.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.backend.agentBootstrap.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "agentBootstrap.createAgentProducts" -}} -{{- $createProducts := list }} -{{- if .Values.config.agentBuilder.enabled }} -{{- $createProducts = append $createProducts "agent_builder" }} -{{- end }} -{{- if .Values.config.insights.enabled }} -{{- $createProducts = append $createProducts "insights" }} -{{- end }} -{{ toYaml $createProducts }} -{{- end -}} - -{{- define "agentBootstrap.destroyAgentProducts" -}} -{{- $destroyProducts := list }} -{{- if not .Values.config.agentBuilder.enabled }} -{{- $destroyProducts = append $destroyProducts "agent_builder" }} -{{- end }} -{{- if not .Values.config.insights.enabled }} -{{- $destroyProducts = append $destroyProducts "insights" }} -{{- end }} -{{ toYaml $destroyProducts }} -{{- end -}} - -{{/* Fail on duplicate keys in the inputted list of environment variables */}} -{{- define "langsmith.detectDuplicates" -}} -{{- $inputList := . -}} -{{- $keyCounts := dict -}} -{{- $duplicates := list -}} - -{{- range $i, $val := $inputList }} - {{- $key := $val.name -}} - {{- if hasKey $keyCounts $key }} - {{- $_ := set $keyCounts $key (add (get $keyCounts $key) 1) -}} - {{- else }} - {{- $_ := set $keyCounts $key 1 -}} - {{- end }} - {{- if gt (get $keyCounts $key) 1 }} - {{- $duplicates = append $duplicates $key -}} - {{- end }} -{{- end }} - -{{- if gt (len $duplicates) 0 }} - {{ fail (printf "Duplicate keys detected: %v" $duplicates) }} -{{- end }} -{{- end -}} - -{{- define "langsmith.checksumAnnotations"}} -checksum/config: {{ include (print $.Template.BasePath "/config-map.yaml") . | sha256sum }} -checksum/frontend-config: {{ include (print $.Template.BasePath "/frontend/config-map.yaml") . | sha256sum }} -{{- if not .Values.config.existingSecretName }} -checksum/secrets: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }} -{{- end }} -{{- if not .Values.redis.external.existingSecretName }} -checksum/redis: {{ include (print $.Template.BasePath "/redis/secrets.yaml") . | sha256sum }} -{{- end }} -{{- if not .Values.postgres.external.existingSecretName }} -checksum/postgres: {{ include (print $.Template.BasePath "/postgres/secrets.yaml") . | sha256sum }} -{{- end }} -{{- if not .Values.clickhouse.external.existingSecretName }} -checksum/clickhouse: {{ include (print $.Template.BasePath "/clickhouse/secrets.yaml") . | sha256sum }} -{{- end }} -{{- end }} - -{{/* -Creates the image reference used for Langsmith deployments. If registry is specified, concatenate it, along with a '/'. -*/}} -{{- define "langsmith.image" -}} -{{- $imageConfig := index .Values.images .component -}} -{{- if .Values.images.registry -}} -{{ .Values.images.registry }}/{{ $imageConfig.repository }}:{{ $imageConfig.tag | default .Chart.AppVersion }} -{{- else -}} -{{ $imageConfig.repository }}:{{ $imageConfig.tag | default .Chart.AppVersion }} -{{- end -}} - -{{- end -}} - -{{- define "langsmith.tlsVolumeMounts" -}} -{{- $mounts := list -}} -{{- if and .Values.config.customCa.secretName .Values.config.customCa.secretKey -}} -{{- $mounts = append $mounts (dict "name" "langsmith-custom-ca" "mountPath" "/etc/ssl/certs/custom-ca-certificates.crt" "subPath" "ca-certificates.crt" "readOnly" true) -}} -{{- end -}} -{{- if .Values.redis.external.clientCert.secretName -}} -{{- $mounts = append $mounts (dict "name" "redis-client-cert" "mountPath" "/etc/redis/certs" "readOnly" true) -}} -{{- end -}} -{{- if .Values.postgres.external.clientCert.secretName -}} -{{- $mounts = append $mounts (dict "name" "postgres-client-cert" "mountPath" "/etc/postgres/certs" "readOnly" true) -}} -{{- end -}} -{{- if .Values.clickhouse.external.clientCert.secretName -}} -{{- $mounts = append $mounts (dict "name" "clickhouse-client-cert" "mountPath" "/etc/clickhouse/certs" "readOnly" true) -}} -{{- end -}} -{{ $mounts | toYaml }} -{{- end -}} - -{{- define "langsmith.tlsVolumes" -}} -{{- $volumes := list -}} -{{- if and .Values.config.customCa.secretName .Values.config.customCa.secretKey -}} -{{- $volumes = append $volumes (dict "name" "langsmith-custom-ca" "secret" (dict "secretName" .Values.config.customCa.secretName "items" (list (dict "key" .Values.config.customCa.secretKey "path" "ca-certificates.crt")))) -}} -{{- end -}} -{{- if .Values.redis.external.clientCert.secretName -}} -{{- $volumes = append $volumes (dict "name" "redis-client-cert" "secret" (dict "secretName" .Values.redis.external.clientCert.secretName "items" (list (dict "key" .Values.redis.external.clientCert.certSecretKey "path" "client.crt" "mode" 0644) (dict "key" .Values.redis.external.clientCert.keySecretKey "path" "client.key" "mode" 0640)))) -}} -{{- end -}} -{{- if .Values.postgres.external.clientCert.secretName -}} -{{- $volumes = append $volumes (dict "name" "postgres-client-cert" "secret" (dict "secretName" .Values.postgres.external.clientCert.secretName "items" (list (dict "key" .Values.postgres.external.clientCert.certSecretKey "path" "client.crt" "mode" 0644) (dict "key" .Values.postgres.external.clientCert.keySecretKey "path" "client.key" "mode" 0640)))) -}} -{{- end -}} -{{- if .Values.clickhouse.external.clientCert.secretName -}} -{{- $volumes = append $volumes (dict "name" "clickhouse-client-cert" "secret" (dict "secretName" .Values.clickhouse.external.clientCert.secretName "items" (list (dict "key" .Values.clickhouse.external.clientCert.certSecretKey "path" "client.crt" "mode" 0644) (dict "key" .Values.clickhouse.external.clientCert.keySecretKey "path" "client.key" "mode" 0640)))) -}} -{{- end -}} -{{ $volumes | toYaml }} -{{- end -}} - -{{/* -Strip protocol (http://, https://, etc.) from hostname -*/}} -{{- define "langsmith.hostnameWithoutProtocol" -}} -{{- if .Values.config.hostname -}} -{{- regexReplaceAll "^[a-zA-Z][a-zA-Z0-9+.-]*://" .Values.config.hostname "" -}} -{{- end -}} -{{- end -}} - -{{- define "agentBuilderOAuthEnvVars" -}} -{{- if .Values.config.agentBuilder.oauth.googleOAuthProvider }} -- name: "GOOGLE_OAUTH_PROVIDER" - value: {{ .Values.config.agentBuilder.oauth.googleOAuthProvider | quote }} -{{- end }} -{{- if .Values.config.agentBuilder.oauth.slackOAuthProvider }} -- name: "SLACK_OAUTH_PROVIDER" - value: {{ .Values.config.agentBuilder.oauth.slackOAuthProvider | quote }} -{{- end }} -{{- if .Values.config.agentBuilder.oauth.linkedinOAuthProvider }} -- name: "LINKEDIN_OAUTH_PROVIDER" - value: {{ .Values.config.agentBuilder.oauth.linkedinOAuthProvider | quote }} -{{- end }} -{{- if .Values.config.agentBuilder.oauth.linearOAuthProvider }} -- name: "LINEAR_OAUTH_PROVIDER" - value: {{ .Values.config.agentBuilder.oauth.linearOAuthProvider | quote }} -{{- end }} -{{- if .Values.config.agentBuilder.oauth.githubOAuthProvider }} -- name: "GITHUB_OAUTH_PROVIDER" - value: {{ .Values.config.agentBuilder.oauth.githubOAuthProvider | quote }} -{{- end }} -{{- end -}} - -{{- define "agentBuilderToolServerEnvVars" -}} -- name: "PORT" - value: "{{ .Values.agentBuilderToolServer.containerPort }}" -{{- include "agentBuilderOAuthEnvVars" . }} -{{- end -}} - -{{- define "authProxy.serviceAccountName" -}} -{{- if .Values.authProxy.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.authProxy.name) .Values.authProxy.serviceAccount.name | trunc 63 | trimSuffix "-" }} -{{- else -}} - {{ default "default" .Values.authProxy.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{- define "agentBuilderTriggerServerEnvVars" -}} -- name: "PORT" - value: "{{ .Values.agentBuilderTriggerServer.containerPort }}" -- name: "TRIGGER_SERVER_HOST_API_URL" - value: "http://{{ include "langsmith.fullname" . }}-{{ .Values.hostBackend.name }}.{{ .Values.namespace | default .Release.Namespace }}.svc.{{ .Values.clusterDomain }}:{{ .Values.hostBackend.service.port }}" -{{- include "agentBuilderOAuthEnvVars" . }} -{{- if .Values.config.agentBuilder.oauth.slackSigningSecret }} -- name: "SLACK_SIGNING_SECRET" - value: {{ .Values.config.agentBuilder.oauth.slackSigningSecret | quote }} -{{- end }} -{{- if .Values.config.agentBuilder.oauth.slackBotId }} -- name: "AGENT_BUILDER_SLACK_BOT_ID" - value: {{ .Values.config.agentBuilder.oauth.slackBotId | quote }} -{{- end }} -{{- end -}} diff --git a/charts/langsmith/values.yaml b/charts/langsmith/values.yaml index 0d15c002..61fc126a 100644 --- a/charts/langsmith/values.yaml +++ b/charts/langsmith/values.yaml @@ -2165,136 +2165,3 @@ agentBuilderTriggerServer: labels: {} annotations: {} automountServiceAccountToken: true - -# Auth Proxy - Optional Envoy-based proxy for validating LangSmith-signed JWTs -# and optionally calling an external auth service before forwarding to an upstream LLM provider or gateway. -# Separate ingress from the main LangSmith app — different hostname, streaming-optimized. -authProxy: - enabled: false - name: "auth-proxy" - containerPort: 10000 - # -- Upstream LLM provider URL (e.g. https://api.openai.com) - upstream: "https://api.openai.com" - # -- JWT issuer claim to validate - jwtIssuer: "langsmith" - # -- JWT audience claims to validate. Must match audiences in the signed JWT. - jwtAudiences: [] - # -- JWKS JSON string containing the public keys for JWT validation. - # Generate with the LangSmith JWKS tooling and paste the full JSON here. - jwksJson: "" - # -- Idle timeout for streaming responses (e.g. SSE from LLM providers) - streamIdleTimeout: "300s" - # External authorization service configuration (for injecting LLM provider auth headers) - extAuthz: - enabled: false - # -- HTTP service URL for ext_authz (e.g. http://my-auth-service:8080) - serviceUrl: "" - # -- Timeout for ext_authz requests - timeout: "10s" - # -- Whether to send the request body to ext_authz - sendBody: false - # -- Maximum request body bytes to buffer for ext_authz - maxRequestBytes: 8192 - ingress: - enabled: false - ingressClassName: "" - labels: {} - # -- Annotations for streaming support. Defaults shown are for nginx ingress controller. - annotations: {} - # nginx.ingress.kubernetes.io/proxy-buffering: "off" - # nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" - # nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" - hosts: [] - # - host: llm-proxy.example.com - # paths: - # - path: / - # pathType: Prefix - tls: [] - # - secretName: llm-proxy-tls - # hosts: - # - llm-proxy.example.com - # -- ArgoCD Rollouts configuration. If enabled, will create a Rollout resource instead of a Deployment. See https://argo-rollouts.readthedocs.io/ - rollout: - enabled: false - # -- Rollout strategy configuration. See https://argo-rollouts.readthedocs.io/en/stable/features/specification/ - strategy: - canary: - steps: - - setWeight: 100 - deployment: - replicas: 1 - labels: {} - annotations: {} - podSecurityContext: {} - securityContext: {} - resources: - limits: - cpu: 500m - memory: 256Mi - requests: - cpu: 100m - memory: 128Mi - command: - - "envoy" - - "-c" - - "/etc/envoy/envoy.yaml" - startupProbe: - httpGet: - path: /healthz - port: 10000 - failureThreshold: 6 - periodSeconds: 10 - timeoutSeconds: 1 - livenessProbe: - httpGet: - path: /healthz - port: 10000 - failureThreshold: 6 - periodSeconds: 10 - timeoutSeconds: 1 - readinessProbe: - httpGet: - path: /healthz - port: 10000 - failureThreshold: 6 - periodSeconds: 10 - timeoutSeconds: 1 - extraContainerConfig: {} - extraEnv: [] - sidecars: [] - initContainers: [] - nodeSelector: {} - tolerations: [] - topologySpreadConstraints: [] - affinity: {} - volumes: [] - volumeMounts: [] - terminationGracePeriodSeconds: 30 - # Autoscaling configuration. - autoscaling: - # HPA-specific configuration - hpa: - enabled: false - minReplicas: 1 - maxReplicas: 5 - targetCPUUtilizationPercentage: 50 - targetMemoryUtilizationPercentage: 80 - additionalMetrics: [] - pdb: - enabled: false - minAvailable: 1 - labels: {} - annotations: {} - service: - type: ClusterIP - port: 10000 - labels: {} - annotations: {} - loadBalancerSourceRanges: [] - loadBalancerIP: "" - serviceAccount: - create: true - name: "" - labels: {} - annotations: {} - automountServiceAccountToken: true From 460197a52cb1449279b6d55f48f481458eab828f Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 14:48:58 -0500 Subject: [PATCH 05/17] remove dup ingress, fix naming --- .../ci/auth-proxy-values.yaml | 33 +++++++-------- .../templates/_helpers.tpl | 30 +++++++------- .../templates/auth-proxy/config-map.yaml | 6 +-- .../templates/auth-proxy/deployment.yaml | 22 +++++----- .../templates/auth-proxy/hpa.yaml | 6 +-- .../templates/auth-proxy/ingress.yaml | 40 ------------------- .../templates/auth-proxy/pdb.yaml | 10 ++--- .../templates/auth-proxy/service-account.yaml | 4 +- .../templates/auth-proxy/service.yaml | 12 +++--- .../templates/http_route.yaml | 8 ++-- .../templates/ingress.yaml | 8 ++-- charts/langsmith-auth-proxy/values.yaml | 29 +++++--------- 12 files changed, 79 insertions(+), 129 deletions(-) delete mode 100644 charts/langsmith-auth-proxy/templates/auth-proxy/ingress.yaml diff --git a/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml b/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml index 9a40f261..52164641 100644 --- a/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml +++ b/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml @@ -12,19 +12,20 @@ authProxy: enabled: true serviceUrl: "http://auth-service:8080" timeout: "10s" - ingress: - enabled: true - ingressClassName: "nginx" - annotations: - nginx.ingress.kubernetes.io/proxy-buffering: "off" - nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" - nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" - hosts: - - host: llm-proxy.example.com - paths: - - path: / - pathType: Prefix - tls: - - secretName: llm-proxy-tls - hosts: - - llm-proxy.example.com + +ingress: + enabled: true + ingressClassName: "nginx" + annotations: + nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + hosts: + - host: llm-proxy.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: llm-proxy-tls + hosts: + - llm-proxy.example.com diff --git a/charts/langsmith-auth-proxy/templates/_helpers.tpl b/charts/langsmith-auth-proxy/templates/_helpers.tpl index 173c1ea8..3bbb30f4 100644 --- a/charts/langsmith-auth-proxy/templates/_helpers.tpl +++ b/charts/langsmith-auth-proxy/templates/_helpers.tpl @@ -1,7 +1,7 @@ {{/* Expand the name of the chart. */}} -{{- define "langsmith.name" -}} +{{- define "authProxy.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} @@ -10,7 +10,7 @@ Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} -{{- define "langsmith.fullname" -}} +{{- define "authProxy.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} @@ -26,19 +26,19 @@ If release name contains chart name it will be used as a full name. {{/* Create chart name and version as used by the chart label. */}} -{{- define "langsmith.chart" -}} +{{- define "authProxy.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} -{{- define "langsmith.labels" -}} +{{- define "authProxy.labels" -}} {{- if .Values.commonLabels }} {{ toYaml .Values.commonLabels }} {{- end }} -helm.sh/chart: {{ include "langsmith.chart" . }} -{{ include "langsmith.selectorLabels" . }} +helm.sh/chart: {{ include "authProxy.chart" . }} +{{ include "authProxy.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -48,12 +48,12 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Common annotations */}} -{{- define "langsmith.annotations" -}} +{{- define "authProxy.annotations" -}} {{- if .Values.commonAnnotations }} {{ toYaml .Values.commonAnnotations }} {{- end }} -helm.sh/chart: {{ include "langsmith.chart" . }} -{{ include "langsmith.selectorLabels" . }} +helm.sh/chart: {{ include "authProxy.chart" . }} +{{ include "authProxy.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -63,7 +63,7 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Common pod annotations */}} -{{- define "langsmith.commonPodAnnotations" -}} +{{- define "authProxy.commonPodAnnotations" -}} {{- if .Values.commonPodAnnotations }} {{ toYaml .Values.commonPodAnnotations }} {{- end }} @@ -72,8 +72,8 @@ Common pod annotations {{/* Selector labels */}} -{{- define "langsmith.selectorLabels" -}} -app.kubernetes.io/name: {{ include "langsmith.name" . }} +{{- define "authProxy.selectorLabels" -}} +app.kubernetes.io/name: {{ include "authProxy.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} @@ -81,7 +81,7 @@ app.kubernetes.io/instance: {{ .Release.Name }} Template for merging commonPodSecurityContext with component-specific podSecurityContext. Component-specific values take precedence over common values. */}} -{{- define "langsmith.podSecurityContext" -}} +{{- define "authProxy.podSecurityContext" -}} {{- $merged := merge .componentSecurityContext .Values.commonPodSecurityContext -}} {{- toYaml $merged -}} {{- end -}} @@ -89,7 +89,7 @@ Component-specific values take precedence over common values. {{/* Creates the image reference used for deployments. If registry is specified, concatenate it, along with a '/'. */}} -{{- define "langsmith.image" -}} +{{- define "authProxy.image" -}} {{- $imageConfig := index .Values.images .component -}} {{- if .Values.images.registry -}} {{ .Values.images.registry }}/{{ $imageConfig.repository }}:{{ $imageConfig.tag | default .Chart.AppVersion }} @@ -100,7 +100,7 @@ Creates the image reference used for deployments. If registry is specified, conc {{- define "authProxy.serviceAccountName" -}} {{- if .Values.authProxy.serviceAccount.create -}} - {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.authProxy.name) .Values.authProxy.serviceAccount.name | trunc 63 | trimSuffix "-" }} + {{ default (printf "%s-%s" (include "authProxy.fullname" .) .Values.authProxy.name) .Values.authProxy.serviceAccount.name | trunc 63 | trimSuffix "-" }} {{- else -}} {{ default "default" .Values.authProxy.serviceAccount.name }} {{- end -}} diff --git a/charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml index e6db8e62..7f91248e 100644 --- a/charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml +++ b/charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml @@ -17,12 +17,12 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + name: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} namespace: {{ .Values.namespace | default .Release.Namespace }} labels: - {{- include "langsmith.labels" . | nindent 4 }} + {{- include "authProxy.labels" . | nindent 4 }} annotations: - {{- include "langsmith.annotations" . | nindent 4 }} + {{- include "authProxy.annotations" . | nindent 4 }} data: envoy.yaml: | static_resources: diff --git a/charts/langsmith-auth-proxy/templates/auth-proxy/deployment.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/deployment.yaml index 3864234e..6a822cc0 100644 --- a/charts/langsmith-auth-proxy/templates/auth-proxy/deployment.yaml +++ b/charts/langsmith-auth-proxy/templates/auth-proxy/deployment.yaml @@ -9,15 +9,15 @@ apiVersion: apps/v1 kind: Deployment {{- end }} metadata: - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + name: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} namespace: {{ .Values.namespace | default .Release.Namespace }} labels: - {{- include "langsmith.labels" . | nindent 4 }} + {{- include "authProxy.labels" . | nindent 4 }} {{- with.Values.authProxy.deployment.labels }} {{- toYaml . | nindent 4 }} {{- end }} annotations: - {{- include "langsmith.annotations" . | nindent 4 }} + {{- include "authProxy.annotations" . | nindent 4 }} {{- with.Values.authProxy.deployment.annotations }} {{- toYaml . | nindent 4 }} {{- end }} @@ -27,12 +27,12 @@ spec: {{- end }} selector: matchLabels: - {{- include "langsmith.selectorLabels" . | nindent 6 }} - app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + {{- include "authProxy.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} template: metadata: annotations: - {{- include "langsmith.commonPodAnnotations" . | nindent 8 }} + {{- include "authProxy.commonPodAnnotations" . | nindent 8 }} {{- with .Values.authProxy.deployment.annotations }} {{- toYaml . | nindent 8 }} {{- end }} @@ -40,8 +40,8 @@ spec: {{- with.Values.authProxy.deployment.labels }} {{- toYaml . | nindent 8 }} {{- end }} - {{- include "langsmith.labels" . | nindent 8 }} - app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + {{- include "authProxy.labels" . | nindent 8 }} + app.kubernetes.io/component: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} spec: terminationGracePeriodSeconds: {{ .Values.authProxy.deployment.terminationGracePeriodSeconds }} {{- with .Values.images.imagePullSecrets }} @@ -49,7 +49,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} securityContext: - {{- include "langsmith.podSecurityContext" (dict "Values" .Values "componentSecurityContext" .Values.authProxy.deployment.podSecurityContext) | nindent 8 }} + {{- include "authProxy.podSecurityContext" (dict "Values" .Values "componentSecurityContext" .Values.authProxy.deployment.podSecurityContext) | nindent 8 }} serviceAccountName: {{ include "authProxy.serviceAccountName" . }} {{- with .Values.authProxy.deployment.initContainers }} initContainers: @@ -65,7 +65,7 @@ spec: env: {{ toYaml . | nindent 12 }} {{- end }} - image: {{ include "langsmith.image" (dict "Values" .Values "Chart" .Chart "component" "authProxyImage") | quote }} + image: {{ include "authProxy.image" (dict "Values" .Values "Chart" .Chart "component" "authProxyImage") | quote }} imagePullPolicy: {{ .Values.images.authProxyImage.pullPolicy }} ports: - name: envoy @@ -123,7 +123,7 @@ spec: {{- end }} - name: envoy-conf configMap: - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + name: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} items: - key: envoy.yaml path: envoy.yaml diff --git a/charts/langsmith-auth-proxy/templates/auth-proxy/hpa.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/hpa.yaml index 0f7b5704..adf152a4 100644 --- a/charts/langsmith-auth-proxy/templates/auth-proxy/hpa.yaml +++ b/charts/langsmith-auth-proxy/templates/auth-proxy/hpa.yaml @@ -2,10 +2,10 @@ apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + name: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} namespace: {{ .Values.namespace | default .Release.Namespace }} labels: - {{- include "langsmith.labels" . | nindent 4 }} + {{- include "authProxy.labels" . | nindent 4 }} spec: scaleTargetRef: {{- if .Values.authProxy.rollout.enabled }} @@ -15,7 +15,7 @@ spec: apiVersion: apps/v1 kind: Deployment {{- end }} - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + name: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} minReplicas: {{ .Values.authProxy.autoscaling.hpa.minReplicas }} maxReplicas: {{ .Values.authProxy.autoscaling.hpa.maxReplicas }} metrics: diff --git a/charts/langsmith-auth-proxy/templates/auth-proxy/ingress.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/ingress.yaml deleted file mode 100644 index 663a0921..00000000 --- a/charts/langsmith-auth-proxy/templates/auth-proxy/ingress.yaml +++ /dev/null @@ -1,40 +0,0 @@ -{{- if and .Values.authProxy.enabled .Values.authProxy.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} - namespace: {{ .Values.namespace | default .Release.Namespace }} - annotations: - {{- include "langsmith.annotations" . | nindent 4 }} - {{- with .Values.authProxy.ingress.annotations }} - {{- toYaml . | nindent 4 }} - {{- end }} - labels: - {{- include "langsmith.labels" . | nindent 4 }} - {{- with .Values.authProxy.ingress.labels }} - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- with .Values.authProxy.ingress.ingressClassName }} - ingressClassName: {{ . }} - {{- end }} - {{- with .Values.authProxy.ingress.tls }} - tls: - {{- toYaml . | nindent 4 }} - {{- end }} - rules: - {{- range .Values.authProxy.ingress.hosts }} - - host: {{ .host }} - http: - paths: - {{- range .paths }} - - path: {{ .path | default "/" }} - pathType: {{ .pathType | default "Prefix" }} - backend: - service: - name: {{ include "langsmith.fullname" $ }}-{{ $.Values.authProxy.name }} - port: - number: {{ $.Values.authProxy.service.port }} - {{- end }} - {{- end }} -{{- end }} diff --git a/charts/langsmith-auth-proxy/templates/auth-proxy/pdb.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/pdb.yaml index c8d7ff08..181d128b 100644 --- a/charts/langsmith-auth-proxy/templates/auth-proxy/pdb.yaml +++ b/charts/langsmith-auth-proxy/templates/auth-proxy/pdb.yaml @@ -2,23 +2,23 @@ apiVersion: policy/v1 kind: PodDisruptionBudget metadata: - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + name: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} namespace: {{ .Values.namespace | default .Release.Namespace }} labels: - {{- include "langsmith.labels" . | nindent 4 }} + {{- include "authProxy.labels" . | nindent 4 }} {{- with .Values.authProxy.pdb.labels }} {{- toYaml . | nindent 4 }} {{- end }} annotations: - {{- include "langsmith.annotations" . | nindent 4 }} + {{- include "authProxy.annotations" . | nindent 4 }} {{- with .Values.authProxy.pdb.annotations }} {{- toYaml . | nindent 4 }} {{- end }} spec: selector: matchLabels: - {{- include "langsmith.selectorLabels" . | nindent 6 }} - app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + {{- include "authProxy.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} {{- if .Values.authProxy.pdb.minAvailable }} minAvailable: {{ .Values.authProxy.pdb.minAvailable }} {{- end }} diff --git a/charts/langsmith-auth-proxy/templates/auth-proxy/service-account.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/service-account.yaml index a1855d03..7f4ef549 100644 --- a/charts/langsmith-auth-proxy/templates/auth-proxy/service-account.yaml +++ b/charts/langsmith-auth-proxy/templates/auth-proxy/service-account.yaml @@ -5,12 +5,12 @@ metadata: name: {{ include "authProxy.serviceAccountName" . }} namespace: {{ .Values.namespace | default .Release.Namespace }} labels: - {{- include "langsmith.labels" . | nindent 4 }} + {{- include "authProxy.labels" . | nindent 4 }} {{- with.Values.authProxy.deployment.labels }} {{- toYaml . | nindent 4 }} {{- end }} annotations: - {{- include "langsmith.annotations" . | nindent 4 }} + {{- include "authProxy.annotations" . | nindent 4 }} {{- with.Values.authProxy.serviceAccount.annotations }} {{- toYaml . | nindent 4 }} {{- end }} diff --git a/charts/langsmith-auth-proxy/templates/auth-proxy/service.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/service.yaml index 250b10b2..7acf79b6 100644 --- a/charts/langsmith-auth-proxy/templates/auth-proxy/service.yaml +++ b/charts/langsmith-auth-proxy/templates/auth-proxy/service.yaml @@ -2,16 +2,16 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + name: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} namespace: {{ .Values.namespace | default .Release.Namespace }} labels: - {{- include "langsmith.labels" . | nindent 4 }} + {{- include "authProxy.labels" . | nindent 4 }} {{- with.Values.authProxy.service.labels }} {{- toYaml . | nindent 4 }} {{- end }} - app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + app.kubernetes.io/component: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} annotations: - {{- include "langsmith.annotations" . | nindent 4 }} + {{- include "authProxy.annotations" . | nindent 4 }} {{- with.Values.authProxy.service.annotations }} {{- toYaml . | nindent 4 }} {{- end }} @@ -28,6 +28,6 @@ spec: targetPort: envoy protocol: TCP selector: - {{- include "langsmith.selectorLabels" . | nindent 4 }} - app.kubernetes.io/component: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + {{- include "authProxy.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} {{- end }} diff --git a/charts/langsmith-auth-proxy/templates/http_route.yaml b/charts/langsmith-auth-proxy/templates/http_route.yaml index 2f8c781e..34c47008 100644 --- a/charts/langsmith-auth-proxy/templates/http_route.yaml +++ b/charts/langsmith-auth-proxy/templates/http_route.yaml @@ -2,15 +2,15 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ include "langsmith.fullname" . }} + name: {{ include "authProxy.fullname" . }} namespace: {{ .Values.namespace | default .Release.Namespace }} annotations: - {{- include "langsmith.annotations" . | nindent 4 }} + {{- include "authProxy.annotations" . | nindent 4 }} {{- with .Values.gateway.annotations }} {{- toYaml . | nindent 4 }} {{- end }} labels: - {{- include "langsmith.labels" . | nindent 4 }} + {{- include "authProxy.labels" . | nindent 4 }} {{- with .Values.gateway.labels }} {{- toYaml . | nindent 4 }} {{- end }} @@ -33,6 +33,6 @@ spec: type: PathPrefix value: / backendRefs: - - name: {{ include "langsmith.fullname" . }}-{{ .Values.authProxy.name }} + - name: {{ include "authProxy.fullname" . }}-{{ .Values.authProxy.name }} port: {{ .Values.authProxy.service.port }} {{- end }} diff --git a/charts/langsmith-auth-proxy/templates/ingress.yaml b/charts/langsmith-auth-proxy/templates/ingress.yaml index ba16c53c..1d7523e2 100644 --- a/charts/langsmith-auth-proxy/templates/ingress.yaml +++ b/charts/langsmith-auth-proxy/templates/ingress.yaml @@ -2,15 +2,15 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: {{ include "langsmith.fullname" . }}-ingress + name: {{ include "authProxy.fullname" . }}-ingress namespace: {{ .Values.namespace | default .Release.Namespace }} annotations: - {{- include "langsmith.annotations" . | nindent 4 }} + {{- include "authProxy.annotations" . | nindent 4 }} {{- with .Values.ingress.annotations }} {{- toYaml . | nindent 4 }} {{- end }} labels: - {{- include "langsmith.labels" . | nindent 4 }} + {{- include "authProxy.labels" . | nindent 4 }} {{- with .Values.ingress.labels }} {{- toYaml . | nindent 4 }} {{- end }} @@ -32,7 +32,7 @@ spec: pathType: {{ .pathType | default "Prefix" }} backend: service: - name: {{ include "langsmith.fullname" $ }}-{{ $.Values.authProxy.name }} + name: {{ include "authProxy.fullname" $ }}-{{ $.Values.authProxy.name }} port: number: {{ $.Values.authProxy.service.port }} {{- end }} diff --git a/charts/langsmith-auth-proxy/values.yaml b/charts/langsmith-auth-proxy/values.yaml index c656a192..2cd0ded5 100644 --- a/charts/langsmith-auth-proxy/values.yaml +++ b/charts/langsmith-auth-proxy/values.yaml @@ -30,7 +30,7 @@ authProxy: name: "auth-proxy" containerPort: 10000 # -- Upstream LLM provider URL (e.g. https://api.openai.com) - upstream: "https://api.openai.com" + upstream: "" # -- JWT issuer claim to validate jwtIssuer: "langsmith" # -- JWT audience claims to validate. Must match audiences in the signed JWT. @@ -51,24 +51,6 @@ authProxy: sendBody: false # -- Maximum request body bytes to buffer for ext_authz maxRequestBytes: 8192 - ingress: - enabled: false - ingressClassName: "" - labels: {} - # -- Annotations for streaming support. Defaults shown are for nginx ingress controller. - annotations: {} - # nginx.ingress.kubernetes.io/proxy-buffering: "off" - # nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" - # nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" - hosts: [] - # - host: llm-proxy.example.com - # paths: - # - path: / - # pathType: Prefix - tls: [] - # - secretName: llm-proxy-tls - # hosts: - # - llm-proxy.example.com # -- ArgoCD Rollouts configuration. If enabled, will create a Rollout resource instead of a Deployment. See https://argo-rollouts.readthedocs.io/ rollout: enabled: false @@ -155,18 +137,25 @@ authProxy: annotations: {} automountServiceAccountToken: true -# -- Top-level Ingress configuration (alternative to authProxy.ingress) +# -- Ingress configuration ingress: enabled: false ingressClassName: "" labels: {} + # -- Annotations for streaming support. Defaults shown are for nginx ingress controller. annotations: {} + # nginx.ingress.kubernetes.io/proxy-buffering: "off" + # nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + # nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" hosts: [] # - host: llm-proxy.example.com # paths: # - path: / # pathType: Prefix tls: [] + # - secretName: llm-proxy-tls + # hosts: + # - llm-proxy.example.com # -- Gateway API HTTPRoute configuration gateway: From 98c81e877e2738f6d40e8bdb2d381a730ab4f415 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 15:02:30 -0500 Subject: [PATCH 06/17] fix json --- charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml b/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml index 52164641..e79bc470 100644 --- a/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml +++ b/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml @@ -6,7 +6,7 @@ authProxy: jwtIssuer: "langsmith" jwtAudiences: - "test-audience" - jwksJson: '{"kty": "RSA","use": "sig","alg": "RS256","kid": "f98e5ea5-2ee6-4141-b1fd-9a3ecb6648fd","d": "Ntcd3fjgYh1ytShRgfgEScbc1t_9H6mNZ5nkyjUJ9WpMUmBk9MltimV0qMDRWs85695c30YD-Uf5VMvgYszSQZZo3iNWX8bfKEffqYboN2zNyhvomB1dboyUXz4I3B4-7Zrxgdamd1adOPg7Rxedck8a3oJwE9FzpypCg67-mQjTnZ8RTTtu5ekvoXYsrR30qI_lWUGiA9aL6pCbTEQOjBombLNkOlwl2Hh7FORSvM3ViEMop7rMDvMAWRPcBcpJgwHhQTBhBx1QMi01DmdX7kXnnsTgrU4bxX9zgIXtBV7Fhlk1bvIVqOTT7M3JMbQG_MXLXjRbvj7bAHta1FRE9Q","n": "uqHU2bRgvKIBe88_ikr3MLdTa4W55gv3DjVFuB6hZxaJIbOzGXE3-FRf7cfqg0Exysow5uuXUUTtq_zaE3AZLvEOt3CmQ3su_OxHPsytTHwLcc74NCL7hozv1uAQTMWAof4_KvyYIYOX5_wRgwoahQJPDSvbQpZvjdxUR7muVps65idF6lZrvoRYQiyuyMzozyFYAqiOI9VIud3Z9S2gSsHRhExPf8UD-HKiTKPUOlWLCwiU9FWWRgYse0jPwzU6j2lXu38aJjJd43ROH7OrcWp4fdLY-pjLQb6rz-RshTgXPkvZxTmfLVSqUkHr0xkM-Rb4T1CmvV4DuXNDBkfkgw","e": "AQAB"}' + jwksJson: '{"keys": [{"kty": "RSA","use": "sig","alg": "RS256","kid": "f98e5ea5-2ee6-4141-b1fd-9a3ecb6648fd","d": "Ntcd3fjgYh1ytShRgfgEScbc1t_9H6mNZ5nkyjUJ9WpMUmBk9MltimV0qMDRWs85695c30YD-Uf5VMvgYszSQZZo3iNWX8bfKEffqYboN2zNyhvomB1dboyUXz4I3B4-7Zrxgdamd1adOPg7Rxedck8a3oJwE9FzpypCg67-mQjTnZ8RTTtu5ekvoXYsrR30qI_lWUGiA9aL6pCbTEQOjBombLNkOlwl2Hh7FORSvM3ViEMop7rMDvMAWRPcBcpJgwHhQTBhBx1QMi01DmdX7kXnnsTgrU4bxX9zgIXtBV7Fhlk1bvIVqOTT7M3JMbQG_MXLXjRbvj7bAHta1FRE9Q","n": "uqHU2bRgvKIBe88_ikr3MLdTa4W55gv3DjVFuB6hZxaJIbOzGXE3-FRf7cfqg0Exysow5uuXUUTtq_zaE3AZLvEOt3CmQ3su_OxHPsytTHwLcc74NCL7hozv1uAQTMWAof4_KvyYIYOX5_wRgwoahQJPDSvbQpZvjdxUR7muVps65idF6lZrvoRYQiyuyMzozyFYAqiOI9VIud3Z9S2gSsHRhExPf8UD-HKiTKPUOlWLCwiU9FWWRgYse0jPwzU6j2lXu38aJjJd43ROH7OrcWp4fdLY-pjLQb6rz-RshTgXPkvZxTmfLVSqUkHr0xkM-Rb4T1CmvV4DuXNDBkfkgw","e": "AQAB"}]}' streamIdleTimeout: "300s" extAuthz: enabled: true From 0650a0d548ccb60b2e983d1fc7186a2e46b5443d Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 15:38:21 -0500 Subject: [PATCH 07/17] e2e test --- .../langsmith-auth-proxy/e2e/e2e-values.yaml | 32 ++++ .../e2e/echo-upstream.yaml | 38 ++++ .../e2e/ext-authz-mock.py | 27 +++ charts/langsmith-auth-proxy/e2e/test.sh | 175 ++++++++++++++++++ 4 files changed, 272 insertions(+) create mode 100644 charts/langsmith-auth-proxy/e2e/e2e-values.yaml create mode 100644 charts/langsmith-auth-proxy/e2e/echo-upstream.yaml create mode 100644 charts/langsmith-auth-proxy/e2e/ext-authz-mock.py create mode 100755 charts/langsmith-auth-proxy/e2e/test.sh diff --git a/charts/langsmith-auth-proxy/e2e/e2e-values.yaml b/charts/langsmith-auth-proxy/e2e/e2e-values.yaml new file mode 100644 index 00000000..ead6ea99 --- /dev/null +++ b/charts/langsmith-auth-proxy/e2e/e2e-values.yaml @@ -0,0 +1,32 @@ +authProxy: + enabled: true + upstream: "http://echo-upstream:10001" + jwtIssuer: "langsmith" + jwtAudiences: + - "test-audience" + # jwksJson injected via --set at install time + streamIdleTimeout: "300s" + extAuthz: + enabled: true + serviceUrl: "http://localhost:10002" + timeout: "10s" + sendBody: false + deployment: + replicas: 1 + sidecars: + - name: ext-authz-mock + image: python:3.12-slim + command: ["python", "/scripts/ext-authz-mock.py"] + ports: + - containerPort: 10002 + volumeMounts: + - name: ext-authz-script + mountPath: /scripts + readOnly: true + volumes: + - name: ext-authz-script + configMap: + name: ext-authz-script + +ingress: + enabled: false diff --git a/charts/langsmith-auth-proxy/e2e/echo-upstream.yaml b/charts/langsmith-auth-proxy/e2e/echo-upstream.yaml new file mode 100644 index 00000000..9292d2e2 --- /dev/null +++ b/charts/langsmith-auth-proxy/e2e/echo-upstream.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: echo-upstream + labels: + app: echo-upstream +spec: + replicas: 1 + selector: + matchLabels: + app: echo-upstream + template: + metadata: + labels: + app: echo-upstream + spec: + containers: + - name: echo + image: mendhak/http-https-echo:35 + ports: + - containerPort: 8080 + env: + - name: HTTP_PORT + value: "10001" +--- +apiVersion: v1 +kind: Service +metadata: + name: echo-upstream + labels: + app: echo-upstream +spec: + selector: + app: echo-upstream + ports: + - port: 10001 + targetPort: 10001 + protocol: TCP diff --git a/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py b/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py new file mode 100644 index 00000000..90174577 --- /dev/null +++ b/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py @@ -0,0 +1,27 @@ +"""Minimal ext_authz HTTP mock for e2e testing. + +Listens on :10002, logs received headers, and returns 200 with an +Authorization header that Envoy will forward upstream. +""" + +from http.server import HTTPServer, BaseHTTPRequestHandler +import sys + + +class Handler(BaseHTTPRequestHandler): + def do_any(self): + print(f"ext_authz check: {self.command} {self.path}", flush=True) + for k, v in self.headers.items(): + print(f" {k}: {v}", flush=True) + self.send_response(200) + self.send_header("Authorization", "Bearer fake-upstream-key") + self.end_headers() + + # Handle every HTTP method the same way + do_GET = do_POST = do_PUT = do_DELETE = do_PATCH = do_HEAD = do_OPTIONS = do_any + + +if __name__ == "__main__": + server = HTTPServer(("0.0.0.0", 10002), Handler) + print("ext_authz mock listening on :10002", flush=True) + server.serve_forever() diff --git a/charts/langsmith-auth-proxy/e2e/test.sh b/charts/langsmith-auth-proxy/e2e/test.sh new file mode 100755 index 00000000..ee336955 --- /dev/null +++ b/charts/langsmith-auth-proxy/e2e/test.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +# End-to-end test for langsmith-auth-proxy chart. +# Spins up a kind cluster, deploys an echo upstream + the chart with a +# Python ext_authz sidecar, and runs curl-based tests through the proxy. +set -euo pipefail + +CLUSTER_NAME="auth-proxy-e2e" +RELEASE_NAME="auth-proxy-e2e" +NAMESPACE="default" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CHART_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +LOCAL_PORT=10000 # local port-forward target + +PASS=0 +FAIL=0 + +# ── Cleanup ────────────────────────────────────────────────────────── +cleanup() { + echo "" + echo "=== Cleanup ===" + # Kill any background port-forward + if [[ -n "${PF_PID:-}" ]] && kill -0 "$PF_PID" 2>/dev/null; then + kill "$PF_PID" 2>/dev/null || true + wait "$PF_PID" 2>/dev/null || true + fi + kind delete cluster --name "$CLUSTER_NAME" 2>/dev/null || true + echo "Done." +} +trap cleanup EXIT + +# ── Helpers ────────────────────────────────────────────────────────── +log() { echo "--- $*"; } +pass() { echo "PASS: $1"; PASS=$((PASS + 1)); } +fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); } + +assert_status() { + local desc="$1" expected="$2" actual="$3" + if [[ "$actual" == "$expected" ]]; then + pass "$desc (HTTP $actual)" + else + fail "$desc — expected $expected, got $actual" + fi +} + +# ── 1. Prerequisites ──────────────────────────────────────────────── +log "Checking prerequisites" +for cmd in kind helm kubectl step curl jq; do + if ! command -v "$cmd" &>/dev/null; then + echo "ERROR: '$cmd' is required but not found in PATH" >&2 + exit 1 + fi +done +echo "All prerequisites found." + +# ── 2. Kind cluster ───────────────────────────────────────────────── +log "Creating kind cluster '$CLUSTER_NAME'" +if kind get clusters 2>/dev/null | grep -qx "$CLUSTER_NAME"; then + echo "Cluster already exists, reusing." +else + kind create cluster --name "$CLUSTER_NAME" --wait 60s +fi +kubectl cluster-info --context "kind-$CLUSTER_NAME" >/dev/null + +# ── 3. Generate RSA keys + JWT ────────────────────────────────────── +log "Generating RSA key pair and test JWT" +TMPDIR_KEYS="$(mktemp -d)" + +# Generate RSA key pair +step crypto keypair "$TMPDIR_KEYS/pub.pem" "$TMPDIR_KEYS/priv.pem" \ + --kty RSA --size 2048 --no-password --insecure + +# Convert public key to JWK and build JWKS +PUB_JWK=$(step crypto key format --jwk < "$TMPDIR_KEYS/pub.pem") +JWKS_JSON=$(echo "$PUB_JWK" | jq -c '{keys: [. + {use: "sig", alg: "RS256"}]}') +echo "JWKS: $JWKS_JSON" + +# Mint a valid JWT +NOW=$(date +%s) +EXP=$(( NOW + 3600 )) +JWT=$(step crypto jwt sign \ + --key "$TMPDIR_KEYS/priv.pem" \ + --iss "langsmith" \ + --aud "test-audience" \ + --sub "e2e-test" \ + --nbf "$NOW" \ + --exp "$EXP") +echo "JWT: ${JWT:0:40}..." + +rm -rf "$TMPDIR_KEYS" + +# ── 4. Deploy echo upstream ───────────────────────────────────────── +log "Deploying echo upstream" +kubectl apply --context "kind-$CLUSTER_NAME" -f "$SCRIPT_DIR/echo-upstream.yaml" +kubectl rollout status deployment/echo-upstream --context "kind-$CLUSTER_NAME" --timeout=90s + +# ── 5. Deploy chart ───────────────────────────────────────────────── +log "Creating ext-authz-script ConfigMap" +kubectl create configmap ext-authz-script \ + --context "kind-$CLUSTER_NAME" \ + --from-file="ext-authz-mock.py=$SCRIPT_DIR/ext-authz-mock.py" \ + --dry-run=client -o yaml | kubectl apply --context "kind-$CLUSTER_NAME" -f - + +log "Installing chart with helm" +# Write JWKS to a temp values file — --set cannot handle nested JSON braces +TMPDIR_VALS="$(mktemp -d)" +cat > "$TMPDIR_VALS/jwks-values.yaml" </dev/null; then + echo "ERROR: port-forward died" >&2 + exit 1 +fi + +# ── 7. Tests ───────────────────────────────────────────────────────── +BASE="http://localhost:$LOCAL_PORT" + +log "Test 1: GET /healthz → 200 (bypasses auth)" +STATUS=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/healthz") +assert_status "/healthz returns 200" "200" "$STATUS" + +log "Test 2: POST /v1/chat/completions without JWT → 401" +STATUS=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/v1/chat/completions") +assert_status "No JWT returns 401" "401" "$STATUS" + +log "Test 3: POST /v1/chat/completions with valid JWT → 200 + injected header" +RESP=$(curl -s -w '\n%{http_code}' -X POST "$BASE/v1/chat/completions" \ + -H "X-LangSmith-LLM-Auth: $JWT" \ + -H "Content-Type: application/json" \ + -d '{"model":"test"}') +BODY=$(echo "$RESP" | sed '$d') +STATUS=$(echo "$RESP" | tail -1) +assert_status "Valid JWT returns 200" "200" "$STATUS" + +# The echo server returns JSON with all received headers — verify ext_authz injected the Authorization header +AUTH_HEADER=$(echo "$BODY" | jq -r '.headers.authorization // empty') +if [[ "$AUTH_HEADER" == "Bearer fake-upstream-key" ]]; then + pass "ext_authz injected Authorization header" +else + fail "Expected Authorization='Bearer fake-upstream-key', got '$AUTH_HEADER'" +fi + +log "Test 4: POST /v1/chat/completions with garbage JWT → 401" +STATUS=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/v1/chat/completions" \ + -H "X-LangSmith-LLM-Auth: garbage.jwt.token") +assert_status "Garbage JWT returns 401" "401" "$STATUS" + +# ── Summary ────────────────────────────────────────────────────────── +echo "" +echo "=== Results: $PASS passed, $FAIL failed ===" +if [[ "$FAIL" -gt 0 ]]; then + exit 1 +fi From b9b0128d44763b1fca3993ec1ff25b125911c2d9 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 15:53:54 -0500 Subject: [PATCH 08/17] improvements for e2e test --- .../langsmith-auth-proxy/e2e/e2e-values.yaml | 2 +- .../{echo-upstream.yaml => fake-gateway.yaml} | 14 ++--- charts/langsmith-auth-proxy/e2e/test.sh | 51 ++++++++++++++++--- 3 files changed, 51 insertions(+), 16 deletions(-) rename charts/langsmith-auth-proxy/e2e/{echo-upstream.yaml => fake-gateway.yaml} (75%) diff --git a/charts/langsmith-auth-proxy/e2e/e2e-values.yaml b/charts/langsmith-auth-proxy/e2e/e2e-values.yaml index ead6ea99..5aaa08a8 100644 --- a/charts/langsmith-auth-proxy/e2e/e2e-values.yaml +++ b/charts/langsmith-auth-proxy/e2e/e2e-values.yaml @@ -1,6 +1,6 @@ authProxy: enabled: true - upstream: "http://echo-upstream:10001" + upstream: "http://fake-gateway:10001" jwtIssuer: "langsmith" jwtAudiences: - "test-audience" diff --git a/charts/langsmith-auth-proxy/e2e/echo-upstream.yaml b/charts/langsmith-auth-proxy/e2e/fake-gateway.yaml similarity index 75% rename from charts/langsmith-auth-proxy/e2e/echo-upstream.yaml rename to charts/langsmith-auth-proxy/e2e/fake-gateway.yaml index 9292d2e2..7fdf39f1 100644 --- a/charts/langsmith-auth-proxy/e2e/echo-upstream.yaml +++ b/charts/langsmith-auth-proxy/e2e/fake-gateway.yaml @@ -1,18 +1,18 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: echo-upstream + name: fake-gateway labels: - app: echo-upstream + app: fake-gateway spec: replicas: 1 selector: matchLabels: - app: echo-upstream + app: fake-gateway template: metadata: labels: - app: echo-upstream + app: fake-gateway spec: containers: - name: echo @@ -26,12 +26,12 @@ spec: apiVersion: v1 kind: Service metadata: - name: echo-upstream + name: fake-gateway labels: - app: echo-upstream + app: fake-gateway spec: selector: - app: echo-upstream + app: fake-gateway ports: - port: 10001 targetPort: 10001 diff --git a/charts/langsmith-auth-proxy/e2e/test.sh b/charts/langsmith-auth-proxy/e2e/test.sh index ee336955..5f74489f 100755 --- a/charts/langsmith-auth-proxy/e2e/test.sh +++ b/charts/langsmith-auth-proxy/e2e/test.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash # End-to-end test for langsmith-auth-proxy chart. -# Spins up a kind cluster, deploys an echo upstream + the chart with a +# Spins up a kind cluster, deploys an fake gateway + the chart with a # Python ext_authz sidecar, and runs curl-based tests through the proxy. set -euo pipefail +CLAIMS_FILE="${1:-}" + CLUSTER_NAME="auth-proxy-e2e" RELEASE_NAME="auth-proxy-e2e" NAMESPACE="default" @@ -74,24 +76,50 @@ PUB_JWK=$(step crypto key format --jwk < "$TMPDIR_KEYS/pub.pem") JWKS_JSON=$(echo "$PUB_JWK" | jq -c '{keys: [. + {use: "sig", alg: "RS256"}]}') echo "JWKS: $JWKS_JSON" -# Mint a valid JWT +# Mint a valid JWT with claims matching LangSmith's token structure. +# Standard claims are set via flags; custom claims are piped as JSON via stdin. +# Pass a .json file as $1 to override the default custom claims. NOW=$(date +%s) EXP=$(( NOW + 3600 )) -JWT=$(step crypto jwt sign \ +JTI=$(uuidgen | tr '[:upper:]' '[:lower:]') + +if [[ -n "$CLAIMS_FILE" ]]; then + echo "Using custom claims from: $CLAIMS_FILE" + CUSTOM_CLAIMS=$(jq -c '.' "$CLAIMS_FILE") +else + echo "Using default fake claims" + CUSTOM_CLAIMS=$(jq -nc \ + --arg jti "$JTI" \ + --arg req "$JTI" \ + '{ + jti: $jti, + ls_user_id: "e2e-ls-user-id", + organization_id: "e2e-org-id", + workspace_id: "e2e-workspace-id", + model_provider: "fake-provider", + model_name: "fake-model", + streaming: false, + request_id: $req, + actor_type: "user" + }') +fi +echo "Custom claims: $CUSTOM_CLAIMS" + +JWT=$(echo "$CUSTOM_CLAIMS" | step crypto jwt sign \ --key "$TMPDIR_KEYS/priv.pem" \ --iss "langsmith" \ --aud "test-audience" \ - --sub "e2e-test" \ + --sub "e2e-test-user-id" \ --nbf "$NOW" \ --exp "$EXP") echo "JWT: ${JWT:0:40}..." rm -rf "$TMPDIR_KEYS" -# ── 4. Deploy echo upstream ───────────────────────────────────────── -log "Deploying echo upstream" -kubectl apply --context "kind-$CLUSTER_NAME" -f "$SCRIPT_DIR/echo-upstream.yaml" -kubectl rollout status deployment/echo-upstream --context "kind-$CLUSTER_NAME" --timeout=90s +# ── 4. Deploy fake gateway ───────────────────────────────────────── +log "Deploying fake gateway" +kubectl apply --context "kind-$CLUSTER_NAME" -f "$SCRIPT_DIR/fake-gateway.yaml" +kubectl rollout status deployment/fake-gateway --context "kind-$CLUSTER_NAME" --timeout=90s # ── 5. Deploy chart ───────────────────────────────────────────────── log "Creating ext-authz-script ConfigMap" @@ -167,6 +195,13 @@ STATUS=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/v1/chat/completio -H "X-LangSmith-LLM-Auth: garbage.jwt.token") assert_status "Garbage JWT returns 401" "401" "$STATUS" +# ── 8. Logs ────────────────────────────────────────────────────────── +log "Echo upstream logs" +kubectl logs --context "kind-$CLUSTER_NAME" -l app=fake-gateway --tail=100 + +log "ext_authz sidecar logs" +kubectl logs --context "kind-$CLUSTER_NAME" "$AUTH_POD" -c ext-authz-mock --tail=100 + # ── Summary ────────────────────────────────────────────────────────── echo "" echo "=== Results: $PASS passed, $FAIL failed ===" From 80a8b93493b33b30d98d734a12f190e7c49ea423 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 16:07:29 -0500 Subject: [PATCH 09/17] readmes, log body --- charts/langsmith-auth-proxy/README.md | 36 ++++++++++++++ charts/langsmith-auth-proxy/e2e/README.md | 48 +++++++++++++++++++ .../e2e/ext-authz-mock.py | 1 + 3 files changed, 85 insertions(+) create mode 100644 charts/langsmith-auth-proxy/README.md create mode 100644 charts/langsmith-auth-proxy/e2e/README.md diff --git a/charts/langsmith-auth-proxy/README.md b/charts/langsmith-auth-proxy/README.md new file mode 100644 index 00000000..93290a60 --- /dev/null +++ b/charts/langsmith-auth-proxy/README.md @@ -0,0 +1,36 @@ +# langsmith-auth-proxy + +Helm chart that deploys an Envoy-based proxy for validating LangSmith-signed JWTs and optionally calling an external authorization service before forwarding requests to an upstream LLM provider or gateway. + +## Request flow + +``` +Client -> Envoy(:10000) + -> Health check filter (/healthz bypasses auth) + -> JWT validation (RS256, configurable issuer + audiences) + -> [optional] ext_authz HTTP filter (e.g. inject provider API key) + -> Upstream LLM provider or gateway +``` + +## ext_authz integration + +This integration uses Envoy's [ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter). +Requ + +When `extAuthz.enabled: true`, Envoy calls the configured service at `/check` before forwarding upstream. The ext_authz service receives the `x-langsmith-llm-auth` header (containing the validated JWT) and can inject/override headers like `Authorization` that get forwarded upstream. + +### Interface + +Input structure is defined [here](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#envoy-v3-api-msg-service-auth-v3-checkrequest). + +```json +{ + "attributes": { + " + } +} +``` + +## E2E tests + +See [e2e/README.md](e2e/README.md) for local end-to-end testing with kind. diff --git a/charts/langsmith-auth-proxy/e2e/README.md b/charts/langsmith-auth-proxy/e2e/README.md new file mode 100644 index 00000000..a06c5a31 --- /dev/null +++ b/charts/langsmith-auth-proxy/e2e/README.md @@ -0,0 +1,48 @@ +# E2E Tests + +Self-contained end-to-end tests for the `langsmith-auth-proxy` chart. Spins up a kind cluster, generates fresh RSA keys + JWT, deploys a fake gateway (echo server) as upstream, runs a Python ext_authz sidecar, and validates the full request flow through Envoy. + +## Prerequisites + +`kind`, `helm`, `kubectl`, `step`, `curl`, `jq` + +## Usage + +```bash +# Run with default fake claims +./test.sh + +# Run with custom JWT claims +./test.sh path/to/claims.json +``` + +## What it tests + +1. `GET /healthz` returns 200 (health check bypasses auth) +2. Request without JWT returns 401 +3. Request with valid JWT returns 200, ext_authz injects `Authorization: Bearer fake-upstream-key` +4. Request with garbage JWT returns 401 + +## Request flow + +``` +curl -H "X-LangSmith-LLM-Auth: " -> Envoy(:10000) + -> JWT filter (validate sig, iss, aud) + -> ext_authz filter -> localhost:10002 (sidecar) + <- 200 + Authorization: Bearer fake-upstream-key + -> fake-gateway:10001 + <- 200 + JSON with all received headers +``` + +## Files + +| File | Purpose | +|------|---------| +| `test.sh` | Orchestration script | +| `e2e-values.yaml` | Helm values override | +| `fake-gateway.yaml` | Echo server Deployment+Service (upstream) | +| `ext-authz-mock.py` | Python ext_authz sidecar mock | + +## Cleanup + +The script deletes the kind cluster on exit via `trap`. To keep it for debugging, comment out the `trap cleanup EXIT` line. diff --git a/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py b/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py index 90174577..f6227fe4 100644 --- a/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py +++ b/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py @@ -11,6 +11,7 @@ class Handler(BaseHTTPRequestHandler): def do_any(self): print(f"ext_authz check: {self.command} {self.path}", flush=True) + print(f" body: {self.rfile.read(int(self.headers['content-length']))}", flush=True) for k, v in self.headers.items(): print(f" {k}: {v}", flush=True) self.send_response(200) From ef728d43a68a4ae575c25d34d6307f5075261bf5 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 16:12:27 -0500 Subject: [PATCH 10/17] fix --- charts/langsmith-auth-proxy/README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/charts/langsmith-auth-proxy/README.md b/charts/langsmith-auth-proxy/README.md index 93290a60..d5af6b47 100644 --- a/charts/langsmith-auth-proxy/README.md +++ b/charts/langsmith-auth-proxy/README.md @@ -14,22 +14,22 @@ Client -> Envoy(:10000) ## ext_authz integration -This integration uses Envoy's [ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter). -Requ +This integration uses Envoy's [HTTP ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter) (not gRPC). When `extAuthz.enabled: true`, Envoy calls the configured service at `/check` before forwarding upstream. The ext_authz service receives the `x-langsmith-llm-auth` header (containing the validated JWT) and can inject/override headers like `Authorization` that get forwarded upstream. ### Interface -Input structure is defined [here](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#envoy-v3-api-msg-service-auth-v3-checkrequest). +This chart uses the **HTTP** `ext_authz` mode — HTTP request in, HTTP response out. The gRPC proto messages (`CheckRequest`, `OkHttpResponse`, etc.) do not apply. -```json -{ - "attributes": { - " - } -} -``` +**Request** — Envoy sends an HTTP request to `{serviceUrl}/check{original_path}` with: +- Same HTTP method as the original request +- Headers matching `allowed_headers` patterns (`x-langsmith-llm-auth`, `x-*`) +- Request body only if `sendBody: true` + +**Response** — The service returns a plain HTTP response: +- `2xx` → allow: headers matching `allowed_upstream_headers` (`authorization`, `x-langsmith-llm-auth`, `x-forwarded-*`) are forwarded upstream +- Non-`2xx` → deny: status code + headers matching `allowed_client_headers` (`www-authenticate`, `x-*`) are sent back to the client ## E2E tests From ec240217b19d2ed9a63defdd817e3ef5a5b246e1 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 16:13:31 -0500 Subject: [PATCH 11/17] simplify ci values --- .../ci/auth-proxy-values.yaml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml b/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml index e79bc470..0b35ba97 100644 --- a/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml +++ b/charts/langsmith-auth-proxy/ci/auth-proxy-values.yaml @@ -14,18 +14,4 @@ authProxy: timeout: "10s" ingress: - enabled: true - ingressClassName: "nginx" - annotations: - nginx.ingress.kubernetes.io/proxy-buffering: "off" - nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" - nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" - hosts: - - host: llm-proxy.example.com - paths: - - path: / - pathType: Prefix - tls: - - secretName: llm-proxy-tls - hosts: - - llm-proxy.example.com + enabled: false From 3711fbc7066cd3af606208e195a02006972842da Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 16:42:55 -0500 Subject: [PATCH 12/17] helm-docs --- charts/langsmith-auth-proxy/README.md | 113 ++++++++++++++++++- charts/langsmith-auth-proxy/README.md.gotmpl | 52 +++++++++ 2 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 charts/langsmith-auth-proxy/README.md.gotmpl diff --git a/charts/langsmith-auth-proxy/README.md b/charts/langsmith-auth-proxy/README.md index d5af6b47..722e0f96 100644 --- a/charts/langsmith-auth-proxy/README.md +++ b/charts/langsmith-auth-proxy/README.md @@ -1,6 +1,8 @@ # langsmith-auth-proxy -Helm chart that deploys an Envoy-based proxy for validating LangSmith-signed JWTs and optionally calling an external authorization service before forwarding requests to an upstream LLM provider or gateway. +![Version: 0.0.1](https://img.shields.io/badge/Version-0.0.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.37.0](https://img.shields.io/badge/AppVersion-1.37.0-informational?style=flat-square) + +Helm chart to deploy the langsmith auth-proxy application. ## Request flow @@ -31,6 +33,115 @@ This chart uses the **HTTP** `ext_authz` mode — HTTP request in, HTTP response - `2xx` → allow: headers matching `allowed_upstream_headers` (`authorization`, `x-langsmith-llm-auth`, `x-forwarded-*`) are forwarded upstream - Non-`2xx` → deny: status code + headers matching `allowed_client_headers` (`www-authenticate`, `x-*`) are sent back to the client +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| authProxy.autoscaling.hpa.additionalMetrics | list | `[]` | | +| authProxy.autoscaling.hpa.enabled | bool | `false` | | +| authProxy.autoscaling.hpa.maxReplicas | int | `5` | | +| authProxy.autoscaling.hpa.minReplicas | int | `1` | | +| authProxy.autoscaling.hpa.targetCPUUtilizationPercentage | int | `50` | | +| authProxy.autoscaling.hpa.targetMemoryUtilizationPercentage | int | `80` | | +| authProxy.containerPort | int | `10000` | | +| authProxy.deployment.affinity | object | `{}` | | +| authProxy.deployment.annotations | object | `{}` | | +| authProxy.deployment.command[0] | string | `"envoy"` | | +| authProxy.deployment.command[1] | string | `"-c"` | | +| authProxy.deployment.command[2] | string | `"/etc/envoy/envoy.yaml"` | | +| authProxy.deployment.extraContainerConfig | object | `{}` | | +| authProxy.deployment.extraEnv | list | `[]` | | +| authProxy.deployment.initContainers | list | `[]` | | +| authProxy.deployment.labels | object | `{}` | | +| authProxy.deployment.livenessProbe.failureThreshold | int | `6` | | +| authProxy.deployment.livenessProbe.httpGet.path | string | `"/healthz"` | | +| authProxy.deployment.livenessProbe.httpGet.port | int | `10000` | | +| authProxy.deployment.livenessProbe.periodSeconds | int | `10` | | +| authProxy.deployment.livenessProbe.timeoutSeconds | int | `1` | | +| authProxy.deployment.nodeSelector | object | `{}` | | +| authProxy.deployment.podSecurityContext | object | `{}` | | +| authProxy.deployment.readinessProbe.failureThreshold | int | `6` | | +| authProxy.deployment.readinessProbe.httpGet.path | string | `"/healthz"` | | +| authProxy.deployment.readinessProbe.httpGet.port | int | `10000` | | +| authProxy.deployment.readinessProbe.periodSeconds | int | `10` | | +| authProxy.deployment.readinessProbe.timeoutSeconds | int | `1` | | +| authProxy.deployment.replicas | int | `1` | | +| authProxy.deployment.resources.limits.cpu | string | `"500m"` | | +| authProxy.deployment.resources.limits.memory | string | `"256Mi"` | | +| authProxy.deployment.resources.requests.cpu | string | `"100m"` | | +| authProxy.deployment.resources.requests.memory | string | `"128Mi"` | | +| authProxy.deployment.securityContext | object | `{}` | | +| authProxy.deployment.sidecars | list | `[]` | | +| authProxy.deployment.startupProbe.failureThreshold | int | `6` | | +| authProxy.deployment.startupProbe.httpGet.path | string | `"/healthz"` | | +| authProxy.deployment.startupProbe.httpGet.port | int | `10000` | | +| authProxy.deployment.startupProbe.periodSeconds | int | `10` | | +| authProxy.deployment.startupProbe.timeoutSeconds | int | `1` | | +| authProxy.deployment.terminationGracePeriodSeconds | int | `30` | | +| authProxy.deployment.tolerations | list | `[]` | | +| authProxy.deployment.topologySpreadConstraints | list | `[]` | | +| authProxy.deployment.volumeMounts | list | `[]` | | +| authProxy.deployment.volumes | list | `[]` | | +| authProxy.enabled | bool | `true` | | +| authProxy.extAuthz.enabled | bool | `false` | | +| authProxy.extAuthz.maxRequestBytes | int | `8192` | Maximum request body bytes to buffer for ext_authz | +| authProxy.extAuthz.sendBody | bool | `false` | Whether to send the request body to ext_authz | +| authProxy.extAuthz.serviceUrl | string | `""` | HTTP service URL for ext_authz (e.g. http://my-auth-service:8080) | +| authProxy.extAuthz.timeout | string | `"10s"` | Timeout for ext_authz requests | +| authProxy.jwksJson | string | `""` | JWKS JSON string containing the public keys for JWT validation. Generate with the LangSmith JWKS tooling and paste the full JSON here. | +| authProxy.jwtAudiences | list | `[]` | JWT audience claims to validate. Must match audiences in the signed JWT. | +| authProxy.jwtIssuer | string | `"langsmith"` | JWT issuer claim to validate | +| authProxy.name | string | `"auth-proxy"` | | +| authProxy.pdb.annotations | object | `{}` | | +| authProxy.pdb.enabled | bool | `false` | | +| authProxy.pdb.labels | object | `{}` | | +| authProxy.pdb.minAvailable | int | `1` | | +| authProxy.rollout | object | `{"enabled":false,"strategy":{"canary":{"steps":[{"setWeight":100}]}}}` | ArgoCD Rollouts configuration. If enabled, will create a Rollout resource instead of a Deployment. See https://argo-rollouts.readthedocs.io/ | +| authProxy.rollout.strategy | object | `{"canary":{"steps":[{"setWeight":100}]}}` | Rollout strategy configuration. See https://argo-rollouts.readthedocs.io/en/stable/features/specification/ | +| authProxy.service.annotations | object | `{}` | | +| authProxy.service.labels | object | `{}` | | +| authProxy.service.loadBalancerIP | string | `""` | | +| authProxy.service.loadBalancerSourceRanges | list | `[]` | | +| authProxy.service.port | int | `10000` | | +| authProxy.service.type | string | `"ClusterIP"` | | +| authProxy.serviceAccount.annotations | object | `{}` | | +| authProxy.serviceAccount.automountServiceAccountToken | bool | `true` | | +| authProxy.serviceAccount.create | bool | `true` | | +| authProxy.serviceAccount.labels | object | `{}` | | +| authProxy.serviceAccount.name | string | `""` | | +| authProxy.streamIdleTimeout | string | `"300s"` | Idle timeout for streaming responses (e.g. SSE from LLM providers) | +| authProxy.upstream | string | `""` | Upstream LLM provider URL (e.g. https://api.openai.com) | +| commonAnnotations | object | `{}` | Annotations that will be applied to all resources created by the chart | +| commonLabels | object | `{}` | Labels that will be applied to all resources created by the chart | +| commonPodAnnotations | object | `{}` | Annotations that will be applied to all pods created by the chart | +| commonPodSecurityContext | object | `{}` | Common pod security context applied to all pods. Component-specific podSecurityContext values will be merged on top of this (component values take precedence). | +| fullnameOverride | string | `""` | String to fully override `"langsmith.fullname"` | +| gateway | object | `{"annotations":{},"enabled":false,"hostnames":[],"labels":{},"name":"","namespace":"","sectionName":""}` | Gateway API HTTPRoute configuration | +| gateway.hostnames | list | `[]` | Hostnames to match on | +| gateway.name | string | `""` | Name of the Gateway resource to attach to | +| gateway.namespace | string | `""` | Namespace of the Gateway resource (if different from chart namespace) | +| gateway.sectionName | string | `""` | SectionName of the Gateway listener to attach to | +| images.authProxyImage.pullPolicy | string | `"IfNotPresent"` | | +| images.authProxyImage.repository | string | `"docker.io/envoyproxy/envoy"` | | +| images.authProxyImage.tag | string | `"v1.37-latest"` | | +| images.imagePullSecrets | list | `[]` | | +| images.registry | string | `""` | If supplied, all children .repository values will be prepended with this registry name + `/` | +| ingress | object | `{"annotations":{},"enabled":false,"hosts":[],"ingressClassName":"","labels":{},"tls":[]}` | Ingress configuration | +| ingress.annotations | object | `{}` | Annotations for streaming support. Defaults shown are for nginx ingress controller. | +| nameOverride | string | `""` | Provide a name in place of `langsmith-auth-proxy` | +| namespace | string | `""` | Namespace to install the chart into. If not set, will use the namespace of the current context. | + ## E2E tests See [e2e/README.md](e2e/README.md) for local end-to-end testing with kind. + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Brian | | | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) +## Docs Generated by [helm-docs](https://github.com/norwoodj/helm-docs) +`helm-docs -t ./charts/langsmith-auth-proxy/README.md.gotmpl` diff --git a/charts/langsmith-auth-proxy/README.md.gotmpl b/charts/langsmith-auth-proxy/README.md.gotmpl new file mode 100644 index 00000000..d7fd3aa7 --- /dev/null +++ b/charts/langsmith-auth-proxy/README.md.gotmpl @@ -0,0 +1,52 @@ +{{ template "chart.header" . }} + +{{ template "chart.versionBadge" . }}{{ template "chart.typeBadge" . }}{{ template "chart.appVersionBadge" . }} + +{{ template "chart.description" . }} + +## Request flow + +``` +Client -> Envoy(:10000) + -> Health check filter (/healthz bypasses auth) + -> JWT validation (RS256, configurable issuer + audiences) + -> [optional] ext_authz HTTP filter (e.g. inject provider API key) + -> Upstream LLM provider or gateway +``` + +## ext_authz integration + +This integration uses Envoy's [HTTP ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter) (not gRPC). + +When `extAuthz.enabled: true`, Envoy calls the configured service at `/check` before forwarding upstream. The ext_authz service receives the `x-langsmith-llm-auth` header (containing the validated JWT) and can inject/override headers like `Authorization` that get forwarded upstream. + +### Interface + +This chart uses the **HTTP** `ext_authz` mode — HTTP request in, HTTP response out. The gRPC proto messages (`CheckRequest`, `OkHttpResponse`, etc.) do not apply. + +**Request** — Envoy sends an HTTP request to `{serviceUrl}/check{original_path}` with: +- Same HTTP method as the original request +- Headers matching `allowed_headers` patterns (`x-langsmith-llm-auth`, `x-*`) +- Request body only if `sendBody: true` + +**Response** — The service returns a plain HTTP response: +- `2xx` → allow: headers matching `allowed_upstream_headers` (`authorization`, `x-langsmith-llm-auth`, `x-forwarded-*`) are forwarded upstream +- Non-`2xx` → deny: status code + headers matching `allowed_client_headers` (`www-authenticate`, `x-*`) are sent back to the client + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +{{- range .Values }} +| {{ .Key }} | {{ .Type }} | {{ if .Default }}{{ .Default }}{{ else }}{{ .AutoDefault }}{{ end }} | {{ if .Description }}{{ .Description }}{{ else }}{{ .AutoDescription }}{{ end }} | +{{- end }} + +## E2E tests + +See [e2e/README.md](e2e/README.md) for local end-to-end testing with kind. + +{{ template "chart.maintainersSection" . }} + +{{ template "helm-docs.versionFooter" . }} +## Docs Generated by [helm-docs](https://github.com/norwoodj/helm-docs) +`helm-docs -t ./charts/langsmith-auth-proxy/README.md.gotmpl` From cf063e78f3be8e7fb233bcb18b00ea32caa2aaa9 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 16:48:08 -0500 Subject: [PATCH 13/17] revert langsmith chart changes --- charts/langsmith/README.md | 2 +- charts/langsmith/_helpers.tpl | 757 ++++++++++++++++++++++++++++++++++ charts/langsmith/values.yaml | 4 - 3 files changed, 758 insertions(+), 5 deletions(-) create mode 100644 charts/langsmith/_helpers.tpl diff --git a/charts/langsmith/README.md b/charts/langsmith/README.md index bdce67a8..6275b388 100644 --- a/charts/langsmith/README.md +++ b/charts/langsmith/README.md @@ -1478,6 +1478,6 @@ For information on how to use this chart, up-to-date release notes, and other gu | Ankush | | | ---------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) +Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) ## Docs Generated by [helm-docs](https://github.com/norwoodj/helm-docs) `helm-docs -t ./charts/langsmith/README.md.gotmpl` diff --git a/charts/langsmith/_helpers.tpl b/charts/langsmith/_helpers.tpl new file mode 100644 index 00000000..c6fbdc96 --- /dev/null +++ b/charts/langsmith/_helpers.tpl @@ -0,0 +1,757 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "langsmith.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "langsmith.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "langsmith.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "langsmith.labels" -}} +{{- if .Values.commonLabels }} +{{ toYaml .Values.commonLabels }} +{{- end }} +helm.sh/chart: {{ include "langsmith.chart" . }} +{{ include "langsmith.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Common annotations +*/}} +{{- define "langsmith.annotations" -}} +{{- if .Values.commonAnnotations }} +{{ toYaml .Values.commonAnnotations }} +{{- end }} +helm.sh/chart: {{ include "langsmith.chart" . }} +{{ include "langsmith.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Common pod annotations +*/}} +{{- define "langsmith.commonPodAnnotations" -}} +{{- if .Values.commonPodAnnotations }} +{{ toYaml .Values.commonPodAnnotations }} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "langsmith.selectorLabels" -}} +app.kubernetes.io/name: {{ include "langsmith.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Name of the secret containing the secrets for this chart. This can be overridden by a secrets file created by +the user or some other secret provisioning mechanism +*/}} +{{- define "langsmith.secretsName" -}} +{{- if .Values.config.existingSecretName }} +{{- .Values.config.existingSecretName }} +{{- else }} +{{- include "langsmith.fullname" . }}-secrets +{{- end }} +{{- end }} + +{{/* +Name of the secret containing the secrets for postgres. This can be overridden by a secrets file created by +the user or some other secret provisioning mechanism +*/}} +{{- define "langsmith.postgresSecretsName" -}} +{{- if .Values.postgres.external.existingSecretName }} +{{- .Values.postgres.external.existingSecretName }} +{{- else }} +{{- include "langsmith.fullname" . }}-postgres +{{- end }} +{{- end }} + +{{/* +Name of the secret containing the secrets for redis. This can be overridden by a secrets file created by +the user or some other secret provisioning mechanism +*/}} +{{- define "langsmith.redisSecretsName" -}} +{{- if .Values.redis.external.existingSecretName }} +{{- .Values.redis.external.existingSecretName }} +{{- else }} +{{- include "langsmith.fullname" . }}-redis +{{- end }} +{{- end }} + +{{/* +Name of the secret containing the secrets for clickhouse. This can be overridden by a secrets file created by +the user or some other secret provisioning mechanism +*/}} +{{- define "langsmith.clickhouseSecretsName" -}} +{{- if .Values.clickhouse.external.existingSecretName }} +{{- .Values.clickhouse.external.existingSecretName }} +{{- else }} +{{- include "langsmith.fullname" . }}-clickhouse +{{- end }} +{{- end }} + +{{/* Include these env vars if they aren't defined in .Values.commonEnv */}} +{{- define "langsmith.conditionalEnvVars" -}} +- name: X_SERVICE_AUTH_JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: api_key_salt + optional: {{ .Values.config.disableSecretCreation }} +{{- end }} +{{- define "langsmith.conditionalEnvVarsResolved" -}} + {{- $commonEnvKeys := list -}} + {{- range $i, $commonEnvVar := .Values.commonEnv -}} + {{- $commonEnvKeys = append $commonEnvKeys $commonEnvVar.name -}} + {{- end -}} + + {{- $resolvedEnvVars := list -}} + {{- range $i, $envVar := include "langsmith.conditionalEnvVars" . | fromYamlArray }} + {{- if not (has $envVar.name $commonEnvKeys) }} + {{- $resolvedEnvVars = append $resolvedEnvVars $envVar -}} + {{- end }} + {{- end }} + + {{- if gt (len $resolvedEnvVars) 0 -}} + {{ $resolvedEnvVars | toYaml }} + {{- end -}} +{{- end }} + + +{{/* +Template for merging commonPodSecurityContext with component-specific podSecurityContext. +Component-specific values take precedence over common values. +Usage: {{ include "langsmith.podSecurityContext" (dict "Values" .Values "componentSecurityContext" .Values.backend.deployment.podSecurityContext) }} +*/}} +{{- define "langsmith.podSecurityContext" -}} +{{- $merged := merge .componentSecurityContext .Values.commonPodSecurityContext -}} +{{- toYaml $merged -}} +{{- end -}} + +{{/* +Template containing common environment variables that are used by several services. +*/}} +{{- define "langsmith.commonEnv" -}} +- name: POSTGRES_DATABASE_URI + valueFrom: + secretKeyRef: + name: {{ include "langsmith.postgresSecretsName" . }} + key: {{ .Values.postgres.external.connectionUrlSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +{{- if .Values.postgres.external.enabled }} +- name: POSTGRES_SCHEMA + value: {{ .Values.postgres.external.schema | quote }} +- name: POSTGRES_TLS + value: {{ .Values.postgres.external.customTls | quote }} +{{- if .Values.postgres.external.clientCert.secretName }} +- name: POSTGRES_TLS_CLIENT_CERT_PATH + value: /etc/postgres/certs/client.crt +- name: POSTGRES_TLS_CLIENT_KEY_PATH + value: /etc/postgres/certs/client.key +{{- end }} +{{- end }} +{{- if .Values.config.hostname }} +- name: LANGSMITH_URL + value: {{ include "langsmith.hostnameWithoutProtocol" . }}{{- with .Values.config.basePath }}/{{ . }}{{- end }} +- name: HOST_BACKEND_ENDPOINT_PUBLIC + value: {{ .Values.config.hostname }}/api-host +{{- end }} +- name: REDIS_CLUSTER_ENABLED + value: {{ .Values.redis.external.cluster.enabled | quote }} +{{- if .Values.redis.external.cluster.enabled }} +- name: REDIS_CLUSTER_DATABASE_URIS + valueFrom: + secretKeyRef: + name: {{ include "langsmith.redisSecretsName" . }} + key: {{ .Values.redis.external.cluster.nodeUrisSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +- name: REDIS_CLUSTER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "langsmith.redisSecretsName" . }} + key: {{ .Values.redis.external.cluster.passwordSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +- name: REDIS_CLUSTER_USE_SSL_CONNECTION + value: {{ .Values.redis.external.cluster.tlsEnabled | quote }} +{{- else }} +- name: REDIS_DATABASE_URI + valueFrom: + secretKeyRef: + name: {{ include "langsmith.redisSecretsName" . }} + key: {{ .Values.redis.external.connectionUrlSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +{{- end }} +{{- if .Values.redis.external.clientCert.secretName }} +- name: REDIS_TLS_CLIENT_CERT_PATH + value: /etc/redis/certs/client.crt +- name: REDIS_TLS_CLIENT_KEY_PATH + value: /etc/redis/certs/client.key +{{- end }} +{{- if .Values.postgres.external.iamAuthProvider }} +- name: POSTGRES_IAM_AUTH_PROVIDER + value: {{ .Values.postgres.external.iamAuthProvider | quote }} +{{- end }} +{{- if .Values.redis.external.iamAuthProvider }} +- name: REDIS_IAM_AUTH_PROVIDER + value: {{ .Values.redis.external.iamAuthProvider | quote }} +{{- end }} +- name: CLICKHOUSE_HYBRID + value: {{ .Values.clickhouse.external.hybrid | quote }} +- name: CLICKHOUSE_DB + valueFrom: + secretKeyRef: + name: {{ include "langsmith.clickhouseSecretsName" . }} + key: {{ .Values.clickhouse.external.databaseSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +- name: CLICKHOUSE_HOST + valueFrom: + secretKeyRef: + name: {{ include "langsmith.clickhouseSecretsName" . }} + key: {{ .Values.clickhouse.external.hostSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +- name: CLICKHOUSE_PORT + valueFrom: + secretKeyRef: + name: {{ include "langsmith.clickhouseSecretsName" . }} + key: {{ .Values.clickhouse.external.portSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +- name: CLICKHOUSE_NATIVE_PORT + valueFrom: + secretKeyRef: + name: {{ include "langsmith.clickhouseSecretsName" . }} + key: {{ .Values.clickhouse.external.nativePortSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +- name: CLICKHOUSE_USER + valueFrom: + secretKeyRef: + name: {{ include "langsmith.clickhouseSecretsName" . }} + key: {{ .Values.clickhouse.external.userSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +- name: CLICKHOUSE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "langsmith.clickhouseSecretsName" . }} + key: {{ .Values.clickhouse.external.passwordSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +- name: CLICKHOUSE_TLS + valueFrom: + secretKeyRef: + name: {{ include "langsmith.clickhouseSecretsName" . }} + key: {{ .Values.clickhouse.external.tlsSecretKey }} + optional: {{ .Values.config.disableSecretCreation }} +{{- if .Values.clickhouse.external.clientCert.secretName }} +- name: CLICKHOUSE_TLS_CLIENT_CERT_PATH + value: /etc/clickhouse/certs/client.crt +- name: CLICKHOUSE_TLS_CLIENT_KEY_PATH + value: /etc/clickhouse/certs/client.key +{{- end }} +- name: CLICKHOUSE_CLUSTER + value: {{ .Values.clickhouse.external.cluster | quote }} +- name: LOG_LEVEL + value: {{ .Values.config.logLevel | quote }} +{{- if .Values.config.oauth.enabled }} +- name: OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: oauth_client_id + optional: {{ .Values.config.disableSecretCreation }} +- name: OAUTH_ISSUER_URL + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: oauth_issuer_url + optional: {{ .Values.config.disableSecretCreation }} +{{- if eq .Values.config.authType "mixed" }} +- name: OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: oauth_client_secret + optional: {{ .Values.config.disableSecretCreation }} +{{- end }} +{{- end }} +- name: LANGSMITH_LICENSE_KEY + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: langsmith_license_key + optional: {{ .Values.config.disableSecretCreation }} +- name: API_KEY_SALT + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: api_key_salt + optional: {{ .Values.config.disableSecretCreation }} +- name: BASIC_AUTH_ENABLED + value: {{ .Values.config.basicAuth.enabled | quote }} +{{- if .Values.config.basicAuth.enabled }} +- name: BASIC_AUTH_JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: jwt_secret + optional: {{ .Values.config.disableSecretCreation }} +{{- end }} +- name: FF_ORG_CREATION_DISABLED + value: {{ .Values.config.userOrgCreationDisabled | quote }} +- name: FF_PERSONAL_ORGS_DISABLED + value: {{ .Values.config.personalOrgsDisabled | quote }} +{{- if .Values.config.ttl.enabled }} +- name: FF_TRACE_TIERS_ENABLED + value: {{ .Values.config.ttl.enabled | quote }} +- name: FF_UPGRADE_TRACE_TIER_ENABLED + value: "true" +- name: TRACE_TIER_TTL_DURATION_SEC_MAP + value: "{ \"longlived\": {{ .Values.config.ttl.ttl_period_seconds.longlived }}, \"shortlived\": {{ .Values.config.ttl.ttl_period_seconds.shortlived }} }" +{{- end }} +{{- if .Values.config.workspaceScopeOrgInvitesEnabled }} +- name: FF_WORKSPACE_SCOPE_ORG_INVITES_ENABLED + value: {{ .Values.config.workspaceScopeOrgInvitesEnabled | quote }} +{{- end }} +{{- if .Values.config.blobStorage.enabled }} +- name: FF_S3_STORAGE_ENABLED + value: {{ .Values.config.blobStorage.enabled | quote }} +- name: FF_BLOB_STORAGE_ENABLED + value: {{ .Values.config.blobStorage.enabled | quote }} +- name: BLOB_STORAGE_ENGINE + value: {{ .Values.config.blobStorage.engine | quote }} +- name: MIN_BLOB_STORAGE_SIZE_KB + value: {{ ternary 0 .Values.config.blobStorage.minBlobStorageSizeKb .Values.clickhouse.external.hybrid | quote }} +{{- if (or (eq .Values.config.blobStorage.engine "S3") (eq .Values.config.blobStorage.engine "s3")) }} +- name: S3_BUCKET_NAME + value: {{ .Values.config.blobStorage.bucketName | quote }} +- name: S3_RUN_MANIFEST_BUCKET_NAME + value: {{ .Values.config.blobStorage.bucketName | quote }} +- name: S3_API_URL + value: {{ .Values.config.blobStorage.apiURL | quote }} +- name: S3_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: blob_storage_access_key + optional: true +- name: S3_ACCESS_KEY_SECRET + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: blob_storage_access_key_secret + optional: true +- name: S3_USE_PATH_STYLE + value: {{ .Values.config.blobStorage.s3UsePathStyle | quote }} +{{- if .Values.config.blobStorage.kmsEncryptionEnabled }} +- name: S3_KMS_ENCRYPTION_ENABLED + value: {{ .Values.config.blobStorage.kmsEncryptionEnabled | quote }} +{{- if .Values.config.blobStorage.kmsKeyArn }} +- name: S3_KMS_KEY_ARN + value: {{ .Values.config.blobStorage.kmsKeyArn | quote }} +{{- end }} +{{- end }} +{{- end }} +{{- if (or (eq .Values.config.blobStorage.engine "Azure") (eq .Values.config.blobStorage.engine "azure")) }} +- name: AZURE_STORAGE_ACCOUNT_NAME + value: {{ .Values.config.blobStorage.azureStorageAccountName | quote }} +- name: AZURE_STORAGE_CONTAINER_NAME + value: {{ .Values.config.blobStorage.azureStorageContainerName | quote }} +- name: AZURE_STORAGE_SERVICE_URL_OVERRIDE + value: {{ .Values.config.blobStorage.azureStorageServiceUrlOverride | quote }} +- name: AZURE_STORAGE_ACCOUNT_KEY + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: azure_storage_account_key + optional: true +- name: AZURE_STORAGE_CONNECTION_STRING + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: azure_storage_connection_string + optional: true +{{- end }} +{{- end }} +- name: FF_CH_SEARCH_ENABLED + value: {{ ternary "false" .Values.config.blobStorage.chSearchEnabled .Values.clickhouse.external.hybrid | quote }} +{{ include "langsmith.conditionalEnvVarsResolved" . }} +- name: REDIS_RUNS_EXPIRY_SECONDS + value: {{ .Values.config.settings.redisRunsExpirySeconds | quote }} +{{- if .Values.config.deployment.enabled }} +- name: LANGGRAPH_CLOUD_LICENSE_KEY + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: langsmith_license_key + optional: {{ .Values.config.disableSecretCreation }} +- name: HOST_QUEUE + value: "host" +- name: HOST_WORKER_RECONCILIATION_CRON_ENABLED + value: "true" +{{- if .Values.config.basePath }} +- name: HOST_LANGCHAIN_API_ENDPOINT + value: "http://{{ include "langsmith.fullname" . }}-{{ .Values.frontend.name }}.{{ .Values.namespace | default .Release.Namespace }}:{{ .Values.frontend.service.httpPort }}/{{ .Values.config.basePath}}/api/v1" +{{- else }} +- name: HOST_LANGCHAIN_API_ENDPOINT + value: "http://{{ include "langsmith.fullname" . }}-{{ .Values.frontend.name }}.{{ .Values.namespace | default .Release.Namespace }}:{{ .Values.frontend.service.httpPort }}/api/v1" +{{- end }} +- name: HOSTED_K8S_ROOT_DOMAIN + value: {{ include "langsmith.hostnameWithoutProtocol" . | quote }} +- name: HOSTED_K8S_SHARED_INGRESS + value: "true" +{{- end }} +- name: ENABLE_LGP_DEPLOYMENT_HEALTH_CHECK + value: {{ .Values.config.deployment.ingressHealthCheckEnabled | quote }} +{{- if and .Values.config.customCa.secretName .Values.config.customCa.secretKey }} +- name: SSL_CERT_FILE + value: /etc/ssl/certs/custom-ca-certificates.crt +{{- end }} +{{- if .Values.ingestQueue.enabled }} +- name: GO_QUEUE_ENABLED_ALL + value: "true" +- name: GO_FEEDBACK_QUEUE_ENABLED_ALL + value: "true" +- name: FF_PERSIST_BATCHED_RUNS_SUCCESS_LOGGING + value: "true" +{{- end }} +{{- if .Values.config.agentBuilder.enabled }} +- name: AGENT_BUILDER_ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: agent_builder_encryption_key +{{- end }} +{{- if .Values.config.insights.enabled }} +- name: CLIO_ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: {{ include "langsmith.secretsName" . }} + key: insights_encryption_key + optional: false +{{- end }} +{{- end }} + + +{{- define "aceBackend.serviceAccountName" -}} +{{- if .Values.aceBackend.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.aceBackend.name) .Values.aceBackend.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.aceBackend.serviceAccount.name }} +{{- end -}} +{{- end -}} + + +{{- define "backend.serviceAccountName" -}} +{{- if .Values.backend.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.backend.name) .Values.backend.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.backend.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "clickhouse.serviceAccountName" -}} +{{- if .Values.clickhouse.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.clickhouse.name) .Values.clickhouse.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.clickhouse.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "frontend.serviceAccountName" -}} +{{- if .Values.frontend.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.frontend.name) .Values.frontend.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.frontend.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "hostBackend.serviceAccountName" -}} +{{- if .Values.hostBackend.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.hostBackend.name) .Values.hostBackend.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.hostBackend.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "listener.serviceAccountName" -}} +{{- if .Values.listener.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.listener.name) .Values.listener.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.listener.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "operator.serviceAccountName" -}} +{{- if .Values.operator.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.operator.name) .Values.operator.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.operator.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "platformBackend.serviceAccountName" -}} +{{- if .Values.platformBackend.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.platformBackend.name) .Values.platformBackend.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.platformBackend.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "playground.serviceAccountName" -}} +{{- if .Values.playground.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.playground.name) .Values.playground.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.playground.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "postgres.serviceAccountName" -}} +{{- if .Values.postgres.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.postgres.name) .Values.postgres.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.postgres.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "queue.serviceAccountName" -}} +{{- if .Values.queue.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.queue.name) .Values.queue.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.queue.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "ingestQueue.serviceAccountName" -}} +{{- if .Values.ingestQueue.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.ingestQueue.name) .Values.ingestQueue.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.ingestQueue.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "redis.serviceAccountName" -}} +{{- if .Values.redis.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.redis.name) .Values.redis.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.redis.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "agentBuilderToolServer.serviceAccountName" -}} +{{- if .Values.agentBuilderToolServer.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.agentBuilderToolServer.name) .Values.agentBuilderToolServer.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.agentBuilderToolServer.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "agentBuilderTriggerServer.serviceAccountName" -}} +{{- if .Values.agentBuilderTriggerServer.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) .Values.agentBuilderTriggerServer.name) .Values.agentBuilderTriggerServer.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.agentBuilderTriggerServer.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "agentBootstrap.serviceAccountName" -}} +{{- if .Values.backend.agentBootstrap.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "langsmith.fullname" .) "agent-bootstrap") .Values.backend.agentBootstrap.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- else -}} + {{ default "default" .Values.backend.agentBootstrap.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{- define "agentBootstrap.createAgentProducts" -}} +{{- $createProducts := list }} +{{- if .Values.config.agentBuilder.enabled }} +{{- $createProducts = append $createProducts "agent_builder" }} +{{- end }} +{{- if .Values.config.insights.enabled }} +{{- $createProducts = append $createProducts "insights" }} +{{- end }} +{{ toYaml $createProducts }} +{{- end -}} + +{{- define "agentBootstrap.destroyAgentProducts" -}} +{{- $destroyProducts := list }} +{{- if not .Values.config.agentBuilder.enabled }} +{{- $destroyProducts = append $destroyProducts "agent_builder" }} +{{- end }} +{{- if not .Values.config.insights.enabled }} +{{- $destroyProducts = append $destroyProducts "insights" }} +{{- end }} +{{ toYaml $destroyProducts }} +{{- end -}} + +{{/* Fail on duplicate keys in the inputted list of environment variables */}} +{{- define "langsmith.detectDuplicates" -}} +{{- $inputList := . -}} +{{- $keyCounts := dict -}} +{{- $duplicates := list -}} + +{{- range $i, $val := $inputList }} + {{- $key := $val.name -}} + {{- if hasKey $keyCounts $key }} + {{- $_ := set $keyCounts $key (add (get $keyCounts $key) 1) -}} + {{- else }} + {{- $_ := set $keyCounts $key 1 -}} + {{- end }} + {{- if gt (get $keyCounts $key) 1 }} + {{- $duplicates = append $duplicates $key -}} + {{- end }} +{{- end }} + +{{- if gt (len $duplicates) 0 }} + {{ fail (printf "Duplicate keys detected: %v" $duplicates) }} +{{- end }} +{{- end -}} + +{{- define "langsmith.checksumAnnotations"}} +checksum/config: {{ include (print $.Template.BasePath "/config-map.yaml") . | sha256sum }} +checksum/frontend-config: {{ include (print $.Template.BasePath "/frontend/config-map.yaml") . | sha256sum }} +{{- if not .Values.config.existingSecretName }} +checksum/secrets: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }} +{{- end }} +{{- if not .Values.redis.external.existingSecretName }} +checksum/redis: {{ include (print $.Template.BasePath "/redis/secrets.yaml") . | sha256sum }} +{{- end }} +{{- if not .Values.postgres.external.existingSecretName }} +checksum/postgres: {{ include (print $.Template.BasePath "/postgres/secrets.yaml") . | sha256sum }} +{{- end }} +{{- if not .Values.clickhouse.external.existingSecretName }} +checksum/clickhouse: {{ include (print $.Template.BasePath "/clickhouse/secrets.yaml") . | sha256sum }} +{{- end }} +{{- end }} + +{{/* +Creates the image reference used for Langsmith deployments. If registry is specified, concatenate it, along with a '/'. +*/}} +{{- define "langsmith.image" -}} +{{- $imageConfig := index .Values.images .component -}} +{{- if .Values.images.registry -}} +{{ .Values.images.registry }}/{{ $imageConfig.repository }}:{{ $imageConfig.tag | default .Chart.AppVersion }} +{{- else -}} +{{ $imageConfig.repository }}:{{ $imageConfig.tag | default .Chart.AppVersion }} +{{- end -}} + +{{- end -}} + +{{- define "langsmith.tlsVolumeMounts" -}} +{{- $mounts := list -}} +{{- if and .Values.config.customCa.secretName .Values.config.customCa.secretKey -}} +{{- $mounts = append $mounts (dict "name" "langsmith-custom-ca" "mountPath" "/etc/ssl/certs/custom-ca-certificates.crt" "subPath" "ca-certificates.crt" "readOnly" true) -}} +{{- end -}} +{{- if .Values.redis.external.clientCert.secretName -}} +{{- $mounts = append $mounts (dict "name" "redis-client-cert" "mountPath" "/etc/redis/certs" "readOnly" true) -}} +{{- end -}} +{{- if .Values.postgres.external.clientCert.secretName -}} +{{- $mounts = append $mounts (dict "name" "postgres-client-cert" "mountPath" "/etc/postgres/certs" "readOnly" true) -}} +{{- end -}} +{{- if .Values.clickhouse.external.clientCert.secretName -}} +{{- $mounts = append $mounts (dict "name" "clickhouse-client-cert" "mountPath" "/etc/clickhouse/certs" "readOnly" true) -}} +{{- end -}} +{{ $mounts | toYaml }} +{{- end -}} + +{{- define "langsmith.tlsVolumes" -}} +{{- $volumes := list -}} +{{- if and .Values.config.customCa.secretName .Values.config.customCa.secretKey -}} +{{- $volumes = append $volumes (dict "name" "langsmith-custom-ca" "secret" (dict "secretName" .Values.config.customCa.secretName "items" (list (dict "key" .Values.config.customCa.secretKey "path" "ca-certificates.crt")))) -}} +{{- end -}} +{{- if .Values.redis.external.clientCert.secretName -}} +{{- $volumes = append $volumes (dict "name" "redis-client-cert" "secret" (dict "secretName" .Values.redis.external.clientCert.secretName "items" (list (dict "key" .Values.redis.external.clientCert.certSecretKey "path" "client.crt" "mode" 0644) (dict "key" .Values.redis.external.clientCert.keySecretKey "path" "client.key" "mode" 0640)))) -}} +{{- end -}} +{{- if .Values.postgres.external.clientCert.secretName -}} +{{- $volumes = append $volumes (dict "name" "postgres-client-cert" "secret" (dict "secretName" .Values.postgres.external.clientCert.secretName "items" (list (dict "key" .Values.postgres.external.clientCert.certSecretKey "path" "client.crt" "mode" 0644) (dict "key" .Values.postgres.external.clientCert.keySecretKey "path" "client.key" "mode" 0640)))) -}} +{{- end -}} +{{- if .Values.clickhouse.external.clientCert.secretName -}} +{{- $volumes = append $volumes (dict "name" "clickhouse-client-cert" "secret" (dict "secretName" .Values.clickhouse.external.clientCert.secretName "items" (list (dict "key" .Values.clickhouse.external.clientCert.certSecretKey "path" "client.crt" "mode" 0644) (dict "key" .Values.clickhouse.external.clientCert.keySecretKey "path" "client.key" "mode" 0640)))) -}} +{{- end -}} +{{ $volumes | toYaml }} +{{- end -}} + +{{/* +Strip protocol (http://, https://, etc.) from hostname +*/}} +{{- define "langsmith.hostnameWithoutProtocol" -}} +{{- if .Values.config.hostname -}} +{{- regexReplaceAll "^[a-zA-Z][a-zA-Z0-9+.-]*://" .Values.config.hostname "" -}} +{{- end -}} +{{- end -}} + +{{- define "agentBuilderOAuthEnvVars" -}} +{{- if .Values.config.agentBuilder.oauth.googleOAuthProvider }} +- name: "GOOGLE_OAUTH_PROVIDER" + value: {{ .Values.config.agentBuilder.oauth.googleOAuthProvider | quote }} +{{- end }} +{{- if .Values.config.agentBuilder.oauth.slackOAuthProvider }} +- name: "SLACK_OAUTH_PROVIDER" + value: {{ .Values.config.agentBuilder.oauth.slackOAuthProvider | quote }} +{{- end }} +{{- if .Values.config.agentBuilder.oauth.linkedinOAuthProvider }} +- name: "LINKEDIN_OAUTH_PROVIDER" + value: {{ .Values.config.agentBuilder.oauth.linkedinOAuthProvider | quote }} +{{- end }} +{{- if .Values.config.agentBuilder.oauth.linearOAuthProvider }} +- name: "LINEAR_OAUTH_PROVIDER" + value: {{ .Values.config.agentBuilder.oauth.linearOAuthProvider | quote }} +{{- end }} +{{- if .Values.config.agentBuilder.oauth.githubOAuthProvider }} +- name: "GITHUB_OAUTH_PROVIDER" + value: {{ .Values.config.agentBuilder.oauth.githubOAuthProvider | quote }} +{{- end }} +{{- end -}} + +{{- define "agentBuilderToolServerEnvVars" -}} +- name: "PORT" + value: "{{ .Values.agentBuilderToolServer.containerPort }}" +{{- include "agentBuilderOAuthEnvVars" . }} +{{- end -}} + +{{- define "agentBuilderTriggerServerEnvVars" -}} +- name: "PORT" + value: "{{ .Values.agentBuilderTriggerServer.containerPort }}" +- name: "TRIGGER_SERVER_HOST_API_URL" + value: "http://{{ include "langsmith.fullname" . }}-{{ .Values.hostBackend.name }}.{{ .Values.namespace | default .Release.Namespace }}.svc.{{ .Values.clusterDomain }}:{{ .Values.hostBackend.service.port }}" +{{- include "agentBuilderOAuthEnvVars" . }} +{{- if .Values.config.agentBuilder.oauth.slackSigningSecret }} +- name: "SLACK_SIGNING_SECRET" + value: {{ .Values.config.agentBuilder.oauth.slackSigningSecret | quote }} +{{- end }} +{{- if .Values.config.agentBuilder.oauth.slackBotId }} +- name: "AGENT_BUILDER_SLACK_BOT_ID" + value: {{ .Values.config.agentBuilder.oauth.slackBotId | quote }} +{{- end }} +{{- end -}} \ No newline at end of file diff --git a/charts/langsmith/values.yaml b/charts/langsmith/values.yaml index 61fc126a..e0ccd251 100644 --- a/charts/langsmith/values.yaml +++ b/charts/langsmith/values.yaml @@ -86,10 +86,6 @@ images: repository: "docker.io/langchain/agent-builder-trigger-server" pullPolicy: IfNotPresent tag: "0.13.14" - authProxyImage: - repository: "docker.io/envoyproxy/envoy" - pullPolicy: IfNotPresent - tag: "v1.37-latest" agentBuilderImage: repository: "docker.io/langchain/agent-builder-deep-agent" pullPolicy: IfNotPresent From 779b9d6e4482cc42dd830489968a8926ddc856e7 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 16:48:43 -0500 Subject: [PATCH 14/17] fix: : --- charts/langsmith/{ => templates}/_helpers.tpl | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename charts/langsmith/{ => templates}/_helpers.tpl (100%) diff --git a/charts/langsmith/_helpers.tpl b/charts/langsmith/templates/_helpers.tpl similarity index 100% rename from charts/langsmith/_helpers.tpl rename to charts/langsmith/templates/_helpers.tpl From a4481bb526d45f34f61493f14219f5c2819d02b0 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 16:49:40 -0500 Subject: [PATCH 15/17] newline --- charts/langsmith/templates/_helpers.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/langsmith/templates/_helpers.tpl b/charts/langsmith/templates/_helpers.tpl index c6fbdc96..85a80d11 100644 --- a/charts/langsmith/templates/_helpers.tpl +++ b/charts/langsmith/templates/_helpers.tpl @@ -754,4 +754,4 @@ Strip protocol (http://, https://, etc.) from hostname - name: "AGENT_BUILDER_SLACK_BOT_ID" value: {{ .Values.config.agentBuilder.oauth.slackBotId | quote }} {{- end }} -{{- end -}} \ No newline at end of file +{{- end -}} From d307c0e792c637b29b6de68eed74ec91c343c400 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 16:50:37 -0500 Subject: [PATCH 16/17] version --- charts/langsmith/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/langsmith/README.md b/charts/langsmith/README.md index 6275b388..bdce67a8 100644 --- a/charts/langsmith/README.md +++ b/charts/langsmith/README.md @@ -1478,6 +1478,6 @@ For information on how to use this chart, up-to-date release notes, and other gu | Ankush | | | ---------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) +Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) ## Docs Generated by [helm-docs](https://github.com/norwoodj/helm-docs) `helm-docs -t ./charts/langsmith/README.md.gotmpl` From 87f8c1ebfeee3de9d8375abafbbdea49498d4bb4 Mon Sep 17 00:00:00 2001 From: Brian Vander Schaaf Date: Tue, 17 Feb 2026 17:44:45 -0500 Subject: [PATCH 17/17] more options for header control, remove deprecated --- charts/langsmith-auth-proxy/README.md | 6 +- .../e2e/ext-authz-mock.py | 8 ++- charts/langsmith-auth-proxy/e2e/test.sh | 22 +++++- .../templates/_helpers.tpl | 24 +++++++ .../templates/auth-proxy/config-map.yaml | 71 ++++++++++--------- charts/langsmith-auth-proxy/values.yaml | 16 ++++- 6 files changed, 107 insertions(+), 40 deletions(-) diff --git a/charts/langsmith-auth-proxy/README.md b/charts/langsmith-auth-proxy/README.md index 722e0f96..cef6cae3 100644 --- a/charts/langsmith-auth-proxy/README.md +++ b/charts/langsmith-auth-proxy/README.md @@ -83,7 +83,11 @@ This chart uses the **HTTP** `ext_authz` mode — HTTP request in, HTTP response | authProxy.deployment.volumeMounts | list | `[]` | | | authProxy.deployment.volumes | list | `[]` | | | authProxy.enabled | bool | `true` | | +| authProxy.extAuthz.allowedHeadersRegex | string | `".*"` | Regex controlling which client request headers are forwarded to the ext_authz service. Defaults to all headers. Maps to http_service.allowed_headers. Uses Google RE2 syntax: https://github.com/google/re2/wiki/Syntax. | +| authProxy.extAuthz.allowedUpstreamHeaders | list | `[{exact: "authorization"}, {prefix: "x-"}]` | Patterns controlling which ext_authz response headers are forwarded upstream (authorization_response.allowed_upstream_headers). Each entry is an object with one of these keys: `exact`, `prefix`, or `safe_regex`. | +| authProxy.extAuthz.disallowedHeadersRegex | string | `""` | Regex controlling which client request headers are NOT forwarded to the ext_authz service (higher precedence than allowedHeadersRegex). Maps to http_service.disallowed_headers. Uses Google RE2 syntax: https://github.com/google/re2/wiki/Syntax. | | authProxy.extAuthz.enabled | bool | `false` | | +| authProxy.extAuthz.headersToAdd | list | `[]` | Static headers to add to every ext_authz check request (authorization_request.headers_to_add). Example: [{key: "x-auth-context", value: "langsmith"}] | | authProxy.extAuthz.maxRequestBytes | int | `8192` | Maximum request body bytes to buffer for ext_authz | | authProxy.extAuthz.sendBody | bool | `false` | Whether to send the request body to ext_authz | | authProxy.extAuthz.serviceUrl | string | `""` | HTTP service URL for ext_authz (e.g. http://my-auth-service:8080) | @@ -142,6 +146,6 @@ See [e2e/README.md](e2e/README.md) for local end-to-end testing with kind. | Brian | | | ---------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.13.1](https://github.com/norwoodj/helm-docs/releases/v1.13.1) +Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) ## Docs Generated by [helm-docs](https://github.com/norwoodj/helm-docs) `helm-docs -t ./charts/langsmith-auth-proxy/README.md.gotmpl` diff --git a/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py b/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py index f6227fe4..1ec79a03 100644 --- a/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py +++ b/charts/langsmith-auth-proxy/e2e/ext-authz-mock.py @@ -1,7 +1,9 @@ """Minimal ext_authz HTTP mock for e2e testing. -Listens on :10002, logs received headers, and returns 200 with an -Authorization header that Envoy will forward upstream. +Listens on :10002, logs received headers, and returns 200 with: +- Authorization header injected (forwarded upstream) +- X-Custom-Added header injected (forwarded upstream) +- x-envoy-auth-headers-to-remove to strip X-Remove-Me from the request """ from http.server import HTTPServer, BaseHTTPRequestHandler @@ -16,6 +18,8 @@ def do_any(self): print(f" {k}: {v}", flush=True) self.send_response(200) self.send_header("Authorization", "Bearer fake-upstream-key") + self.send_header("X-Custom-Added", "from-ext-authz") + self.send_header("x-envoy-auth-headers-to-remove", "x-remove-me") self.end_headers() # Handle every HTTP method the same way diff --git a/charts/langsmith-auth-proxy/e2e/test.sh b/charts/langsmith-auth-proxy/e2e/test.sh index 5f74489f..ecd1bb48 100755 --- a/charts/langsmith-auth-proxy/e2e/test.sh +++ b/charts/langsmith-auth-proxy/e2e/test.sh @@ -173,16 +173,18 @@ log "Test 2: POST /v1/chat/completions without JWT → 401" STATUS=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/v1/chat/completions") assert_status "No JWT returns 401" "401" "$STATUS" -log "Test 3: POST /v1/chat/completions with valid JWT → 200 + injected header" +log "Test 3: POST /v1/chat/completions with valid JWT → 200 + header add/remove" RESP=$(curl -s -w '\n%{http_code}' -X POST "$BASE/v1/chat/completions" \ -H "X-LangSmith-LLM-Auth: $JWT" \ + -H "X-Remove-Me: should-be-removed" \ -H "Content-Type: application/json" \ -d '{"model":"test"}') BODY=$(echo "$RESP" | sed '$d') STATUS=$(echo "$RESP" | tail -1) assert_status "Valid JWT returns 200" "200" "$STATUS" -# The echo server returns JSON with all received headers — verify ext_authz injected the Authorization header +# The echo server returns JSON with all received headers +# Verify ext_authz injected the Authorization header AUTH_HEADER=$(echo "$BODY" | jq -r '.headers.authorization // empty') if [[ "$AUTH_HEADER" == "Bearer fake-upstream-key" ]]; then pass "ext_authz injected Authorization header" @@ -190,6 +192,22 @@ else fail "Expected Authorization='Bearer fake-upstream-key', got '$AUTH_HEADER'" fi +# Verify ext_authz added X-Custom-Added header +CUSTOM_HEADER=$(echo "$BODY" | jq -r '.headers["x-custom-added"] // empty') +if [[ "$CUSTOM_HEADER" == "from-ext-authz" ]]; then + pass "ext_authz added X-Custom-Added header" +else + fail "Expected X-Custom-Added='from-ext-authz', got '$CUSTOM_HEADER'" +fi + +# Verify ext_authz removed X-Remove-Me header +REMOVED_HEADER=$(echo "$BODY" | jq -r '.headers["x-remove-me"] // empty') +if [[ -z "$REMOVED_HEADER" ]]; then + pass "ext_authz removed X-Remove-Me header" +else + fail "Expected X-Remove-Me to be removed, but got '$REMOVED_HEADER'" +fi + log "Test 4: POST /v1/chat/completions with garbage JWT → 401" STATUS=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/v1/chat/completions" \ -H "X-LangSmith-LLM-Auth: garbage.jwt.token") diff --git a/charts/langsmith-auth-proxy/templates/_helpers.tpl b/charts/langsmith-auth-proxy/templates/_helpers.tpl index 3bbb30f4..2183366a 100644 --- a/charts/langsmith-auth-proxy/templates/_helpers.tpl +++ b/charts/langsmith-auth-proxy/templates/_helpers.tpl @@ -98,6 +98,30 @@ Creates the image reference used for deployments. If registry is specified, conc {{- end -}} {{- end -}} +{{/* +Extract hostname from a URL string. +Usage: include "authProxy.urlHostname" "http://example.com:8080" +*/}} +{{- define "authProxy.urlHostname" -}} +{{- $url := urlParse . -}} +{{- $parts := splitList ":" $url.host -}} +{{- index $parts 0 -}} +{{- end -}} + +{{/* +Extract port from a URL string. Defaults to 443 for https, 80 for http. +Usage: include "authProxy.urlPort" "http://example.com:8080" +*/}} +{{- define "authProxy.urlPort" -}} +{{- $url := urlParse . -}} +{{- $parts := splitList ":" $url.host -}} +{{- if gt (len $parts) 1 -}} +{{- index $parts 1 -}} +{{- else -}} +{{- if eq $url.scheme "https" -}}443{{- else -}}80{{- end -}} +{{- end -}} +{{- end -}} + {{- define "authProxy.serviceAccountName" -}} {{- if .Values.authProxy.serviceAccount.create -}} {{ default (printf "%s-%s" (include "authProxy.fullname" .) .Values.authProxy.name) .Values.authProxy.serviceAccount.name | trunc 63 | trimSuffix "-" }} diff --git a/charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml b/charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml index 7f91248e..6d11af05 100644 --- a/charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml +++ b/charts/langsmith-auth-proxy/templates/auth-proxy/config-map.yaml @@ -1,19 +1,7 @@ {{- if .Values.authProxy.enabled }} -{{- $upstreamUrl := urlParse .Values.authProxy.upstream -}} -{{- $upstreamScheme := $upstreamUrl.scheme -}} -{{- $upstreamHost := $upstreamUrl.host -}} -{{- $hostParts := splitList ":" $upstreamHost -}} -{{- $upstreamHostname := index $hostParts 0 -}} -{{- $upstreamPort := "" -}} -{{- if gt (len $hostParts) 1 -}} - {{- $upstreamPort = index $hostParts 1 -}} -{{- else -}} - {{- if eq $upstreamScheme "https" -}} - {{- $upstreamPort = "443" -}} - {{- else -}} - {{- $upstreamPort = "80" -}} - {{- end -}} -{{- end -}} +{{- $upstreamScheme := (urlParse .Values.authProxy.upstream).scheme -}} +{{- $upstreamHostname := include "authProxy.urlHostname" .Values.authProxy.upstream -}} +{{- $upstreamPort := include "authProxy.urlPort" .Values.authProxy.upstream -}} apiVersion: v1 kind: ConfigMap metadata: @@ -87,23 +75,48 @@ data: typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz failure_mode_allow: false + allowed_headers: + patterns: + - safe_regex: + regex: {{ .Values.authProxy.extAuthz.allowedHeadersRegex | quote }} + {{- if .Values.authProxy.extAuthz.disallowedHeadersRegex }} + disallowed_headers: + patterns: + - safe_regex: + regex: {{ .Values.authProxy.extAuthz.disallowedHeadersRegex | quote }} + {{- end }} http_service: server_uri: uri: {{ .Values.authProxy.extAuthz.serviceUrl }} cluster: ext_authz_service timeout: {{ .Values.authProxy.extAuthz.timeout }} path_prefix: "/check" + {{- if .Values.authProxy.extAuthz.headersToAdd }} authorization_request: - allowed_headers: - patterns: - - exact: "x-langsmith-llm-auth" - - prefix: "x-" + headers_to_add: + {{- range .Values.authProxy.extAuthz.headersToAdd }} + - key: {{ .key | quote }} + value: {{ .value | quote }} + {{- end }} + {{- end }} authorization_response: allowed_upstream_headers: patterns: - - exact: "authorization" - - exact: "x-langsmith-llm-auth" - - prefix: "x-forwarded-" + {{- if .Values.authProxy.extAuthz.allowedUpstreamHeaders }} + {{- range .Values.authProxy.extAuthz.allowedUpstreamHeaders }} + {{- if .exact }} + - exact: {{ .exact | quote }} + {{- else if .prefix }} + - prefix: {{ .prefix | quote }} + {{- else if .safe_regex }} + - safe_regex: + regex: {{ .safe_regex | quote }} + {{- end }} + {{- end }} + {{- else }} + - safe_regex: + regex: ".*" + {{- end }} allowed_client_headers: patterns: - exact: "www-authenticate" @@ -140,16 +153,6 @@ data: sni: {{ $upstreamHostname }} {{- end }} {{- if .Values.authProxy.extAuthz.enabled }} - {{- $extAuthzUrl := urlParse .Values.authProxy.extAuthz.serviceUrl }} - {{- $extAuthzHost := $extAuthzUrl.host }} - {{- $extAuthzParts := splitList ":" $extAuthzHost }} - {{- $extAuthzHostname := index $extAuthzParts 0 }} - {{- $extAuthzPort := "" }} - {{- if gt (len $extAuthzParts) 1 }} - {{- $extAuthzPort = index $extAuthzParts 1 }} - {{- else }} - {{- $extAuthzPort = "80" }} - {{- end }} - name: ext_authz_service connect_timeout: 5s type: STRICT_DNS @@ -161,7 +164,7 @@ data: - endpoint: address: socket_address: - address: {{ $extAuthzHostname }} - port_value: {{ $extAuthzPort }} + address: {{ include "authProxy.urlHostname" .Values.authProxy.extAuthz.serviceUrl }} + port_value: {{ include "authProxy.urlPort" .Values.authProxy.extAuthz.serviceUrl }} {{- end }} {{- end }} diff --git a/charts/langsmith-auth-proxy/values.yaml b/charts/langsmith-auth-proxy/values.yaml index 2cd0ded5..6e69b110 100644 --- a/charts/langsmith-auth-proxy/values.yaml +++ b/charts/langsmith-auth-proxy/values.yaml @@ -40,13 +40,27 @@ authProxy: jwksJson: "" # -- Idle timeout for streaming responses (e.g. SSE from LLM providers) streamIdleTimeout: "300s" - # External authorization service configuration (for injecting LLM provider auth headers) + # External authorization service configuration (for injecting LLM provider auth headers). + # See https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto for details. extAuthz: enabled: false # -- HTTP service URL for ext_authz (e.g. http://my-auth-service:8080) serviceUrl: "" # -- Timeout for ext_authz requests timeout: "10s" + # -- Regex controlling which client request headers are forwarded to the ext_authz service. Defaults to all headers. + # Maps to http_service.allowed_headers. Uses Google RE2 syntax: https://github.com/google/re2/wiki/Syntax. + allowedHeadersRegex: ".*" + # -- Regex controlling which client request headers are NOT forwarded to the ext_authz service (higher precedence than allowedHeadersRegex). + # Maps to http_service.disallowed_headers. Uses Google RE2 syntax: https://github.com/google/re2/wiki/Syntax. + disallowedHeadersRegex: "" + # -- Static headers to add to every ext_authz check request (authorization_request.headers_to_add). + # Example: [{key: "x-auth-context", value: "langsmith"}] + headersToAdd: [] + # -- Patterns controlling which ext_authz response headers are forwarded upstream (authorization_response.allowed_upstream_headers). + # Each entry is an object with one of these keys: `exact`, `prefix`, or `safe_regex`. + # @default -- `[{exact: "authorization"}, {prefix: "x-"}]` + allowedUpstreamHeaders: [] # -- Whether to send the request body to ext_authz sendBody: false # -- Maximum request body bytes to buffer for ext_authz