Skip to content

Commit 04a956e

Browse files
authored
Expose backend security and org authorization settings (#87)
* add url security env vars * fix: Allow arbitrary unsafe URL request modes The chart should pass through trimmed API URL security settings without blocking render-time values, leaving mode validation to the application. * Expose allowed org ID authorization settings (#88) * add allowed org ids * fix: Accept PRIMARY_ORG_NAME from API env Deploys that source the primary organization through extra environment variables should satisfy the same self-hosted service-token requirement as the chart value, while still rejecting wildcard or empty orgs with no primary organization configured. * feat: Pass URL security settings to AI Gateway Keep outbound URL validation behavior consistent between the API and AI Gateway when operators configure shared URL security values. Update tests and value guidance to preserve trimming and ID allowlist expectations.
1 parent 5d6ce7e commit 04a956e

5 files changed

Lines changed: 223 additions & 1 deletion

File tree

braintrust/templates/ai-gateway-configmap.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ data:
2222
BRAINTRUST_API_URL: {{ default (include "braintrust.apiInternalUrl" .) .Values.aiGateway.braintrustApiUrl | quote }}
2323
GATEWAY_JSON_LOGS: "true"
2424
GATEWAY_TELEMETRY: {{ .Values.global.controlPlaneTelemetry | quote }}
25+
{{- with (.Values.api.unsafeUrlRequestMode | default "" | toString | trim) }}
26+
BRAINTRUST_UNSAFE_URL_REQUEST_MODE: {{ . | quote }}
27+
{{- end }}
28+
{{- with (.Values.api.urlSecurityDnsServers | default "" | toString | trim) }}
29+
BRAINTRUST_URL_SECURITY_DNS_SERVERS: {{ . | quote }}
30+
{{- end }}
31+
{{- with (.Values.api.urlSecurityAllowCidrs | default "" | toString | trim) }}
32+
BRAINTRUST_URL_SECURITY_ALLOW_CIDRS: {{ . | quote }}
33+
{{- end }}
2534
{{- if .Values.global.controlPlaneTelemetry }}
2635
{{- $appUrl := .Values.aiGateway.braintrustAppUrl | trimSuffix "/" }}
2736
OTLP_HTTP_ENDPOINT: {{ printf "%s/api/pulse/otel" $appUrl | quote }}

braintrust/templates/api-configmap.yaml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
{{- $orgName := .Values.global.orgName | default "" | toString | trim -}}
2+
{{- $primaryOrgName := .Values.global.primaryOrgName | default "" | toString | trim -}}
3+
{{- $hasPrimaryOrgNameExtraEnv := false -}}
4+
{{- range .Values.api.extraEnvVars -}}
5+
{{- if eq (.name | default "" | toString) "PRIMARY_ORG_NAME" -}}
6+
{{- $hasPrimaryOrgNameExtraEnv = true -}}
7+
{{- end -}}
8+
{{- end -}}
9+
{{- if and (or (eq $orgName "") (eq $orgName "*")) (eq $primaryOrgName "") (not $hasPrimaryOrgNameExtraEnv) -}}
10+
{{- fail "global.primaryOrgName or api.extraEnvVars PRIMARY_ORG_NAME is required when global.orgName is empty or \"*\"; self-hosted service-token management needs a primary organization." -}}
11+
{{- end -}}
12+
{{- $allowedOrgIds := .Values.global.allowedOrgIds | default "" | toString | trim -}}
113
---
214
apiVersion: v1
315
kind: ConfigMap
@@ -13,7 +25,11 @@ metadata:
1325
{{- toYaml . | nindent 4 }}
1426
{{- end }}
1527
data:
16-
ORG_NAME: {{ .Values.global.orgName | quote }}
28+
ORG_NAME: {{ $orgName | quote }}
29+
PRIMARY_ORG_NAME: {{ $primaryOrgName | quote }}
30+
{{- with $allowedOrgIds }}
31+
ALLOWED_ORG_IDS: {{ . | quote }}
32+
{{- end }}
1733

1834
{{- if eq .Values.cloud "azure" }}
1935
AZURE_STORAGE_ACCOUNT_NAME: {{ .Values.objectStorage.azure.storageAccountName | quote }}
@@ -55,6 +71,15 @@ data:
5571
INSERT_LOGS2: "true"
5672
ALLOW_INVALID_BASE64: {{ .Values.api.allowInvalidBase64 | default "false" | quote }}
5773
NODE_MEMORY_PERCENT: {{ .Values.api.nodeMemoryPercent | default "80" | quote }}
74+
{{- with (.Values.api.unsafeUrlRequestMode | default "" | toString | trim) }}
75+
BRAINTRUST_UNSAFE_URL_REQUEST_MODE: {{ . | quote }}
76+
{{- end }}
77+
{{- with (.Values.api.urlSecurityDnsServers | default "" | toString | trim) }}
78+
BRAINTRUST_URL_SECURITY_DNS_SERVERS: {{ . | quote }}
79+
{{- end }}
80+
{{- with (.Values.api.urlSecurityAllowCidrs | default "" | toString | trim) }}
81+
BRAINTRUST_URL_SECURITY_ALLOW_CIDRS: {{ . | quote }}
82+
{{- end }}
5883
{{- if .Values.brainstoreWalFooterVersion }}
5984
BRAINSTORE_WAL_FOOTER_VERSION: {{ .Values.brainstoreWalFooterVersion | quote }}
6085
{{- end }}

braintrust/tests/ai-gateway-configmap_test.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,37 @@ tests:
4141
- equal:
4242
path: data.GATEWAY_TELEMETRY
4343
value: "status,metrics"
44+
- isNull:
45+
path: data.BRAINTRUST_UNSAFE_URL_REQUEST_MODE
46+
- isNull:
47+
path: data.BRAINTRUST_URL_SECURITY_DNS_SERVERS
48+
- isNull:
49+
path: data.BRAINTRUST_URL_SECURITY_ALLOW_CIDRS
4450
- equal:
4551
path: data.OTLP_HTTP_ENDPOINT
4652
value: "https://www.braintrust.dev/api/pulse/otel"
4753

54+
- it: should include URL security env vars when configured
55+
values:
56+
- __fixtures__/base-values.yaml
57+
set:
58+
aiGateway.enabled: true
59+
api.unsafeUrlRequestMode: " proxy "
60+
api.urlSecurityDnsServers: " 1.1.1.1,8.8.8.8 "
61+
api.urlSecurityAllowCidrs: " 10.0.0.0/8,192.168.0.0/16 "
62+
release:
63+
namespace: "braintrust"
64+
asserts:
65+
- equal:
66+
path: data.BRAINTRUST_UNSAFE_URL_REQUEST_MODE
67+
value: "proxy"
68+
- equal:
69+
path: data.BRAINTRUST_URL_SECURITY_DNS_SERVERS
70+
value: "1.1.1.1,8.8.8.8"
71+
- equal:
72+
path: data.BRAINTRUST_URL_SECURITY_ALLOW_CIDRS
73+
value: "10.0.0.0/8,192.168.0.0/16"
74+
4875
- it: should set GATEWAY_REGION when region is provided
4976
values:
5077
- __fixtures__/base-values.yaml

braintrust/tests/api-configmap_test.yaml

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,152 @@ tests:
1616
- equal:
1717
path: data.ORG_NAME
1818
value: "test-org"
19+
- equal:
20+
path: data.PRIMARY_ORG_NAME
21+
value: ""
1922
- equal:
2023
path: data.BRAINSTORE_ENABLED
2124
value: "true"
2225
- equal:
2326
path: data.BRAINSTORE_DEFAULT
2427
value: "force"
2528

29+
- it: should omit URL security env vars when unset
30+
values:
31+
- __fixtures__/base-values.yaml
32+
release:
33+
namespace: "braintrust"
34+
asserts:
35+
- isNull:
36+
path: data.BRAINTRUST_UNSAFE_URL_REQUEST_MODE
37+
- isNull:
38+
path: data.BRAINTRUST_URL_SECURITY_DNS_SERVERS
39+
- isNull:
40+
path: data.BRAINTRUST_URL_SECURITY_ALLOW_CIDRS
41+
42+
- it: should include URL security env vars when configured
43+
values:
44+
- __fixtures__/base-values.yaml
45+
set:
46+
api.unsafeUrlRequestMode: " reject "
47+
api.urlSecurityDnsServers: " 1.1.1.1,8.8.8.8 "
48+
api.urlSecurityAllowCidrs: " 10.0.0.0/8,192.168.0.0/16 "
49+
release:
50+
namespace: "braintrust"
51+
asserts:
52+
- equal:
53+
path: data.BRAINTRUST_UNSAFE_URL_REQUEST_MODE
54+
value: "reject"
55+
- equal:
56+
path: data.BRAINTRUST_URL_SECURITY_DNS_SERVERS
57+
value: "1.1.1.1,8.8.8.8"
58+
- equal:
59+
path: data.BRAINTRUST_URL_SECURITY_ALLOW_CIDRS
60+
value: "10.0.0.0/8,192.168.0.0/16"
61+
62+
- it: should omit allowed org IDs when unset
63+
values:
64+
- __fixtures__/base-values.yaml
65+
release:
66+
namespace: "braintrust"
67+
asserts:
68+
- isNull:
69+
path: data.ALLOWED_ORG_IDS
70+
71+
- it: should omit allowed org IDs when blank
72+
values:
73+
- __fixtures__/base-values.yaml
74+
set:
75+
global.allowedOrgIds: " "
76+
release:
77+
namespace: "braintrust"
78+
asserts:
79+
- isNull:
80+
path: data.ALLOWED_ORG_IDS
81+
82+
- it: should include allowed org IDs when configured
83+
values:
84+
- __fixtures__/base-values.yaml
85+
set:
86+
global.allowedOrgIds: " 00000000-0000-4000-8000-000000000001,00000000-0000-4000-8000-000000000002 "
87+
release:
88+
namespace: "braintrust"
89+
asserts:
90+
- equal:
91+
path: data.ALLOWED_ORG_IDS
92+
value: "00000000-0000-4000-8000-000000000001,00000000-0000-4000-8000-000000000002"
93+
94+
- it: should include primary org name when configured
95+
values:
96+
- __fixtures__/base-values.yaml
97+
set:
98+
global.primaryOrgName: " primary-org "
99+
release:
100+
namespace: "braintrust"
101+
asserts:
102+
- equal:
103+
path: data.PRIMARY_ORG_NAME
104+
value: "primary-org"
105+
106+
- it: should allow wildcard org name when primary org name is configured
107+
values:
108+
- __fixtures__/base-values.yaml
109+
set:
110+
global.orgName: "*"
111+
global.primaryOrgName: "primary-org"
112+
release:
113+
namespace: "braintrust"
114+
asserts:
115+
- equal:
116+
path: data.ORG_NAME
117+
value: "*"
118+
- equal:
119+
path: data.PRIMARY_ORG_NAME
120+
value: "primary-org"
121+
122+
- it: should allow wildcard org name when primary org name is provided via extra env vars
123+
values:
124+
- __fixtures__/base-values.yaml
125+
set:
126+
global.orgName: "*"
127+
global.primaryOrgName: ""
128+
api.extraEnvVars:
129+
- name: PRIMARY_ORG_NAME
130+
value: "primary-org"
131+
release:
132+
namespace: "braintrust"
133+
asserts:
134+
- equal:
135+
path: data.ORG_NAME
136+
value: "*"
137+
- equal:
138+
path: data.PRIMARY_ORG_NAME
139+
value: ""
140+
141+
- it: should reject empty org name without primary org name
142+
values:
143+
- __fixtures__/base-values.yaml
144+
set:
145+
global.orgName: ""
146+
global.primaryOrgName: ""
147+
release:
148+
namespace: "braintrust"
149+
asserts:
150+
- failedTemplate:
151+
errorMessage: "global.primaryOrgName or api.extraEnvVars PRIMARY_ORG_NAME is required when global.orgName is empty or \"*\"; self-hosted service-token management needs a primary organization."
152+
153+
- it: should reject wildcard org name without primary org name
154+
values:
155+
- __fixtures__/base-values.yaml
156+
set:
157+
global.orgName: "*"
158+
global.primaryOrgName: " "
159+
release:
160+
namespace: "braintrust"
161+
asserts:
162+
- failedTemplate:
163+
errorMessage: "global.primaryOrgName or api.extraEnvVars PRIMARY_ORG_NAME is required when global.orgName is empty or \"*\"; self-hosted service-token management needs a primary organization."
164+
26165
- it: should use correct namespace from helper when createNamespace is false
27166
values:
28167
- __fixtures__/base-values.yaml

braintrust/values.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Global configs
22
global:
33
orgName: "<your org name on Braintrust>"
4+
# Required when orgName is empty or "*" unless PRIMARY_ORG_NAME is supplied
5+
# via api.extraEnvVars. Used to authorize self-hosted service-token management.
6+
primaryOrgName: ""
7+
# Optional comma-separated Braintrust Org ID allowlist (IDs, not org names).
8+
# Do not include spaces. If orgName is a specific name, that org is included
9+
# in the allowlist; include its Braintrust Org ID here for forward compatibility.
10+
allowedOrgIds: ""
411
# When createNamespace is true, the namespace will be created and resources will be in global.namespace
512
# When createNamespace is false, resources will use .Release.Namespace (the namespace specified during helm install/upgrade)
613
createNamespace: false
@@ -126,6 +133,21 @@ api:
126133
backfillDisableNonhistorical: false
127134
allowInvalidBase64: false # By default, we will error on invalid base64 strings. Setting this to true will allow invalid base64 strings to be processed.
128135
nodeMemoryPercent: "80"
136+
# Controls how Braintrust backends handle outbound requests to user-supplied URLs
137+
# that fail URL-security checks, such as URLs resolving to private or reserved IP
138+
# ranges. Applies to the API and AI Gateway when configured. Use "off" to allow,
139+
# "proxy" to proxy, "warn" to allow with warnings, or "reject" to block. Leave
140+
# empty to use the application default of "warn".
141+
unsafeUrlRequestMode: ""
142+
# Comma-separated DNS resolver IP addresses Braintrust backends should query when
143+
# checking user-supplied URLs. Set this to force URL-security validation through
144+
# trusted resolvers, such as VNet or corporate DNS, before falling back to the host
145+
# resolver. Leave empty to use the application default resolver behavior.
146+
urlSecurityDnsServers: ""
147+
# Optional comma-separated CIDR ranges that Braintrust backend URL-security
148+
# validation may allow even if private or reserved. Hard-blocked metadata,
149+
# link-local, multicast, unspecified, and future-use ranges remain blocked.
150+
urlSecurityAllowCidrs: ""
129151
extraEnvVars:
130152
# Example:
131153
# - name: MY_ENV_VAR

0 commit comments

Comments
 (0)