Sin descripción

run-combined-stack.sh 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  4. export SOC_SHARED_NETWORK="${SOC_SHARED_NETWORK:-soc_shared}"
  5. export IRIS_HTTPS_PORT="${IRIS_HTTPS_PORT:-8443}"
  6. export INTERFACE_HTTPS_PORT="${IRIS_HTTPS_PORT}"
  7. export SHUFFLE_OPENSEARCH_PORT="${SHUFFLE_OPENSEARCH_PORT:-9201}"
  8. export PAGERDUTY_STUB_PORT="${PAGERDUTY_STUB_PORT:-18080}"
  9. export SOC_INTEGRATOR_PORT="${SOC_INTEGRATOR_PORT:-8088}"
  10. if [[ $# -eq 0 ]]; then
  11. COMMAND="up"
  12. ARGS=(-d)
  13. else
  14. COMMAND="$1"
  15. shift
  16. ARGS=("$@")
  17. fi
  18. if [[ "${COMMAND}" == "help" || "${COMMAND}" == "--help" || "${COMMAND}" == "-h" ]]; then
  19. cat <<'EOF'
  20. Usage:
  21. ./run-combined-stack.sh [command] [target] [options]
  22. Commands:
  23. up Start services (default: up -d when no args)
  24. recreate Force-recreate containers (picks up bind-mount inode changes)
  25. dedup Remove duplicate OpenSearch index patterns in Wazuh dashboard
  26. down Stop services (requires explicit target)
  27. logs View logs
  28. status Show container and endpoint status
  29. cleanup Remove unused Docker resources (containers/images/cache)
  30. help Show this help message
  31. Targets:
  32. all|--all All stacks (wazuh, iris, shuffle, pagerduty, integrator, flask-openapi-shuffle)
  33. wazuh Wazuh single-node stack
  34. iris IRIS-web stack
  35. shuffle Shuffle stack
  36. pagerduty PagerDuty stub
  37. integrator SOC integrator stack
  38. flask-openapi-shuffle Flask OpenAPI demo stack
  39. Examples:
  40. ./run-combined-stack.sh
  41. ./run-combined-stack.sh up --all -d
  42. ./run-combined-stack.sh up iris -d
  43. ./run-combined-stack.sh up flask-openapi-shuffle -d
  44. ./run-combined-stack.sh recreate wazuh
  45. ./run-combined-stack.sh recreate --all
  46. ./run-combined-stack.sh dedup
  47. ./run-combined-stack.sh down shuffle
  48. ./run-combined-stack.sh down --all
  49. ./run-combined-stack.sh logs integrator -f
  50. ./run-combined-stack.sh logs --all --tail 200
  51. ./run-combined-stack.sh cleanup
  52. ./run-combined-stack.sh cleanup --with-volumes
  53. ./run-combined-stack.sh status
  54. EOF
  55. exit 0
  56. fi
  57. if [[ "${COMMAND}" == "up" || "${COMMAND}" == "down" || "${COMMAND}" == "logs" ]]; then
  58. if ! docker network inspect "${SOC_SHARED_NETWORK}" >/dev/null 2>&1; then
  59. docker network create "${SOC_SHARED_NETWORK}" >/dev/null
  60. fi
  61. fi
  62. if [[ "${COMMAND}" == "status" ]]; then
  63. exec "${ROOT_DIR}/soc-status.sh"
  64. fi
  65. dedup_index_patterns() {
  66. local dashboard_url="https://localhost:443"
  67. local user="kibanaserver"
  68. local pass="kibanaserver"
  69. local max_wait=60
  70. local waited=0
  71. echo "Waiting for Wazuh dashboard to be ready..."
  72. until curl -sk -u "${user}:${pass}" "${dashboard_url}/api/status" -o /dev/null 2>&1; do
  73. sleep 3
  74. waited=$((waited + 3))
  75. if [[ ${waited} -ge ${max_wait} ]]; then
  76. echo "Dashboard not ready after ${max_wait}s — skipping dedup."
  77. return 1
  78. fi
  79. done
  80. echo "Scanning for duplicate index patterns..."
  81. python3 - <<PYEOF
  82. import json, sys, urllib.request, urllib.error, ssl
  83. BASE = "${dashboard_url}"
  84. AUTH = ("${user}", "${pass}")
  85. ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE
  86. def req(method, path, data=None):
  87. import base64
  88. token = base64.b64encode(f"{AUTH[0]}:{AUTH[1]}".encode()).decode()
  89. headers = {"osd-xsrf": "true", "Authorization": f"Basic {token}"}
  90. if data:
  91. headers["Content-Type"] = "application/json"
  92. r = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
  93. with urllib.request.urlopen(r, context=ctx, timeout=15) as resp:
  94. return json.loads(resp.read())
  95. # Fetch all index-pattern saved objects
  96. result = req("GET", "/api/saved_objects/_find?type=index-pattern&per_page=100")
  97. patterns = result.get("saved_objects", [])
  98. # Group by title
  99. from collections import defaultdict
  100. by_title = defaultdict(list)
  101. for p in patterns:
  102. by_title[p["attributes"]["title"]].append(p)
  103. deleted = 0
  104. for title, objs in by_title.items():
  105. if len(objs) <= 1:
  106. continue
  107. # Keep the one whose ID matches the title (canonical), or the oldest updated_at
  108. canonical = next((o for o in objs if o["id"] == title), None)
  109. if not canonical:
  110. canonical = sorted(objs, key=lambda o: o.get("updated_at", ""))[0]
  111. to_delete = [o for o in objs if o["id"] != canonical["id"]]
  112. for obj in to_delete:
  113. try:
  114. req("DELETE", f"/api/saved_objects/index-pattern/{obj['id']}")
  115. print(f" deleted [{obj['id']}] title='{title}'")
  116. deleted += 1
  117. except urllib.error.HTTPError as e:
  118. print(f" error deleting [{obj['id']}]: {e}")
  119. if deleted == 0:
  120. print(" no duplicates found.")
  121. else:
  122. print(f" removed {deleted} duplicate(s).")
  123. PYEOF
  124. }
  125. run_cleanup() {
  126. local with_volumes="${1:-false}"
  127. echo "Pruning stopped containers..."
  128. docker container prune -f
  129. echo "Pruning unused images..."
  130. docker image prune -a -f
  131. echo "Pruning builder cache..."
  132. docker builder prune -f
  133. if [[ "${with_volumes}" == "true" ]]; then
  134. echo "Pruning unused volumes..."
  135. docker volume prune -f
  136. fi
  137. echo "Current Docker disk usage:"
  138. docker system df
  139. }
  140. run_wazuh() {
  141. docker compose \
  142. --project-name wazuh-single \
  143. --project-directory "${ROOT_DIR}/wazuh-docker/single-node" \
  144. -f "${ROOT_DIR}/wazuh-docker/single-node/docker-compose.yml" \
  145. -f "${ROOT_DIR}/compose-overrides/wazuh.shared-network.yml" \
  146. "${COMMAND}" ${ARGS[@]+"${ARGS[@]}"}
  147. }
  148. run_iris() {
  149. docker compose \
  150. --project-name iris-web \
  151. --project-directory "${ROOT_DIR}/iris-web" \
  152. -f "${ROOT_DIR}/iris-web/docker-compose.dev.yml" \
  153. -f "${ROOT_DIR}/compose-overrides/iris.shared-network.yml" \
  154. "${COMMAND}" ${ARGS[@]+"${ARGS[@]}"}
  155. }
  156. run_shuffle() {
  157. docker compose \
  158. --project-name shuffle \
  159. --project-directory "${ROOT_DIR}/Shuffle" \
  160. -f "${ROOT_DIR}/Shuffle/docker-compose.yml" \
  161. -f "${ROOT_DIR}/compose-overrides/shuffle.shared-network.yml" \
  162. "${COMMAND}" ${ARGS[@]+"${ARGS[@]}"}
  163. }
  164. run_pagerduty_stub() {
  165. docker compose \
  166. --project-name pagerduty-stub \
  167. --project-directory "${ROOT_DIR}" \
  168. -f "${ROOT_DIR}/compose-overrides/pagerduty.stub.yml" \
  169. "${COMMAND}" ${ARGS[@]+"${ARGS[@]}"}
  170. }
  171. run_soc_integrator() {
  172. docker compose \
  173. --project-name soc-integrator \
  174. --project-directory "${ROOT_DIR}/compose-overrides" \
  175. -f "${ROOT_DIR}/compose-overrides/soc-integrator.yml" \
  176. "${COMMAND}" ${ARGS[@]+"${ARGS[@]}"}
  177. }
  178. run_flask_openapi_shuffle() {
  179. docker compose \
  180. --project-name flask-openapi-shuffle \
  181. --project-directory "${ROOT_DIR}/flask-openapi-shuffle" \
  182. -f "${ROOT_DIR}/flask-openapi-shuffle/docker-compose.yml" \
  183. "${COMMAND}" ${ARGS[@]+"${ARGS[@]}"}
  184. }
  185. run_target() {
  186. local target="$1"
  187. case "${target}" in
  188. wazuh)
  189. run_wazuh
  190. ;;
  191. iris)
  192. run_iris
  193. ;;
  194. shuffle)
  195. run_shuffle
  196. ;;
  197. pagerduty)
  198. run_pagerduty_stub
  199. ;;
  200. integrator)
  201. run_soc_integrator
  202. ;;
  203. flask-openapi-shuffle)
  204. run_flask_openapi_shuffle
  205. ;;
  206. *)
  207. echo "Unknown target: ${target}"
  208. echo "Use one of: wazuh, iris, shuffle, pagerduty, integrator, flask-openapi-shuffle"
  209. exit 1
  210. ;;
  211. esac
  212. }
  213. run_all() {
  214. local mode="$1"
  215. if [[ "${mode}" == "down" ]]; then
  216. run_flask_openapi_shuffle
  217. run_soc_integrator
  218. run_pagerduty_stub
  219. run_shuffle
  220. run_iris
  221. run_wazuh
  222. else
  223. run_wazuh
  224. run_iris
  225. run_shuffle
  226. run_pagerduty_stub
  227. run_soc_integrator
  228. run_flask_openapi_shuffle
  229. fi
  230. }
  231. follow_all_logs() {
  232. COMMAND="logs"
  233. ARGS=("-f" "--tail" "${LOG_TAIL:-100}")
  234. run_wazuh &
  235. run_iris &
  236. run_shuffle &
  237. run_pagerduty_stub &
  238. run_soc_integrator &
  239. run_flask_openapi_shuffle &
  240. trap 'kill 0' INT TERM
  241. wait
  242. }
  243. run_logs_for_target() {
  244. local target="${1:-all}"
  245. case "${target}" in
  246. wazuh)
  247. run_wazuh
  248. ;;
  249. iris)
  250. run_iris
  251. ;;
  252. shuffle)
  253. run_shuffle
  254. ;;
  255. pagerduty)
  256. run_pagerduty_stub
  257. ;;
  258. integrator)
  259. run_soc_integrator
  260. ;;
  261. flask-openapi-shuffle)
  262. run_flask_openapi_shuffle
  263. ;;
  264. all|--all)
  265. run_wazuh
  266. run_iris
  267. run_shuffle
  268. run_pagerduty_stub
  269. run_soc_integrator
  270. run_flask_openapi_shuffle
  271. ;;
  272. *)
  273. echo "Unknown logs target: ${target}"
  274. echo "Use one of: wazuh, iris, shuffle, pagerduty, integrator, flask-openapi-shuffle"
  275. exit 1
  276. ;;
  277. esac
  278. }
  279. if [[ "${COMMAND}" == "down" ]]; then
  280. TARGET="${1:-}"
  281. if [[ -z "${TARGET}" ]]; then
  282. echo "Refusing to run 'down' without a target."
  283. echo "Use one of:"
  284. echo " ./run-combined-stack.sh down <wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle>"
  285. echo " ./run-combined-stack.sh down --all"
  286. exit 1
  287. fi
  288. case "${TARGET}" in
  289. wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle)
  290. ARGS=("${ARGS[@]:1}")
  291. run_target "${TARGET}"
  292. ;;
  293. all|--all)
  294. ARGS=("${ARGS[@]:1}")
  295. run_all "down"
  296. ;;
  297. *)
  298. run_all "down"
  299. ;;
  300. esac
  301. elif [[ "${COMMAND}" == "logs" ]]; then
  302. LOGS_TARGET="${1:-all}"
  303. case "${LOGS_TARGET}" in
  304. wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle)
  305. ARGS=("${ARGS[@]:1}")
  306. run_logs_for_target "${LOGS_TARGET}"
  307. ;;
  308. all|--all)
  309. ARGS=("${ARGS[@]:1}")
  310. run_logs_for_target "all"
  311. ;;
  312. *)
  313. for arg in ${ARGS[@]+"${ARGS[@]}"}; do
  314. if [[ "${arg}" == "-f" || "${arg}" == "--follow" ]]; then
  315. echo "For follow mode, specify one target:"
  316. echo "./run-combined-stack.sh logs <wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle> -f"
  317. exit 1
  318. fi
  319. done
  320. run_logs_for_target "all"
  321. ;;
  322. esac
  323. elif [[ "${COMMAND}" == "up" ]]; then
  324. TARGET="${1:-all}"
  325. case "${TARGET}" in
  326. wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle)
  327. ARGS=("${ARGS[@]:1}")
  328. run_target "${TARGET}"
  329. ;;
  330. all|--all)
  331. ARGS=("${ARGS[@]:1}")
  332. HAS_DETACH="false"
  333. for arg in ${ARGS[@]+"${ARGS[@]}"}; do
  334. if [[ "${arg}" == "-d" || "${arg}" == "--detach" ]]; then
  335. HAS_DETACH="true"
  336. break
  337. fi
  338. done
  339. if [[ "${HAS_DETACH}" == "true" ]]; then
  340. run_all "up"
  341. else
  342. ARGS+=("-d")
  343. run_all "up"
  344. follow_all_logs
  345. fi
  346. ;;
  347. *)
  348. run_all "up"
  349. ;;
  350. esac
  351. elif [[ "${COMMAND}" == "recreate" ]]; then
  352. TARGET="${1:-all}"
  353. COMMAND="up"
  354. case "${TARGET}" in
  355. wazuh|iris|shuffle|pagerduty|integrator|flask-openapi-shuffle)
  356. ARGS=("--force-recreate" "-d")
  357. run_target "${TARGET}"
  358. ;;
  359. all|--all|*)
  360. ARGS=("--force-recreate" "-d")
  361. run_all "up"
  362. ;;
  363. esac
  364. elif [[ "${COMMAND}" == "cleanup" ]]; then
  365. WITH_VOLUMES="false"
  366. for arg in ${ARGS[@]+"${ARGS[@]}"}; do
  367. case "${arg}" in
  368. --with-volumes|-v)
  369. WITH_VOLUMES="true"
  370. ;;
  371. *)
  372. ;;
  373. esac
  374. done
  375. run_cleanup "${WITH_VOLUMES}"
  376. elif [[ "${COMMAND}" == "dedup" ]]; then
  377. dedup_index_patterns
  378. else
  379. run_all "up"
  380. fi