-
Notifications
You must be signed in to change notification settings - Fork 9
/
90.buildAcl.sh
executable file
·380 lines (325 loc) · 12.9 KB
/
90.buildAcl.sh
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
#! /usr/local/bin/bash
###
# Copyright DataStax, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###
###
# NOTE 1: the default MacOS /bin/bash version is 3.x and doesn't have the feature of
# associative arrary. Homebrew installed bash is under "/usr/local/bin/bash"
#
# Change to default "/bin/bash" if your system has the right version (4.x and above)
#
# This script is used for generating the Ansible host inventory file from
# the cluster topology raw definition file
#
# this script only works for bash 4 and above
# * by default, MacOs bash version is 3.x (/bin/bash)
# * use custom-installed bash using homebrew (/usar/local/bin/bash) at version 5.x
#
SCRIPT_FOLDER=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
ANS_SCRIPT_HOMEDIR=$( cd -- "${SCRIPT_FOLDER}/" &> /dev/null && pwd )
echo
DEBUG=false
bashVerCmdOut=$(bash --version)
re='[0-9].[0-9].[0-9]'
bashVersion=$(echo ${bashVerCmdOut} | grep -o "version ${re}" | grep -o "${re}")
bashVerMajor=$(echo ${bashVersion} | awk -F'.' '{print $1}' )
if [[ ${bashVerMajor} -lt 4 ]]; then
echo "[ERROR] Unspported bash version (${bashVersion}). Must be version 4.x and above!";
echo
exit 1
fi
# only 1 parameter: the message to print for debug purpose
debugMsg() {
if [[ "${DEBUG}" == "true" ]]; then
if [[ $# -eq 0 ]]; then
echo
else
echo "[Debug] $1"
fi
fi
}
aclRawDefHomeDir="${ANS_SCRIPT_HOMEDIR}/permission_matrix"
validAclOpTypeArr=("grant" "revoke")
validAclOpTypeListStr="${validAclOpTypeArr[@]}"
debugMsg "validAclOpTypeListStr=${validAclOpTypeListStr}"
validResourceTypeArr=("topic" "namespace" "ns-subscription" "tp-subscription")
validResourceTypeListStr="${validResourceTypeArr[@]}"
debugMsg "validResourceTypeListStr=${validResourceTypeListStr}"
validAclActionArr=("produce" "consume" "sources" "sinks" "functions" "packages")
validAclActionListStr="${validAclActionArr[@]}"
debugMsg "validAclActionListStr=${validAclActionListStr}"
usage() {
echo "Usage: buildAnsiHostInvFile.sh [-h]"
echo " -clstrName <cluster_name>"
echo " -aclDef <acl_definition_file>"
echo " [-skipRoleJwt] <skip_role_jwt_generation>"
echo " [-ansiPrivKey <ansi_private_key>"
echo " [-ansiSshUser <ansi_ssh_user>"
echo " -h : Show usage info"
echo " -clstrName : Pulsar cluster name"
echo " -aclDef : Pulsar ACL definition file"
echo " [-skipRoleJwt] : Whether to skip JWT token generation for the specified roles"
echo " [-ansiPrivKey] : The private SSH key file used to connect to Ansible hosts"
echo " [-ansiSshUser] : The SSH user used to connect to Ansible hosts"
}
if [[ $# -eq 0 || $# -gt 10 ]]; then
usage
echo
exit 10
fi
while [[ "$#" -gt 0 ]]; do
case $1 in
-h) usage; echo; exit 0 ;;
-clstrName) clstrName=$2; shift ;;
-aclDef) aclDefFileName=$2; shift ;;
-skipRoleJwt) skipRoleJwt=$(echo $2 | tr '[:upper:]' '[:lower:]'); shift ;;
-ansiPrivKey) ansiPrivKey=$2; shift ;;
-ansiSshUser) ansiSshUser=$2; shift ;;
*) echo "[ERROR] Unknown parameter passed: $1"; echo; exit 20 ;;
esac
shift
done
ANSI_HOSTINV_FILE="hosts_${clstrName}.ini"
aclDefExecLogHomeDir="${aclRawDefHomeDir}/${clstrName}/logs"
mkdir -p "${aclDefExecLogHomeDir}/acl_perm_exec_log"
aclDefFilePath="${aclRawDefHomeDir}/${clstrName}/${aclDefFileName}"
if [[ -z "${ansiPrivKey// }" ]]; then
ansiPrivKey=${ANSI_SSH_PRIV_KEY}
fi
if [[ -z "${ansiSshUser// }" ]]; then
ansiSshUser=${ANSI_SSH_USER}
fi
if [[ -z "${ansiPrivKey// }" || -z "${ansiSshUser// }" ]]; then
echo "[ERROR] Missing mandatory SSH key and user name which can be either explicitly provided via '-ansiPrivKey' and '-ansiSshUser' or set in `setenv_automation.sh` file!"
echo
exit 25
fi
debugMsg "aclDefFilePath=${aclDefFilePath}"
debugMsg "ansiPrivKey=${ansiPrivKey}"
debugMsg "ansiSshUser=${ansiSshUser}"
# Check if the corrsponding Pulsar cluster definition file exists
if ! [[ -f "${aclDefFilePath}" ]]; then
echo "[ERROR] Can't find the specified ACL raw definition file of the specific Pulsar cluster: ${aclDefFilePath}";
echo
exit 30
fi
re='(true|false)'
if [[ -z "${skipRoleJwt// }" ]]; then
skipRoleJwt="false"
fi
if ! [[ ${skipRoleJwt} =~ $re ]]; then
echo "[ERROR] Invalid value for the following input parameter of '-skipRoleJwt'. Value 'true' or 'false' is expected."
echo
exit 40
fi
##
# Check if an element is contained in an arrary
# - 1st parameter: the element to match
# - 2nd parameter: the array
containsElementInArr () {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
stepCnt=0
stepCnt=$((stepCnt+1))
echo "${stepCnt}. Process the specified raw ACL definition file for validity check: ${aclDefFilePath} ..."
roleNameArr=()
uniqueRoleNameArr=()
aclOpArr=()
resourceTypeArr=()
resourceNameArr=()
aclActionListStrArr=()
while read LINE || [ -n "${LINE}" ]; do
# Ignore comments
case "${LINE}" in \#*) continue ;; esac
IFS=',' read -r -a FIELDS <<< "${LINE#/}"
roleName=${FIELDS[0]}
aclOp=${FIELDS[1]}
resourceType=${FIELDS[2]}
resourceName=${FIELDS[3]}
aclActionListStr=${FIELDS[4]}
debugMsg "roleName=${roleName}"
debugMsg "aclOp=$(echo ${aclOp} | tr '[:upper:]' '[:lower:]')"
debugMsg "resourceType=$(echo ${resourceType} | tr '[:upper:]' '[:lower:]')"
debugMsg "resourceName=${resourceName}"
debugMsg "aclActionListStr=$(echo ${aclActionListStr} | tr '[:upper:]' '[:lower:]')"
# General validity test
if [[ -z "${roleName// }"|| -z "${aclOp// }" || -z "${resourceType// }" || -z "${resourceName// }" ]]; then
echo "[ERROR] Invalid ACL defintion line: \"${LINE}\". All fields (except 'aclOption') must not be empty!"
echo
exit 50
elif ! [[ "${validAclOpTypeListStr}" =~ "${aclOp}" ]]; then
echo "[ERROR] Invalid ACL operation type '${aclOp}' on line \"${LINE}\". Valid types: ${validAclOpTypeListStr}"
echo
exit 60
elif ! [[ "${validResourceTypeListStr}" =~ "${resourceType}" ]]; then
echo "[ERROR] Invalid ACL resouce type '${resourceType}' on line \"${LINE}\". Valid types: ${validResourceTypeListStr}"
echo
exit 70
else
IFS='+' read -r -a aclLineActionArr <<< "${aclActionListStr}"
for aclAction in ${aclLineActionArr[@]} ; do
if ! [[ "${validAclActionListStr}" =~ "${aclAction}" ]]; then
echo "[ERROR] Invalid ACL action type '${aclAction}' on line \"${LINE}\". Valid types: ${validAclActionListStr}"
echo
exit 80
fi
done
fi
# Resource type specific validity check
tp_re="^(persistent|non-persistent)://[[:alnum:]_.-]+/[[:alnum:]_.-]+/[[:alnum:]_.-]+$"
ns_re="^[[:alnum:]_.-]+/[[:alnum:]_.-]+$"
ns_sb_re="^[[:alnum:]_.-]+/[[:alnum:]_.-]+:[[:alnum:]_.-]+$"
if [[ "${resourceType}" == "topic" ]]; then
if ! [[ "${resourceName}" =~ ${tp_re} ]]; then
echo "[ERROR] Invalid resource name pattern ('${resourceName}') for the specified resource type '${resourceType}' on line \"${LINE}\". Expecting name pattern: \"${tp_re}\"!"
echo
exit 90
fi
elif [[ "${resourceType}" == "namespace" ]]; then
if ! [[ "${resourceName}" =~ ${ns_re} ]]; then
echo "[ERROR] Invalid resource name pattern ('${resourceName}') for the specified resource type '${resourceType}' on line \"${LINE}\". Expecting name pattern: \"${ns_re}\"!"
echo
exit 100
fi
elif [[ "${resourceType}" == "ns-subscription" ]]; then
if ! [[ "${resourceName}" =~ ${ns_sb_re} ]]; then
echo "[ERROR] Invalid resource name pattern ('${resourceName}') for the specified resource type '${resourceType}' on line \"${LINE}\". Expecting name pattern: \"${ns_sb_re}\"!"
echo
exit 110
fi
fi
# aclActionListStr can be empty for 'subscription' resource
if [[ -z "${aclActionListStr// }" ]]; then
aclActionListStr="n/a"
fi
roleNameArr+=("${roleName}")
containsElementInArr "${roleName}" "${uniqueRoleNameArr[@]}"
if [[ $? -eq 1 ]]; then
uniqueRoleNameArr+=("${roleName}")
fi
aclOpArr+=("${aclOp}")
resourceTypeArr+=("${resourceType}")
aclActionListStrArr+=("${aclActionListStr}")
resourceNameArr+=("${resourceName}")
done < ${aclDefFilePath}
roleNameList="${roleNameArr[@]}"
uniqueRoleNameList="${uniqueRoleNameArr[@]}"
aclOpList="${aclOpArr[@]}"
resourceTypeList="${resourceTypeArr[@]}"
aclActionListStrList="${aclActionListStrArr[@]}"
debugMsg "roleNameList=${roleNameList}"
debugMsg "uniqueRoleNameList=${uniqueRoleNameList}"
debugMsg "aclOpList=${aclOpList}"
debugMsg "resourceTypeList=${resourceTypeList}"
debugMsg "aclActionListStrArr=${aclActionListStrList}"
echo " done!"
if [[ "${skipRoleJwt}" == "false" ]]; then
echo
stepCnt=$((stepCnt+1))
ansiPlaybookName="01.create_secFiles.yaml"
ANSI_HOSTINV_FILE="hosts_${clstrName}.ini"
ansiTcExecLog="${aclDefExecLogHomeDir}/${aclDefFileName}-create_secFiles.yaml.log"
echo "${stepCnt}. Call Ansible script to create JWT tokens for all roles specified in the ACL raw definition file"
echo " execution log file: ${ansiTcExecLog}"
ansible-playbook -i ${ANSI_HOSTINV_FILE} ${ansiPlaybookName} \
--extra-vars="cleanLocalSecStaging=false user_roles_list=${uniqueRoleNameList// /,} jwtTokenOnly=true brokerOnly=true" \
--private-key=${ansiPrivKey} \
-u ${ansiSshUser} -v > ${ansiTcExecLog} 2>&1
if [[ $? -ne 0 ]]; then
echo " [ERROR] Failed to create specifie JWT tokens for the specified roles!"
echo
exit 120
else
echo " done!"
fi
echo
fi
echo
stepCnt=$((stepCnt+1))
echo "${stepCnt}. Generate pulsar-amdin command template file to grant/revoke permissions according to the ACL definition."
pulsarCliAclExecTemplFile="${aclRawDefHomeDir}/${clstrName}/${aclDefFileName}_pulsarCliCmdTmpl"
echo "#! /bin/bsh" > ${pulsarCliAclExecTemplFile}
echo >> ${pulsarCliAclExecTemplFile}
echo "aclIndex=0" >> ${pulsarCliAclExecTemplFile}
echo >> ${pulsarCliAclExecTemplFile}
for index in "${!roleNameArr[@]}"; do
roleName=${roleNameArr[$index]}
aclOp=${aclOpArr[$index]}
resourceType=${resourceTypeArr[$index]}
resourceName=${resourceNameArr[$index]}
# convert to the format recogonized by pulsar-admin command
aclActionListStr=$(echo ${aclActionListStrArr[$index]} | tr '+' ',' )
if [[ "${resourceType}" == "namespace" ]]; then
adminCmd="namespaces"
adminSubCmd="${aclOp}-permission"
elif [[ "${resourceType}" == "topic" ]]; then
adminCmd="topics"
adminSubCmd="${aclOp}-permission"
elif [[ "${resourceType}" == "ns-subscription" ]]; then
adminCmd="namespaces"
adminSubCmd="${aclOp}-subscription-permission"
#
## future work: topic subscription
#
# elif [[ "${resourceType}" == "ns-subscription" ]]; then
# adminCmd="namespaces"
# adminSubCmd="${aclOp}-subscription-permission"
fi
pulsarAdminCmdStrToExec="<PULSAR_ADMIN_CMD> ${adminCmd} ${adminSubCmd}"
if [[ "${resourceType}" == "ns-subscription" ]]; then
# for "ns-subscription", the resource name is in format "<tenant>/<namespace>:<subscription>"
IFS=':' read -r -a nsSubArr <<< "${resourceName}"
pulsarAdminCmdStrToExec="${pulsarAdminCmdStrToExec} ${nsSubArr[0]} --subscription ${nsSubArr[1]} --roles ${roleName}"
#
## future work: topic subscription
#
# elif [[ "${resourceType}" == "ns-subscription" ]]; then
# pulsarAdminCmdStrToExec="TBD ..."
else
pulsarAdminCmdStrToExec="${pulsarAdminCmdStrToExec} ${resourceName} --role ${roleName}"
if [[ "${aclOp}" == "grant" ]]; then
pulsarAdminCmdStrToExec="${pulsarAdminCmdStrToExec} --actions ${aclActionListStr}"
fi
fi
echo 'aclIndex=$((aclIndex+1))' >> ${pulsarCliAclExecTemplFile}
echo "${pulsarAdminCmdStrToExec}" >> ${pulsarCliAclExecTemplFile}
echo 'if [[ $? -ne 0 ]]; then' >> ${pulsarCliAclExecTemplFile}
echo ' exit ${aclIndex}' >> ${pulsarCliAclExecTemplFile}
echo 'fi' >> ${pulsarCliAclExecTemplFile}
echo >> ${pulsarCliAclExecTemplFile}
done
echo 'exit 0' >> ${pulsarCliAclExecTemplFile}
echo " done!"
echo
stepCnt=$((stepCnt+1))
ansiPlaybookName="exec_AclPermControl.yaml"
ansiTcExecLog="${aclDefExecLogHomeDir}/${aclDefFileName}-exec_AclPermControl.yaml.log"
echo "${stepCnt}. Call Ansible script to execute Pulsar ACL permission management commands"
echo " execution log file: ${ansiTcExecLog}"
ansible-playbook -i ${ANSI_HOSTINV_FILE} ${ansiPlaybookName} \
--extra-vars="aclDefRawName=${aclDefFileName}" \
--private-key=${ansiPrivKey} \
-u ${ansiSshUser} -v > ${ansiTcExecLog} 2>&1
if [[ $? -ne 0 ]]; then
echo " [ERROR] Not all Pulsar ACL permission management commands are executed successfully! Please check the remote execute log!"
echo
exit 120
else
echo " done!"
fi
echo