背景
上次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
新增如下内容,用来控制是否开启认证
修改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