Skip to content

Commit f7e4498

Browse files
authored
[helm] Enable SASL authentication configurations (apache#2506)
1 parent ac21483 commit f7e4498

File tree

10 files changed

+721
-6
lines changed

10 files changed

+721
-6
lines changed

helm/README.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This chart deploys an Apache Fluss cluster on Kubernetes, following Helm best pr
2222
It requires a Zookeeper ensemble to be running in the same Kubernetes cluster. In future releases, we may add support for an embedded Zookeeper cluster.
2323

2424

25-
## Development environment
25+
## Development environment
2626

2727
| component | version |
2828
| ------------------------------------------------------------------------------ | ------- |
@@ -33,7 +33,7 @@ It requires a Zookeeper ensemble to be running in the same Kubernetes cluster. I
3333
| [Apache Fluss](https://fluss.apache.org/docs/) | v0.10.0-incubating |
3434

3535

36-
## Image requirements
36+
## Image requirements
3737

3838
A container image for Fluss is available on DockerHub as `fluss/fluss`. You can use it directly or build your own from this repo. To use your own image you need to build the project with [Maven](https://fluss.apache.org/community/dev/building/) and build it with Docker.
3939

@@ -98,6 +98,34 @@ Important Fluss options surfaced by the chart:
9898
- zookeeper.address must point to a reachable ensemble.
9999
- data.dir defaults to /tmp/fluss/data; use a PVC if persistence.enabled=true.
100100

101+
### Security configuration
102+
103+
The authentication methods are configured through `security` values block.
104+
105+
```yaml
106+
listeners:
107+
internal:
108+
port: 9123
109+
client:
110+
port: 9124
111+
112+
security:
113+
client:
114+
sasl:
115+
mechanism: plain
116+
plain:
117+
users:
118+
- username: client-user
119+
password: client-password
120+
121+
internal:
122+
sasl:
123+
mechanism: plain
124+
plain:
125+
username: internal-user
126+
password: internal-password
127+
```
128+
101129
### Private Docker Registry
102130
103131
If you are pulling the Fluss image from a private Docker registry, you can configure the chart using `image.registry` and `image.pullSecrets`.

helm/templates/NOTES.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
CHART NAME: {{ .Chart.Name }}
20+
CHART VERSION: {{ .Chart.Version }}
21+
APP VERSION: {{ .Chart.AppVersion }}
22+
23+
{{ include "fluss.security.validateValues" . }}

helm/templates/_security.tpl

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
{{/*
20+
Returns the authentication mechanism value of a given listener.
21+
Allowed mechanism values: '', 'plain'
22+
Usage:
23+
include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "client")
24+
*/}}
25+
{{- define "fluss.security.listener.mechanism" -}}
26+
{{- $listener := index .context.security .listener | default (dict) -}}
27+
{{- $sasl := $listener.sasl | default (dict) -}}
28+
{{- $mechanism := lower (default "" $sasl.mechanism) -}}
29+
{{- $mechanism -}}
30+
{{- end -}}
31+
32+
{{/*
33+
Returns true if any of the listeners uses SASL based authentication mechanism ('plain' for now).
34+
Usage:
35+
include "fluss.security.sasl.enabled" .
36+
*/}}
37+
{{- define "fluss.security.sasl.enabled" -}}
38+
{{- $internal := include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "internal") -}}
39+
{{- $client := include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "client") -}}
40+
{{- if or (ne $internal "") (ne $client "") -}}true{{- end -}}
41+
{{- end -}}
42+
43+
{{/*
44+
Returns true if any of the listeners uses 'plain' authentication mechanism.
45+
Usage:
46+
include "fluss.security.sasl.plain.enabled" .
47+
*/}}
48+
{{- define "fluss.security.sasl.plain.enabled" -}}
49+
{{- $internal := include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "internal") -}}
50+
{{- $client := include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "client") -}}
51+
{{- if or (eq $internal "plain") (eq $client "plain") -}}true{{- end -}}
52+
{{- end -}}
53+
54+
{{/*
55+
Returns protocol value derived from listener mechanism.
56+
Usage:
57+
include "fluss.security.listener.protocol" (dict "context" .Values "listener" "internal")
58+
*/}}
59+
{{- define "fluss.security.listener.protocol" -}}
60+
{{- $mechanism := include "fluss.security.listener.mechanism" (dict "context" .context "listener" .listener) -}}
61+
{{- if eq $mechanism "" -}}PLAINTEXT{{- else -}}SASL{{- end -}}
62+
{{- end -}}
63+
64+
{{/*
65+
Returns comma separated list of enabled mechanisms.
66+
Usage:
67+
include "fluss.security.sasl.enabledMechanisms" .
68+
*/}}
69+
{{- define "fluss.security.sasl.enabledMechanisms" -}}
70+
{{- $mechanisms := list -}}
71+
{{- range $listener := list "internal" "client" -}}
72+
{{- $current := include "fluss.security.listener.mechanism" (dict "context" $.Values "listener" $listener) -}}
73+
{{- if and (ne $current "") (not (has (upper $current) $mechanisms)) -}}
74+
{{- $mechanisms = append $mechanisms (upper $current) -}}
75+
{{- end -}}
76+
{{- end -}}
77+
{{- join "," $mechanisms -}}
78+
{{- end -}}
79+
80+
{{/*
81+
Validates that SASL mechanisms are valid.
82+
Returns an error message if invalid, empty string otherwise.
83+
Usage:
84+
include "fluss.security.sasl.validateMechanisms" .
85+
*/}}
86+
{{- define "fluss.security.sasl.validateMechanisms" -}}
87+
{{- $allowedMechanisms := list "" "plain" -}}
88+
{{- range $listener := list "internal" "client" -}}
89+
{{- $listenerValues := index $.Values.security $listener | default (dict) -}}
90+
{{- $sasl := $listenerValues.sasl | default (dict) -}}
91+
{{- $mechanism := lower (default "" $sasl.mechanism) -}}
92+
{{- if not (has $mechanism $allowedMechanisms) -}}
93+
{{- printf "security.%s.sasl.mechanism must be empty or: plain" $listener -}}
94+
{{- end -}}
95+
{{- end -}}
96+
{{- end -}}
97+
98+
{{/*
99+
Validates that the client PLAIN mechanism block contains the required users.
100+
Returns an error message if invalid, empty string otherwise.
101+
Usage:
102+
include "fluss.security.sasl.validateClientPlainUsers" .
103+
*/}}
104+
{{- define "fluss.security.sasl.validateClientPlainUsers" -}}
105+
{{- $clientMechanism := include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "client") -}}
106+
{{- if eq $clientMechanism "plain" -}}
107+
{{- $users := .Values.security.client.sasl.plain.users | default (list) -}}
108+
{{- if eq (len $users) 0 -}}
109+
{{- print "security.client.sasl.plain.users must contain at least one user when security.client.sasl.mechanism is plain" -}}
110+
{{- else -}}
111+
{{- range $idx, $user := $users -}}
112+
{{- if or (empty $user.username) (empty $user.password) -}}
113+
{{- printf "security.client.sasl.plain.users[%d] must set both username and password" $idx -}}
114+
{{- end -}}
115+
{{- end -}}
116+
{{- end -}}
117+
{{- end -}}
118+
{{- end -}}
119+
120+
{{/*
121+
Returns the default internal SASL username based on the release name.
122+
Usage:
123+
include "fluss.security.sasl.plain.internal.defaultUsername" .
124+
*/}}
125+
{{- define "fluss.security.sasl.plain.internal.defaultUsername" -}}
126+
{{- printf "fluss-internal-user-%s" .Release.Name -}}
127+
{{- end -}}
128+
129+
{{/*
130+
Returns the default internal SASL password based on the release name (sha256 hashed).
131+
Usage:
132+
include "fluss.security.sasl.plain.internal.defaultPassword" .
133+
*/}}
134+
{{- define "fluss.security.sasl.plain.internal.defaultPassword" -}}
135+
{{- printf "fluss-internal-password-%s" .Release.Name | sha256sum -}}
136+
{{- end -}}
137+
138+
{{/*
139+
Returns the resolved internal SASL username (user-provided or auto-generated default).
140+
Usage:
141+
include "fluss.security.sasl.plain.internal.username" .
142+
*/}}
143+
{{- define "fluss.security.sasl.plain.internal.username" -}}
144+
{{- .Values.security.internal.sasl.plain.username | default (include "fluss.security.sasl.plain.internal.defaultUsername" .) -}}
145+
{{- end -}}
146+
147+
{{/*
148+
Returns the resolved internal SASL password (user-provided or auto-generated default).
149+
Usage:
150+
include "fluss.security.sasl.plain.internal.password" .
151+
*/}}
152+
{{- define "fluss.security.sasl.plain.internal.password" -}}
153+
{{- .Values.security.internal.sasl.plain.password | default (include "fluss.security.sasl.plain.internal.defaultPassword" .) -}}
154+
{{- end -}}
155+
156+
{{/*
157+
Returns a warning if the internal SASL user is using auto-generated credentials.
158+
Usage:
159+
include "fluss.security.sasl.warnInternalUser" .
160+
*/}}
161+
{{- define "fluss.security.sasl.warnInternalUser" -}}
162+
{{- if (include "fluss.security.sasl.enabled" .) -}}
163+
{{- $internalMechanism := include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "internal") -}}
164+
{{- if eq $internalMechanism "plain" -}}
165+
{{- if and (not .Values.security.internal.sasl.plain.username) (not .Values.security.internal.sasl.plain.password) -}}
166+
{{- print "You are using AUTO-GENERATED SASL credentials for internal communication.\n It is strongly recommended to set the following values in production:\n - security.internal.sasl.plain.username\n - security.internal.sasl.plain.password" -}}
167+
{{- end -}}
168+
{{- end -}}
169+
{{- end -}}
170+
{{- end -}}
171+
172+
{{/*
173+
Compile all warnings and errors into a single message.
174+
Usage:
175+
include "fluss.security.validateValues" .
176+
*/}}
177+
{{- define "fluss.security.validateValues" -}}
178+
179+
{{- $errMessages := list -}}
180+
{{- $errMessages = append $errMessages (include "fluss.security.sasl.validateMechanisms" .) -}}
181+
{{- $errMessages = append $errMessages (include "fluss.security.sasl.validateClientPlainUsers" .) -}}
182+
183+
{{- $errMessages = without $errMessages "" -}}
184+
{{- $errMessage := join "\n" $errMessages -}}
185+
186+
{{- $warnMessages := list -}}
187+
{{- $warnMessages = append $warnMessages (include "fluss.security.sasl.warnInternalUser" .) -}}
188+
189+
{{- $warnMessages = without $warnMessages "" -}}
190+
{{- $warnMessage := join "\n" $warnMessages -}}
191+
192+
{{- if $warnMessage -}}
193+
{{- printf "\nVALUES WARNING:\n%s" $warnMessage -}}
194+
{{- end -}}
195+
196+
{{- if $errMessage -}}
197+
{{- printf "\nVALUES VALIDATION:\n%s" $errMessage | fail -}}
198+
{{- end -}}
199+
200+
{{- end -}}
201+
202+
{{/*
203+
Returns the SASL JAAS config name.
204+
Usage:
205+
include "fluss.security.sasl.configName" .
206+
*/}}
207+
{{- define "fluss.security.sasl.configName" -}}
208+
{{ include "fluss.fullname" . }}-sasl-jaas-config
209+
{{- end -}}

helm/templates/configmap.yaml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,19 @@ data:
2626
server.yaml: |
2727
{{- range $key, $val := .Values.configurationOverrides }}
2828
{{ $key }}: {{ tpl (printf "%v" $val) $ }}
29-
{{- end }}
29+
{{- end }}
30+
31+
### Security
32+
33+
{{- $internalProtocol := include "fluss.security.listener.protocol" (dict "context" .Values "listener" "internal") | trim -}}
34+
{{- $clientProtocol := include "fluss.security.listener.protocol" (dict "context" .Values "listener" "client") | trim -}}
35+
{{- $enabledMechanisms := include "fluss.security.sasl.enabledMechanisms" . | trim -}}
36+
{{- $internalMechanism := include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "internal") -}}
37+
{{- if (include "fluss.security.sasl.enabled" .) }}
38+
security.protocol.map: INTERNAL:{{ $internalProtocol }},CLIENT:{{ $clientProtocol }}
39+
security.sasl.enabled.mechanisms: {{ $enabledMechanisms }}
40+
{{- if ne $internalMechanism "" }}
41+
client.security.protocol: SASL
42+
client.security.sasl.mechanism: {{ upper $internalMechanism }}
43+
{{- end }}
44+
{{- end }}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
{{- if (include "fluss.security.sasl.plain.enabled" .) -}}
20+
{{- $internalMechanism := include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "internal") -}}
21+
{{- $clientMechanism := include "fluss.security.listener.mechanism" (dict "context" .Values "listener" "client") -}}
22+
{{- $internalUsername := include "fluss.security.sasl.plain.internal.username" . -}}
23+
{{- $internalPassword := include "fluss.security.sasl.plain.internal.password" . -}}
24+
apiVersion: v1
25+
kind: Secret
26+
metadata:
27+
name: {{ include "fluss.security.sasl.configName" . }}
28+
labels:
29+
{{- include "fluss.labels" . | nindent 4 }}
30+
type: Opaque
31+
stringData:
32+
jaas.conf: |
33+
{{- if eq $internalMechanism "plain" }}
34+
internal.FlussServer {
35+
org.apache.fluss.security.auth.sasl.plain.PlainLoginModule required
36+
user_{{ $internalUsername }}="{{ $internalPassword }}";
37+
};
38+
FlussClient {
39+
org.apache.fluss.security.auth.sasl.plain.PlainLoginModule required
40+
username="{{ $internalUsername }}"
41+
password="{{ $internalPassword }}";
42+
};
43+
{{- end }}
44+
{{- if eq $clientMechanism "plain" }}
45+
client.FlussServer {
46+
org.apache.fluss.security.auth.sasl.plain.PlainLoginModule required
47+
{{- range .Values.security.client.sasl.plain.users | default (list) }}
48+
user_{{ .username }}="{{ .password }}"
49+
{{- end }};
50+
};
51+
{{- end }}
52+
{{- end -}}

0 commit comments

Comments
 (0)