背景

上次emqx使用mysql认证后(参见这个文章),结果发现在部分情况下会导致emqx无法连接到mysql认证,需要开关一下认证才恢复,怀疑和emqx mysql的启动顺序有关,为了稳妥起见,决定切换到使用emqx内置的数据库进行认证,原先使用的5.7.1版本做这个事情很麻烦,只能通过emqx的api接口进行,而且放在k8s中的话,也无法通过init容器进行,因为这会emqx没起来。。。

一通搜索之后发现,5.7.2版本中引入了一个新功能,通过bootstrap file可以进行用户初始化,这就很完美了,只需要挂载配置进去,emqx重启就会自动初始化用户进去,实现还是之前的套路,改造一下helm包。

获取helm文件

1
2
helm repo add emqx-operator https://repos.emqx.io/charts
helm pull emqx-operator/emqx --version 5.7.2

修改文件

增加配置文件模板

template目录中新增emqx_config.yaml,相比原生配置增加了authentication这一段内容,重点在于增加bootstrap_file和bootstrap_type两个字段:
bootstrap_file很好理解,后面认证的初始化数据路径,支持csv和json,这里我用了json;
bootstrap_type支持plain和hash,就是后面bootstrap_file内容中password是否要hash加密,plain的话对应的密码就是明文,hash需要先进行一次加密,这里使用hash,防止通过查看配置文件直接泄露密码。

初始化认证文件

配置文件中init_user.json部分即为初始化认证的用户信息:
user_id:用户名
salt:用来增加密码复杂度,防止彩虹表,自己随便弄串字符串就行
password_hash: 真正使用的密码后面拼接salt,然后将得到的字符串进行一次sha256加密
is_super: 是否超级用户,但是这里我也有点蒙,没看到具体的定义,ai了一下,且看看吧

能力维度 超级用户 普通用户
MQTT 发布 / 订阅权限 无限制访问所有 Topic(绕过 ACL) 仅能访问 ACL 规则允许的 Topic
系统操作权限 可执行所有 CLI/API 管理操作 仅能执行授权范围内的操作(如仅查看自身信息)
权限校验 跳过所有 ACL/Topic 权限检查 严格校验 ACL/Topic 权限
管理功能访问 可登录 Dashboard、操作所有集群节点 仅能登录 Dashboard(若授权),操作受限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
{{- if .Values.authentication }}
apiVersion: v1
kind: ConfigMap
metadata:
name: emqx-config
namespace: {{ .Values.namespace }}
data:
emqx.conf: |-
node {
name = "emqx@127.0.0.1"
cookie = "emqxsecretcookie"
data_dir = "data"
}

cluster {
name = emqxcl
discovery_strategy = manual
}

log {
}

dashboard {
listeners.http {
bind = 18083
}
}

authentication {
backend = "built_in_database"
mechanism = "password_based"
password_hash_algorithm {
name = "sha256",
salt_position = "suffix"
}
user_id_type = "username"
bootstrap_file = "/opt/emqx/init_user.json"
bootstrap_type = hash
}
init_user.json: |-
[
{
"user_id":"sylink_emqx",
"password_hash":"ac5a34352a819d04a9d8c5c4e8964859b413b18e7067d0473cd45df6a33bc223",
"salt": "e378187547bf2d6f0545a3f441aa4d8a",
"is_superuser": true
}
]

{{- end }}

修改values.yaml

新增如下内容,用来控制是否开启认证

1
authentication: true

修改StatefulSet.yaml

修改了volume和volumes内容,挂载配置文件和初始化认证信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "emqx.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "emqx.name" . }}
helm.sh/chart: {{ include "emqx.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
serviceName: {{ include "emqx.fullname" . }}-headless
podManagementPolicy: {{ .Values.podManagementPolicy }}
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
volumeClaimTemplates:
- metadata:
name: emqx-data
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: {{ include "emqx.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
{{- if .Values.persistence.storageClassName }}
storageClassName: {{ .Values.persistence.storageClassName | quote }}
{{- end }}
accessModes:
- {{ .Values.persistence.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- end }}
updateStrategy:
type: RollingUpdate
{{- if .Values.minReadySeconds }}
minReadySeconds: {{ .Values.minReadySeconds }}
{{- end }}
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "emqx.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
annotations:
prometheus.io/path: /api/v5/prometheus/stats
prometheus.io/port: '18083'
prometheus.io/scrape: 'true'
labels:
app: {{ include "emqx.name" . }}
version: {{ .Chart.AppVersion }}
app.kubernetes.io/name: {{ include "emqx.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.recreatePods }}
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum | quote }}
{{- end }}
spec:
serviceAccountName: {{ include "emqx.serviceAccountName" . }}
{{- if .Values.priorityClassName }}
priorityClassName: {{ .Values.priorityClassName }}
{{- end }}
volumes:
{{- if .Values.ssl.enabled }}
- name: ssl-cert
secret:
secretName: {{ include "emqx.ssl.secretName" . }}
{{- end }}
{{- if not .Values.persistence.enabled }}
- name: emqx-data
emptyDir: {}
{{- else if .Values.persistence.existingClaim }}
- name: emqx-data
persistentVolumeClaim:
{{- with .Values.persistence.existingClaim }}
claimName: {{ tpl . $ }}
{{- end }}
{{- end }}
{{- if .Values.emqxLicenseSecretName }}
- name: emqx-license
secret:
secretName: {{ .Values.emqxLicenseSecretName }}
{{- end }}
{{- if .Values.authentication }}
- name: emqx-config
configMap:
name: emqx-config
- configMap:
defaultMode: 420
name: emqx-config
name: init-user
{{- end }}
{{- if .Values.extraVolumes }}
{{- toYaml .Values.extraVolumes | nindent 6 }}
{{- end }}
{{- if .Values.podSecurityContext.enabled }}
securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }}
{{- end }}
{{- if .Values.initContainers }}
initContainers:
{{- toYaml .Values.initContainers | nindent 8 }}
{{- end }}
{{- if .Values.image.pullSecrets }}
imagePullSecrets:
{{- range .Values.image.pullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
containers:
- name: emqx
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- if .Values.containerSecurityContext.enabled }}
securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }}
{{- end }}
ports:
- name: mqtt
containerPort: {{ splitList ":" ( .Values.emqxConfig.EMQX_LISTENERS__TCP__DEFAULT__BIND | default "1883" ) | last }}
- name: mqttssl
containerPort: {{ splitList ":" ( .Values.emqxConfig.EMQX_LISTENERS__SSL__DEFAULT__BIND | default "8883" ) | last }}
- name: ws
containerPort: {{ splitList ":" ( .Values.emqxConfig.EMQX_LISTENERS__WS__DEFAULT__BIND | default "8083" ) | last }}
- name: wss
containerPort: {{ splitList ":" ( .Values.emqxConfig.EMQX_LISTENERS__WSS__DEFAULT__BIND | default "8084" ) | last }}
- name: dashboard
containerPort: {{ splitList ":" ( .Values.emqxConfig.EMQX_DASHBOARD__LISTENERS__HTTP__BIND | default "18083" ) | last }}
{{- if not (empty .Values.emqxConfig.EMQX_DASHBOARD__LISTENERS__HTTPS__BIND) }}
- name: dashboardtls
containerPort: {{ splitList ":" .Values.emqxConfig.EMQX_DASHBOARD__LISTENERS__HTTPS__BIND | last }}
{{- end }}
- name: ekka
containerPort: 4370
- name: genrpc-manual
containerPort: 5369
envFrom:
- configMapRef:
name: {{ include "emqx.fullname" . }}-env
{{- if .Values.envFromSecret }}
- secretRef:
name: {{ .Values.envFromSecret }}
{{- end }}
resources:
{{ toYaml .Values.resources | indent 12 }}
volumeMounts:
- name: emqx-data
mountPath: "/opt/emqx/data"
{{- if .Values.ssl.enabled }}
- name: ssl-cert
mountPath: /tmp/ssl
readOnly: true
{{- end}}
{{ if .Values.emqxLicenseSecretName }}
- name: emqx-license
mountPath: "/opt/emqx/etc/emqx.lic"
subPath: "emqx.lic"
readOnly: true
{{- end }}
{{- if .Values.authentication }}
- name: emqx-config
mountPath: "/opt/emqx/etc/emqx.conf"
subPath: "emqx.conf"
readOnly: true
- name: init-user
mountPath: /opt/emqx/init_user.json
subPath: init_user.json
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- toYaml .Values.extraVolumeMounts | nindent 10 }}
{{- end }}
readinessProbe:
httpGet:
path: /status
port: {{ splitList ":" ( .Values.emqxConfig.EMQX_DASHBOARD__LISTENERS__HTTP__BIND | default "18083" ) | last }}
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 30
livenessProbe:
httpGet:
path: /status
port: {{ splitList ":" ( .Values.emqxConfig.EMQX_DASHBOARD__LISTENERS__HTTP__BIND | default "18083" ) | last }}
initialDelaySeconds: 60
periodSeconds: 30
failureThreshold: 10
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
{{- range . }}
- maxSkew: {{ .maxSkew }}
topologyKey: {{ .topologyKey }}
whenUnsatisfiable: {{ .whenUnsatisfiable }}
labelSelector:
matchLabels:
app.kubernetes.io/name: {{ include "emqx.name" $ }}
app.kubernetes.io/instance: {{ $.Release.Name }}
{{- if .minDomains }}
minDomains: {{ .minDomains }}
{{- end }}
{{- if .matchLabelKeys }}
matchLabelKeys:
{{- range .matchLabelKeys }}
- {{ . }}
{{- end }}
{{- end }}
{{- if .nodeAffinityPolicy }}
nodeAffinityPolicy: {{ .nodeAffinityPolicy }}
{{- end }}
{{- if .nodeTaintsPolicy }}
nodeTaintsPolicy: {{ .nodeTaintsPolicy }}
{{- end }}
{{- end }}
{{- end }}

参考
https://docs.emqx.com/zh/emqx/v5.9/access-control/authn/user_management.html
https://github.com/emqx/emqx/pull/13336