Skip to content

Commit 8b35927

Browse files
mirror from operator 3.166.0- 2026-06-03
1 parent 8439b66 commit 8b35927

9 files changed

Lines changed: 441 additions & 22 deletions

File tree

mirrord-license-server/Chart.yaml

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
apiVersion: v2
22
name: mirrord-operator-license-server
3-
description: A Helm chart for Kubernetes
3+
description: Self-hosted, air-gapped license server for the mirrord Operator that manages Enterprise licenses with no external telemetry.
44

55
# A chart can be either an 'application' or a 'library' chart.
66
#
@@ -15,10 +15,36 @@ type: application
1515
# This is the chart version. This version number should be incremented each time you make changes
1616
# to the chart and its templates, including the app version.
1717
# Versions are expected to follow Semantic Versioning (https://semver.org/)
18-
version: 3.165.0
18+
version: 3.166.0
1919

2020
# This is the version number of the application being deployed. This version number should be
2121
# incremented each time you make changes to the application. Versions are not expected to
2222
# follow Semantic Versioning. They should reflect the version the application is using.
2323
# It is recommended to use it with quotes.
24-
appVersion: "3.165.0"
24+
appVersion: "3.166.0"
25+
26+
home: https://metalbear.com/mirrord
27+
icon: https://raw.githubusercontent.com/metalbear-co/mirrord/main/images/icon.png
28+
sources:
29+
- https://github.com/metalbear-co/charts
30+
keywords:
31+
- mirrord
32+
- kubernetes
33+
- license-server
34+
- air-gapped
35+
- enterprise
36+
- metalbear
37+
maintainers:
38+
- name: MetalBear
39+
email: eyal@metalbear.com
40+
url: https://metalbear.com
41+
annotations:
42+
artifacthub.io/links: |
43+
- name: Documentation
44+
url: https://metalbear.com/mirrord/docs
45+
- name: Pricing
46+
url: https://metalbear.com/mirrord/pricing
47+
- name: Source
48+
url: https://github.com/metalbear-co/charts
49+
- name: support
50+
url: https://github.com/metalbear-co/mirrord/issues

mirrord-license-server/templates/_helpers.tpl

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,90 @@ app.kubernetes.io/instance: {{ $.Release.Name }}
66
app.kubernetes.io/version: {{ $.Chart.AppVersion | quote }}
77
app.kubernetes.io/managed-by: {{ $.Release.Service }}
88
{{- end }}
9+
10+
{{- define "mirrord-license-server.samlProxyConfigMapName" -}}
11+
mirrord-license-server-saml-proxy-config
12+
{{- end }}
13+
14+
{{- define "mirrord-license-server.samlProxyAssetsSecretName" -}}
15+
mirrord-license-server-saml-proxy-assets
16+
{{- end }}
17+
18+
{{- define "mirrord-license-server.samlProxyServerConfig" -}}
19+
# Disable "forward proxy" mode.
20+
# We only want Apache to proxy to our own localhost backends, never to arbitrary destinations.
21+
ProxyRequests Off
22+
# Forward the original Host header to the backend instead of rewriting it to 127.0.0.1.
23+
# This preserves the externally visible hostname in case the backend needs it.
24+
ProxyPreserveHost On
25+
# Add standard proxy headers such as X-Forwarded-For and X-Forwarded-Proto.
26+
ProxyAddHeaders On
27+
28+
# Remove any identity headers that arrived from the outside world.
29+
# This prevents a client from spoofing authentication by sending these headers directly.
30+
RequestHeader unset X-Remote-User
31+
RequestHeader unset X-Remote-Group
32+
RequestHeader unset X-Remote-Extra-NameID
33+
RequestHeader unset X-Remote-Extra-SessionIndex
34+
35+
# Do not proxy the internal Mellon endpoints.
36+
# The "!" target means "leave this path to Apache itself", which is required because
37+
# mod_auth_mellon serves its own login/logout/assertion-consumer endpoints here.
38+
ProxyPass "/mellon" "!"
39+
# When the dashboard is enabled, its report endpoints live on the dashboard port.
40+
# These must be matched before the broader "/api/" rule below.
41+
ProxyPass "/api/" "http://127.0.0.1:{{ add .Values.dashboard.port 1 }}/api/"
42+
# Rewrite redirects from the dashboard report endpoints back to the public URL space.
43+
ProxyPassReverse "/api/" "http://127.0.0.1:{{ add .Values.dashboard.port 1 }}/api/"
44+
# Send all non-API browser traffic to the dashboard web UI.
45+
ProxyPass "/" "http://127.0.0.1:{{ add .Values.dashboard.port 1 }}/"
46+
# Rewrite redirects from the dashboard web UI back to the public URL space.
47+
ProxyPassReverse "/" "http://127.0.0.1:{{ add .Values.dashboard.port 1 }}/"
48+
# Apply SAML authentication to everything by default.
49+
# More specific Location blocks below carve out exceptions for health checks and Mellon's own
50+
# internal endpoints.
51+
<Location />
52+
# Tell Apache to use mod_auth_mellon as the authentication provider here.
53+
AuthType Mellon
54+
# Run the full SAML login flow on this path.
55+
MellonEnable auth
56+
# Allow any successfully authenticated user through.
57+
Require valid-user
58+
# Identity Provider (IdP) metadata:
59+
# describes the corporate SAML provider we redirect the browser to and trust assertions from.
60+
MellonIdPMetadataFile "{{ .Values.saml.proxy.assets.mountPath }}/idp-metadata.xml"
61+
# Service Provider (SP) metadata:
62+
# describes this Apache proxy as a SAML participant.
63+
MellonSPMetadataFile "{{ .Values.saml.proxy.assets.mountPath }}/sp-metadata.xml"
64+
# Private key belonging to this Service Provider.
65+
# Mellon uses it together with the certificate below when participating in SAML exchanges.
66+
MellonSPPrivateKeyFile "{{ .Values.saml.proxy.assets.mountPath }}/sp.key"
67+
# Public certificate that matches the Service Provider private key above.
68+
MellonSPCertFile "{{ .Values.saml.proxy.assets.mountPath }}/sp.cert"
69+
# URL prefix where Mellon exposes its own SAML helper endpoints such as login callbacks.
70+
MellonEndpointPath /mellon
71+
# The IdP returns the SAML assertion via a cross-site POST to the ACS endpoint, so the
72+
# browser must send Mellon's session cookie on that cross-site request. Browsers only do
73+
# this for SameSite=None cookies, and only honor SameSite=None when the cookie is Secure.
74+
# The proxy must therefore be reached over HTTPS (TLS terminated here or upstream).
75+
MellonSecureCookie On
76+
MellonCookieSameSite None
77+
# Copy the authenticated username into a trusted header for downstream Rust services.
78+
# The value comes from Apache's REMOTE_USER environment variable after SAML login succeeds.
79+
RequestHeader set X-Remote-User "%{REMOTE_USER}e" env=REMOTE_USER
80+
# Forward the SAML NameID in a generic "extra" header for downstream consumers that want it.
81+
RequestHeader set X-Remote-Extra-NameID "%{MELLON_NAME_ID}e" env=MELLON_NAME_ID
82+
# Forward the SAML SessionIndex in a generic "extra" header for downstream consumers.
83+
RequestHeader set X-Remote-Extra-SessionIndex "%{MELLON_SESSION_INDEX}e" env=MELLON_SESSION_INDEX
84+
</Location>
85+
86+
# Expose Mellon's own helper endpoints without trying to protect them with another SAML round.
87+
# These endpoints are part of the SAML handshake itself.
88+
<Location /mellon>
89+
# "info" tells Mellon to handle its own endpoints here without requiring a normal protected
90+
# application request.
91+
MellonEnable info
92+
# Allow the browser and the Identity Provider to reach these helper endpoints.
93+
Require all granted
94+
</Location>
95+
{{- end }}

mirrord-license-server/templates/deployment.yaml

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
{{- if and (gt (int .Values.server.replicas) 1) (eq .Values.database.kind "sqlite") }}
22
{{- fail "unable to set more than 1 replica when using sqlite, please switch to postgres database for multiple replicas" }}
33
{{- end }}
4+
{{- $tlsEnabled := or (not (empty (index .Values.tls.data "tls.key"))) .Values.tls.certManager.enabled }}
5+
{{- if and .Values.saml.enabled (not .Values.dashboard.enabled) }}
6+
{{- fail "saml.enabled=true requires dashboard.enabled=true" }}
7+
{{- end }}
8+
{{- if and .Values.saml.enabled .Values.dashboard.enabled (not (or .Values.saml.proxy.assets.existingSecret .Values.saml.proxy.assets.data)) }}
9+
{{- fail "saml.proxy.assets.existingSecret or saml.proxy.assets.data is required when saml.enabled=true" }}
10+
{{- end }}
411

512
apiVersion: apps/v1
613
kind: Deployment
@@ -16,7 +23,7 @@ spec:
1623
selector:
1724
matchLabels:
1825
app: mirrord-license-server
19-
{{- if eq .Values.database.kind "sqlite" }}
26+
{{- if or (eq .Values.database.kind "sqlite") .Values.database.migrateFromSqlite }}
2027
strategy:
2128
# This is needed, as the license server uses a ReadWriteOnce PVC.
2229
type: Recreate
@@ -49,10 +56,10 @@ spec:
4956
runAsGroup: 2000
5057
fsGroup: 2000
5158
{{/* Allow low port using ip_unprivileged_port_start */}}
52-
{{- if lt (int .Values.server.port) 1024 -}}
59+
{{- if or (lt (int .Values.server.port) 1024) (lt (int .Values.dashboard.port) 1024) -}}
5360
sysctls:
5461
- name: net.ipv4.ip_unprivileged_port_start
55-
value: {{ .Values.server.port | quote}}
62+
value: {{ min (int .Values.server.port) (int .Values.dashboard.port) | quote}}
5663
{{- end }}
5764
{{- if .Values.server.tolerations }}
5865
tolerations:
@@ -68,6 +75,7 @@ spec:
6875
{{- end }}
6976

7077
containers:
78+
# License server container
7179
- args: ["license-server"]
7280
env:
7381
- name: RUST_LOG
@@ -80,6 +88,10 @@ spec:
8088
{{- else if eq .Values.database.kind "postgres" }}
8189
- name: DATABASE_TYPE
8290
value: "postgres"
91+
{{- if .Values.database.migrateFromSqlite }}
92+
- name: MIGRATE_DATABASE_URL
93+
value: sqlite:///opt/mirrord/data/license-server.db
94+
{{- end }}
8395
{{- if kindIs "string" .Values.database.host }}
8496
- name: PGHOST
8597
value: {{ .Values.database.host | quote }}
@@ -148,12 +160,27 @@ spec:
148160
{{- if .Values.dashboard.enabled }}
149161
- name: DASHBOARD_ENABLED
150162
value: "true"
151-
- name: DASHBOARD_PORT
152-
value: {{ .Values.dashboard.port | quote }}
163+
{{- end }}
164+
{{- if .Values.dashboard.enabled }}
165+
- name: DASHBOARD_ADDRESS
166+
{{- if .Values.saml.enabled }}
167+
# SAML proxy listens on the dashboard port on public IP.
168+
# License server listens on a different port on localhost.
169+
value: {{ printf "127.0.0.1:%d" (add (int .Values.dashboard.port) 1) | quote }}
170+
{{- else }}
171+
# There is no proxy.
172+
# License server listens on the dashboard port on public IP.
173+
value: {{ printf "0.0.0.0:%d" (int .Values.dashboard.port) | quote }}
174+
{{- end }}
175+
{{- if .Values.saml.enabled }}
176+
# SAML proxy terminates TLS and injects verified headers.
177+
- name: DASHBOARD_AUTH_PROXY
178+
value: "true"
179+
{{- end }}
153180
{{- end }}
154181
- name: ERROR_EVENTS_RETENTION_DAYS
155182
value: {{ .Values.server.retention.operatorErrors | quote }}
156-
{{- if or (index .Values.tls.data "tls.key") .Values.tls.certManager.enabled }}
183+
{{- if $tlsEnabled }}
157184
- name: TLS_CERT_PATH
158185
value: /tls/tls.crt
159186
- name: TLS_KEY_PATH
@@ -182,21 +209,21 @@ spec:
182209
httpGet:
183210
path: /health
184211
port: {{ .Values.server.port }}
185-
scheme: {{ or (index .Values.tls.data "tls.key") .Values.tls.certManager.enabled | ternary "HTTPS" "HTTP" | quote }}
212+
scheme: {{ $tlsEnabled | ternary "HTTPS" "HTTP" | quote }}
186213
periodSeconds: 5
187214
name: license-server
188215
ports:
189216
- containerPort: {{ .Values.server.port }}
190217
name: https
191-
{{- if .Values.dashboard.enabled }}
218+
{{- if and .Values.dashboard.enabled (not .Values.saml.enabled) }}
192219
- containerPort: {{ .Values.dashboard.port }}
193220
name: dashboard
194221
{{- end }}
195222
readinessProbe:
196223
httpGet:
197224
path: /health
198225
port: {{ .Values.server.port }}
199-
scheme: {{ or (index .Values.tls.data "tls.key") .Values.tls.certManager.enabled | ternary "HTTPS" "HTTP" | quote }}
226+
scheme: {{ $tlsEnabled | ternary "HTTPS" "HTTP" | quote }}
200227
periodSeconds: 5
201228
resources:
202229
requests:
@@ -210,7 +237,7 @@ spec:
210237
privileged: false
211238
readOnlyRootFilesystem: true
212239
volumeMounts:
213-
{{- if or (index .Values.tls.data "tls.key") .Values.tls.certManager.enabled }}
240+
{{- if $tlsEnabled }}
214241
- mountPath: /tls
215242
name: tls-volume
216243
{{- end }}
@@ -220,15 +247,54 @@ spec:
220247
name: license-volume
221248
{{- end }}
222249
# needed for the license-server create sqlite database
223-
{{- if eq .Values.database.kind "sqlite" }}
250+
{{- if or (eq .Values.database.kind "sqlite") .Values.database.migrateFromSqlite }}
224251
- mountPath: /opt/mirrord/data
225252
name: data
226253
{{- end }}
227254
- mountPath: /tmp
228255
name: tmp
256+
257+
{{- if and .Values.saml.enabled .Values.dashboard.enabled }}
258+
# SAML proxy container
259+
- name: saml-proxy
260+
image: {{ .Values.saml.proxy.image }}:{{ .Values.saml.proxy.tag }}
261+
imagePullPolicy: {{ default "IfNotPresent" .Values.saml.proxy.imagePullPolicy }}
262+
{{- if .Values.saml.proxy.extraEnv }}
263+
env:
264+
{{- range $name, $value := .Values.saml.proxy.extraEnv }}
265+
- name: {{ $name }}
266+
value: {{ $value | quote }}
267+
{{- end }}
268+
{{- end }}
269+
ports:
270+
- containerPort: {{ .Values.dashboard.port }}
271+
name: dashboard
272+
resources:
273+
{{- toYaml .Values.saml.proxy.resources | nindent 10 }}
274+
securityContext:
275+
allowPrivilegeEscalation: false
276+
privileged: false
277+
readOnlyRootFilesystem: false
278+
volumeMounts:
279+
- mountPath: /usr/local/apache2/conf/httpd.conf
280+
name: saml-proxy-config
281+
subPath: httpd.conf
282+
{{- if or .Values.saml.proxy.assets.existingSecret .Values.saml.proxy.assets.data }}
283+
- mountPath: {{ .Values.saml.proxy.assets.mountPath }}
284+
name: saml-proxy-assets
285+
readOnly: true
286+
{{- end }}
287+
{{- if $tlsEnabled }}
288+
- mountPath: /tls
289+
name: tls-volume
290+
{{- end }}
291+
- mountPath: /tmp
292+
name: tmp
293+
{{- end }}
294+
229295
serviceAccountName: {{ .Values.sa.name }}
230296
volumes:
231-
{{- if or (index .Values.tls.data "tls.key") .Values.tls.certManager.enabled }}
297+
{{- if $tlsEnabled }}
232298
- name: tls-volume
233299
secret:
234300
secretName: {{ .Values.tls.secret }}
@@ -239,10 +305,20 @@ spec:
239305
secret:
240306
secretName: {{ .Values.license.file.secret }}
241307
{{- end }}
242-
{{- if eq .Values.database.kind "sqlite" }}
308+
{{- if or (eq .Values.database.kind "sqlite") .Values.database.migrateFromSqlite }}
243309
- name: data
244310
persistentVolumeClaim:
245311
claimName: mirrord-license-server-pvc
246312
{{- end }}
313+
{{- if and .Values.saml.enabled .Values.dashboard.enabled }}
314+
- name: saml-proxy-config
315+
configMap:
316+
name: {{ include "mirrord-license-server.samlProxyConfigMapName" . }}
317+
{{- if or .Values.saml.proxy.assets.existingSecret .Values.saml.proxy.assets.data }}
318+
- name: saml-proxy-assets
319+
secret:
320+
secretName: {{ default (include "mirrord-license-server.samlProxyAssetsSecretName" .) .Values.saml.proxy.assets.existingSecret }}
321+
{{- end }}
322+
{{- end }}
247323
- emptyDir: {}
248324
name: tmp

mirrord-license-server/templates/pvc.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{{- if eq .Values.database.kind "sqlite" }}
1+
{{- if or (eq .Values.database.kind "sqlite") .Values.database.migrateFromSqlite }}
22
apiVersion: v1
33
kind: PersistentVolumeClaim
44
metadata:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{{- if and .Values.saml.enabled .Values.dashboard.enabled .Values.saml.proxy.assets.data }}
2+
apiVersion: v1
3+
kind: Secret
4+
metadata:
5+
name: {{ include "mirrord-license-server.samlProxyAssetsSecretName" . }}
6+
namespace: {{ .Values.namespace }}
7+
labels:
8+
{{- include "mirrord-license-server.labels" . | nindent 4 }}
9+
stringData:
10+
{{- toYaml .Values.saml.proxy.assets.data | nindent 2 }}
11+
type: Opaque
12+
{{- end }}

0 commit comments

Comments
 (0)