├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-MIT ├── Makefile ├── README.md ├── build.rs ├── rustfmt.toml └── src ├── actions.rs ├── config.rs ├── container.rs ├── context.rs ├── logs.rs ├── main.rs ├── menu ├── advanced.rs ├── configure.rs ├── evebox.rs ├── mod.rs ├── suricata.rs └── suricata_update.rs ├── menus.rs ├── prelude.rs ├── ruleindex.rs ├── selfupdate.rs └── term.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | target: 14 | - arm-unknown-linux-musleabihf 15 | - x86_64-unknown-linux-musl 16 | - aarch64-unknown-linux-musl 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | - uses: actions-rs/cargo@v1 23 | name: Test 24 | with: 25 | use-cross: true 26 | command: test 27 | args: --target=${{ matrix.target }} 28 | if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }} 29 | - uses: actions-rs/cargo@v1 30 | name: Build 31 | with: 32 | use-cross: true 33 | command: build 34 | args: --release --target=${{ matrix.target }} 35 | - run: find target -name easy 36 | - uses: actions/upload-artifact@v4 37 | with: 38 | name: ${{ matrix.target }}-easy 39 | path: target/${{ matrix.target }}/release/easy 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea 3 | *~ 4 | /data 5 | /simple-ids.yml 6 | /simple-ids.toml 7 | /*.conf 8 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys 0.59.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 73 | dependencies = [ 74 | "anstyle", 75 | "once_cell", 76 | "windows-sys 0.59.0", 77 | ] 78 | 79 | [[package]] 80 | name = "anyhow" 81 | version = "1.0.95" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" 84 | 85 | [[package]] 86 | name = "autocfg" 87 | version = "1.4.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 90 | 91 | [[package]] 92 | name = "backtrace" 93 | version = "0.3.74" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 96 | dependencies = [ 97 | "addr2line", 98 | "cfg-if", 99 | "libc", 100 | "miniz_oxide", 101 | "object", 102 | "rustc-demangle", 103 | "windows-targets 0.52.6", 104 | ] 105 | 106 | [[package]] 107 | name = "base64" 108 | version = "0.22.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 111 | 112 | [[package]] 113 | name = "bitflags" 114 | version = "1.3.2" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 117 | 118 | [[package]] 119 | name = "bitflags" 120 | version = "2.8.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 123 | 124 | [[package]] 125 | name = "block-buffer" 126 | version = "0.10.4" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 129 | dependencies = [ 130 | "generic-array", 131 | ] 132 | 133 | [[package]] 134 | name = "bumpalo" 135 | version = "3.17.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 138 | 139 | [[package]] 140 | name = "byteorder" 141 | version = "1.5.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 144 | 145 | [[package]] 146 | name = "bytes" 147 | version = "1.10.0" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" 150 | 151 | [[package]] 152 | name = "cc" 153 | version = "1.2.13" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" 156 | dependencies = [ 157 | "shlex", 158 | ] 159 | 160 | [[package]] 161 | name = "cfg-if" 162 | version = "1.0.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 165 | 166 | [[package]] 167 | name = "cfg_aliases" 168 | version = "0.2.1" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 171 | 172 | [[package]] 173 | name = "clap" 174 | version = "4.5.28" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" 177 | dependencies = [ 178 | "clap_builder", 179 | "clap_derive", 180 | ] 181 | 182 | [[package]] 183 | name = "clap_builder" 184 | version = "4.5.27" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" 187 | dependencies = [ 188 | "anstream", 189 | "anstyle", 190 | "clap_lex", 191 | "strsim", 192 | ] 193 | 194 | [[package]] 195 | name = "clap_derive" 196 | version = "4.5.28" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" 199 | dependencies = [ 200 | "heck", 201 | "proc-macro2", 202 | "quote", 203 | "syn", 204 | ] 205 | 206 | [[package]] 207 | name = "clap_lex" 208 | version = "0.7.4" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 211 | 212 | [[package]] 213 | name = "colorchoice" 214 | version = "1.0.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 217 | 218 | [[package]] 219 | name = "colored" 220 | version = "3.0.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" 223 | dependencies = [ 224 | "windows-sys 0.59.0", 225 | ] 226 | 227 | [[package]] 228 | name = "cpufeatures" 229 | version = "0.2.17" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 232 | dependencies = [ 233 | "libc", 234 | ] 235 | 236 | [[package]] 237 | name = "crossterm" 238 | version = "0.25.0" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" 241 | dependencies = [ 242 | "bitflags 1.3.2", 243 | "crossterm_winapi", 244 | "libc", 245 | "mio 0.8.11", 246 | "parking_lot", 247 | "signal-hook", 248 | "signal-hook-mio", 249 | "winapi", 250 | ] 251 | 252 | [[package]] 253 | name = "crossterm" 254 | version = "0.28.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 257 | dependencies = [ 258 | "bitflags 2.8.0", 259 | "crossterm_winapi", 260 | "mio 1.0.3", 261 | "parking_lot", 262 | "rustix", 263 | "signal-hook", 264 | "signal-hook-mio", 265 | "winapi", 266 | ] 267 | 268 | [[package]] 269 | name = "crossterm_winapi" 270 | version = "0.9.1" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 273 | dependencies = [ 274 | "winapi", 275 | ] 276 | 277 | [[package]] 278 | name = "crypto-common" 279 | version = "0.1.6" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 282 | dependencies = [ 283 | "generic-array", 284 | "typenum", 285 | ] 286 | 287 | [[package]] 288 | name = "ctrlc" 289 | version = "3.4.5" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" 292 | dependencies = [ 293 | "nix", 294 | "windows-sys 0.59.0", 295 | ] 296 | 297 | [[package]] 298 | name = "digest" 299 | version = "0.10.7" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 302 | dependencies = [ 303 | "block-buffer", 304 | "crypto-common", 305 | ] 306 | 307 | [[package]] 308 | name = "dirs" 309 | version = "6.0.0" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 312 | dependencies = [ 313 | "dirs-sys", 314 | ] 315 | 316 | [[package]] 317 | name = "dirs-sys" 318 | version = "0.5.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 321 | dependencies = [ 322 | "libc", 323 | "option-ext", 324 | "redox_users", 325 | "windows-sys 0.59.0", 326 | ] 327 | 328 | [[package]] 329 | name = "displaydoc" 330 | version = "0.2.5" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 333 | dependencies = [ 334 | "proc-macro2", 335 | "quote", 336 | "syn", 337 | ] 338 | 339 | [[package]] 340 | name = "dyn-clone" 341 | version = "1.0.18" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" 344 | 345 | [[package]] 346 | name = "equivalent" 347 | version = "1.0.1" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 350 | 351 | [[package]] 352 | name = "errno" 353 | version = "0.3.10" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 356 | dependencies = [ 357 | "libc", 358 | "windows-sys 0.59.0", 359 | ] 360 | 361 | [[package]] 362 | name = "evectl" 363 | version = "0.1.0-alpha.7" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "56ef94c8c5d42bb3bb23fe8eb5d150fe9e91babb2f9b2eb2289d0cbd2aef8521" 366 | dependencies = [ 367 | "anyhow", 368 | "clap", 369 | "colored", 370 | "crossterm 0.28.1", 371 | "ctrlc", 372 | "dirs", 373 | "inquire", 374 | "libc", 375 | "regex", 376 | "reqwest", 377 | "semver", 378 | "serde", 379 | "serde_json", 380 | "serde_yaml", 381 | "sha2", 382 | "tempfile", 383 | "toml", 384 | "tracing", 385 | "tracing-subscriber", 386 | ] 387 | 388 | [[package]] 389 | name = "fastrand" 390 | version = "2.3.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 393 | 394 | [[package]] 395 | name = "fnv" 396 | version = "1.0.7" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 399 | 400 | [[package]] 401 | name = "form_urlencoded" 402 | version = "1.2.1" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 405 | dependencies = [ 406 | "percent-encoding", 407 | ] 408 | 409 | [[package]] 410 | name = "futures-channel" 411 | version = "0.3.31" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 414 | dependencies = [ 415 | "futures-core", 416 | "futures-sink", 417 | ] 418 | 419 | [[package]] 420 | name = "futures-core" 421 | version = "0.3.31" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 424 | 425 | [[package]] 426 | name = "futures-io" 427 | version = "0.3.31" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 430 | 431 | [[package]] 432 | name = "futures-sink" 433 | version = "0.3.31" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 436 | 437 | [[package]] 438 | name = "futures-task" 439 | version = "0.3.31" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 442 | 443 | [[package]] 444 | name = "futures-util" 445 | version = "0.3.31" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 448 | dependencies = [ 449 | "futures-core", 450 | "futures-io", 451 | "futures-sink", 452 | "futures-task", 453 | "memchr", 454 | "pin-project-lite", 455 | "pin-utils", 456 | "slab", 457 | ] 458 | 459 | [[package]] 460 | name = "fuzzy-matcher" 461 | version = "0.3.7" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 464 | dependencies = [ 465 | "thread_local", 466 | ] 467 | 468 | [[package]] 469 | name = "fxhash" 470 | version = "0.2.1" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 473 | dependencies = [ 474 | "byteorder", 475 | ] 476 | 477 | [[package]] 478 | name = "generic-array" 479 | version = "0.14.7" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 482 | dependencies = [ 483 | "typenum", 484 | "version_check", 485 | ] 486 | 487 | [[package]] 488 | name = "getrandom" 489 | version = "0.2.15" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 492 | dependencies = [ 493 | "cfg-if", 494 | "js-sys", 495 | "libc", 496 | "wasi 0.11.0+wasi-snapshot-preview1", 497 | "wasm-bindgen", 498 | ] 499 | 500 | [[package]] 501 | name = "getrandom" 502 | version = "0.3.1" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 505 | dependencies = [ 506 | "cfg-if", 507 | "libc", 508 | "wasi 0.13.3+wasi-0.2.2", 509 | "windows-targets 0.52.6", 510 | ] 511 | 512 | [[package]] 513 | name = "gimli" 514 | version = "0.31.1" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 517 | 518 | [[package]] 519 | name = "hashbrown" 520 | version = "0.15.2" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 523 | 524 | [[package]] 525 | name = "heck" 526 | version = "0.5.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 529 | 530 | [[package]] 531 | name = "http" 532 | version = "1.2.0" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 535 | dependencies = [ 536 | "bytes", 537 | "fnv", 538 | "itoa", 539 | ] 540 | 541 | [[package]] 542 | name = "http-body" 543 | version = "1.0.1" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 546 | dependencies = [ 547 | "bytes", 548 | "http", 549 | ] 550 | 551 | [[package]] 552 | name = "http-body-util" 553 | version = "0.1.2" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 556 | dependencies = [ 557 | "bytes", 558 | "futures-util", 559 | "http", 560 | "http-body", 561 | "pin-project-lite", 562 | ] 563 | 564 | [[package]] 565 | name = "httparse" 566 | version = "1.10.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" 569 | 570 | [[package]] 571 | name = "hyper" 572 | version = "1.6.0" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 575 | dependencies = [ 576 | "bytes", 577 | "futures-channel", 578 | "futures-util", 579 | "http", 580 | "http-body", 581 | "httparse", 582 | "itoa", 583 | "pin-project-lite", 584 | "smallvec", 585 | "tokio", 586 | "want", 587 | ] 588 | 589 | [[package]] 590 | name = "hyper-rustls" 591 | version = "0.27.5" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" 594 | dependencies = [ 595 | "futures-util", 596 | "http", 597 | "hyper", 598 | "hyper-util", 599 | "rustls", 600 | "rustls-pki-types", 601 | "tokio", 602 | "tokio-rustls", 603 | "tower-service", 604 | "webpki-roots", 605 | ] 606 | 607 | [[package]] 608 | name = "hyper-util" 609 | version = "0.1.10" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 612 | dependencies = [ 613 | "bytes", 614 | "futures-channel", 615 | "futures-util", 616 | "http", 617 | "http-body", 618 | "hyper", 619 | "pin-project-lite", 620 | "socket2", 621 | "tokio", 622 | "tower-service", 623 | "tracing", 624 | ] 625 | 626 | [[package]] 627 | name = "icu_collections" 628 | version = "1.5.0" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 631 | dependencies = [ 632 | "displaydoc", 633 | "yoke", 634 | "zerofrom", 635 | "zerovec", 636 | ] 637 | 638 | [[package]] 639 | name = "icu_locid" 640 | version = "1.5.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 643 | dependencies = [ 644 | "displaydoc", 645 | "litemap", 646 | "tinystr", 647 | "writeable", 648 | "zerovec", 649 | ] 650 | 651 | [[package]] 652 | name = "icu_locid_transform" 653 | version = "1.5.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 656 | dependencies = [ 657 | "displaydoc", 658 | "icu_locid", 659 | "icu_locid_transform_data", 660 | "icu_provider", 661 | "tinystr", 662 | "zerovec", 663 | ] 664 | 665 | [[package]] 666 | name = "icu_locid_transform_data" 667 | version = "1.5.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 670 | 671 | [[package]] 672 | name = "icu_normalizer" 673 | version = "1.5.0" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 676 | dependencies = [ 677 | "displaydoc", 678 | "icu_collections", 679 | "icu_normalizer_data", 680 | "icu_properties", 681 | "icu_provider", 682 | "smallvec", 683 | "utf16_iter", 684 | "utf8_iter", 685 | "write16", 686 | "zerovec", 687 | ] 688 | 689 | [[package]] 690 | name = "icu_normalizer_data" 691 | version = "1.5.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 694 | 695 | [[package]] 696 | name = "icu_properties" 697 | version = "1.5.1" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 700 | dependencies = [ 701 | "displaydoc", 702 | "icu_collections", 703 | "icu_locid_transform", 704 | "icu_properties_data", 705 | "icu_provider", 706 | "tinystr", 707 | "zerovec", 708 | ] 709 | 710 | [[package]] 711 | name = "icu_properties_data" 712 | version = "1.5.0" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 715 | 716 | [[package]] 717 | name = "icu_provider" 718 | version = "1.5.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 721 | dependencies = [ 722 | "displaydoc", 723 | "icu_locid", 724 | "icu_provider_macros", 725 | "stable_deref_trait", 726 | "tinystr", 727 | "writeable", 728 | "yoke", 729 | "zerofrom", 730 | "zerovec", 731 | ] 732 | 733 | [[package]] 734 | name = "icu_provider_macros" 735 | version = "1.5.0" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 738 | dependencies = [ 739 | "proc-macro2", 740 | "quote", 741 | "syn", 742 | ] 743 | 744 | [[package]] 745 | name = "idna" 746 | version = "1.0.3" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 749 | dependencies = [ 750 | "idna_adapter", 751 | "smallvec", 752 | "utf8_iter", 753 | ] 754 | 755 | [[package]] 756 | name = "idna_adapter" 757 | version = "1.2.0" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 760 | dependencies = [ 761 | "icu_normalizer", 762 | "icu_properties", 763 | ] 764 | 765 | [[package]] 766 | name = "indexmap" 767 | version = "2.7.1" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 770 | dependencies = [ 771 | "equivalent", 772 | "hashbrown", 773 | ] 774 | 775 | [[package]] 776 | name = "inquire" 777 | version = "0.7.5" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" 780 | dependencies = [ 781 | "bitflags 2.8.0", 782 | "crossterm 0.25.0", 783 | "dyn-clone", 784 | "fuzzy-matcher", 785 | "fxhash", 786 | "newline-converter", 787 | "once_cell", 788 | "unicode-segmentation", 789 | "unicode-width", 790 | ] 791 | 792 | [[package]] 793 | name = "ipnet" 794 | version = "2.11.0" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 797 | 798 | [[package]] 799 | name = "is_terminal_polyfill" 800 | version = "1.70.1" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 803 | 804 | [[package]] 805 | name = "itoa" 806 | version = "1.0.14" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 809 | 810 | [[package]] 811 | name = "js-sys" 812 | version = "0.3.77" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 815 | dependencies = [ 816 | "once_cell", 817 | "wasm-bindgen", 818 | ] 819 | 820 | [[package]] 821 | name = "lazy_static" 822 | version = "1.5.0" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 825 | 826 | [[package]] 827 | name = "libc" 828 | version = "0.2.169" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 831 | 832 | [[package]] 833 | name = "libredox" 834 | version = "0.1.3" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 837 | dependencies = [ 838 | "bitflags 2.8.0", 839 | "libc", 840 | ] 841 | 842 | [[package]] 843 | name = "linux-raw-sys" 844 | version = "0.4.15" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 847 | 848 | [[package]] 849 | name = "litemap" 850 | version = "0.7.4" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 853 | 854 | [[package]] 855 | name = "lock_api" 856 | version = "0.4.12" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 859 | dependencies = [ 860 | "autocfg", 861 | "scopeguard", 862 | ] 863 | 864 | [[package]] 865 | name = "log" 866 | version = "0.4.25" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 869 | 870 | [[package]] 871 | name = "memchr" 872 | version = "2.7.4" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 875 | 876 | [[package]] 877 | name = "mime" 878 | version = "0.3.17" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 881 | 882 | [[package]] 883 | name = "miniz_oxide" 884 | version = "0.8.3" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" 887 | dependencies = [ 888 | "adler2", 889 | ] 890 | 891 | [[package]] 892 | name = "mio" 893 | version = "0.8.11" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 896 | dependencies = [ 897 | "libc", 898 | "log", 899 | "wasi 0.11.0+wasi-snapshot-preview1", 900 | "windows-sys 0.48.0", 901 | ] 902 | 903 | [[package]] 904 | name = "mio" 905 | version = "1.0.3" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 908 | dependencies = [ 909 | "libc", 910 | "log", 911 | "wasi 0.11.0+wasi-snapshot-preview1", 912 | "windows-sys 0.52.0", 913 | ] 914 | 915 | [[package]] 916 | name = "newline-converter" 917 | version = "0.3.0" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" 920 | dependencies = [ 921 | "unicode-segmentation", 922 | ] 923 | 924 | [[package]] 925 | name = "nix" 926 | version = "0.29.0" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 929 | dependencies = [ 930 | "bitflags 2.8.0", 931 | "cfg-if", 932 | "cfg_aliases", 933 | "libc", 934 | ] 935 | 936 | [[package]] 937 | name = "nu-ansi-term" 938 | version = "0.46.0" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 941 | dependencies = [ 942 | "overload", 943 | "winapi", 944 | ] 945 | 946 | [[package]] 947 | name = "object" 948 | version = "0.36.7" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 951 | dependencies = [ 952 | "memchr", 953 | ] 954 | 955 | [[package]] 956 | name = "once_cell" 957 | version = "1.20.3" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 960 | 961 | [[package]] 962 | name = "option-ext" 963 | version = "0.2.0" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 966 | 967 | [[package]] 968 | name = "overload" 969 | version = "0.1.1" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 972 | 973 | [[package]] 974 | name = "parking_lot" 975 | version = "0.12.3" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 978 | dependencies = [ 979 | "lock_api", 980 | "parking_lot_core", 981 | ] 982 | 983 | [[package]] 984 | name = "parking_lot_core" 985 | version = "0.9.10" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 988 | dependencies = [ 989 | "cfg-if", 990 | "libc", 991 | "redox_syscall", 992 | "smallvec", 993 | "windows-targets 0.52.6", 994 | ] 995 | 996 | [[package]] 997 | name = "percent-encoding" 998 | version = "2.3.1" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1001 | 1002 | [[package]] 1003 | name = "pin-project-lite" 1004 | version = "0.2.16" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1007 | 1008 | [[package]] 1009 | name = "pin-utils" 1010 | version = "0.1.0" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1013 | 1014 | [[package]] 1015 | name = "ppv-lite86" 1016 | version = "0.2.20" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1019 | dependencies = [ 1020 | "zerocopy", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "proc-macro2" 1025 | version = "1.0.93" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 1028 | dependencies = [ 1029 | "unicode-ident", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "quinn" 1034 | version = "0.11.6" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" 1037 | dependencies = [ 1038 | "bytes", 1039 | "pin-project-lite", 1040 | "quinn-proto", 1041 | "quinn-udp", 1042 | "rustc-hash", 1043 | "rustls", 1044 | "socket2", 1045 | "thiserror", 1046 | "tokio", 1047 | "tracing", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "quinn-proto" 1052 | version = "0.11.9" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" 1055 | dependencies = [ 1056 | "bytes", 1057 | "getrandom 0.2.15", 1058 | "rand", 1059 | "ring", 1060 | "rustc-hash", 1061 | "rustls", 1062 | "rustls-pki-types", 1063 | "slab", 1064 | "thiserror", 1065 | "tinyvec", 1066 | "tracing", 1067 | "web-time", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "quinn-udp" 1072 | version = "0.5.9" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" 1075 | dependencies = [ 1076 | "cfg_aliases", 1077 | "libc", 1078 | "once_cell", 1079 | "socket2", 1080 | "tracing", 1081 | "windows-sys 0.59.0", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "quote" 1086 | version = "1.0.38" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 1089 | dependencies = [ 1090 | "proc-macro2", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "rand" 1095 | version = "0.8.5" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1098 | dependencies = [ 1099 | "libc", 1100 | "rand_chacha", 1101 | "rand_core", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "rand_chacha" 1106 | version = "0.3.1" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1109 | dependencies = [ 1110 | "ppv-lite86", 1111 | "rand_core", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "rand_core" 1116 | version = "0.6.4" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1119 | dependencies = [ 1120 | "getrandom 0.2.15", 1121 | ] 1122 | 1123 | [[package]] 1124 | name = "redox_syscall" 1125 | version = "0.5.8" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 1128 | dependencies = [ 1129 | "bitflags 2.8.0", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "redox_users" 1134 | version = "0.5.0" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" 1137 | dependencies = [ 1138 | "getrandom 0.2.15", 1139 | "libredox", 1140 | "thiserror", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "regex" 1145 | version = "1.11.1" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1148 | dependencies = [ 1149 | "aho-corasick", 1150 | "memchr", 1151 | "regex-automata", 1152 | "regex-syntax", 1153 | ] 1154 | 1155 | [[package]] 1156 | name = "regex-automata" 1157 | version = "0.4.9" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1160 | dependencies = [ 1161 | "aho-corasick", 1162 | "memchr", 1163 | "regex-syntax", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "regex-syntax" 1168 | version = "0.8.5" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1171 | 1172 | [[package]] 1173 | name = "reqwest" 1174 | version = "0.12.12" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" 1177 | dependencies = [ 1178 | "base64", 1179 | "bytes", 1180 | "futures-channel", 1181 | "futures-core", 1182 | "futures-util", 1183 | "http", 1184 | "http-body", 1185 | "http-body-util", 1186 | "hyper", 1187 | "hyper-rustls", 1188 | "hyper-util", 1189 | "ipnet", 1190 | "js-sys", 1191 | "log", 1192 | "mime", 1193 | "once_cell", 1194 | "percent-encoding", 1195 | "pin-project-lite", 1196 | "quinn", 1197 | "rustls", 1198 | "rustls-pemfile", 1199 | "rustls-pki-types", 1200 | "serde", 1201 | "serde_json", 1202 | "serde_urlencoded", 1203 | "sync_wrapper", 1204 | "tokio", 1205 | "tokio-rustls", 1206 | "tower", 1207 | "tower-service", 1208 | "url", 1209 | "wasm-bindgen", 1210 | "wasm-bindgen-futures", 1211 | "web-sys", 1212 | "webpki-roots", 1213 | "windows-registry", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "ring" 1218 | version = "0.17.8" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1221 | dependencies = [ 1222 | "cc", 1223 | "cfg-if", 1224 | "getrandom 0.2.15", 1225 | "libc", 1226 | "spin", 1227 | "untrusted", 1228 | "windows-sys 0.52.0", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "rustc-demangle" 1233 | version = "0.1.24" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1236 | 1237 | [[package]] 1238 | name = "rustc-hash" 1239 | version = "2.1.1" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 1242 | 1243 | [[package]] 1244 | name = "rustix" 1245 | version = "0.38.44" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1248 | dependencies = [ 1249 | "bitflags 2.8.0", 1250 | "errno", 1251 | "libc", 1252 | "linux-raw-sys", 1253 | "windows-sys 0.59.0", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "rustls" 1258 | version = "0.23.22" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" 1261 | dependencies = [ 1262 | "once_cell", 1263 | "ring", 1264 | "rustls-pki-types", 1265 | "rustls-webpki", 1266 | "subtle", 1267 | "zeroize", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "rustls-pemfile" 1272 | version = "2.2.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1275 | dependencies = [ 1276 | "rustls-pki-types", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "rustls-pki-types" 1281 | version = "1.11.0" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1284 | dependencies = [ 1285 | "web-time", 1286 | ] 1287 | 1288 | [[package]] 1289 | name = "rustls-webpki" 1290 | version = "0.102.8" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1293 | dependencies = [ 1294 | "ring", 1295 | "rustls-pki-types", 1296 | "untrusted", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "rustversion" 1301 | version = "1.0.19" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 1304 | 1305 | [[package]] 1306 | name = "ryu" 1307 | version = "1.0.19" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 1310 | 1311 | [[package]] 1312 | name = "scopeguard" 1313 | version = "1.2.0" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1316 | 1317 | [[package]] 1318 | name = "semver" 1319 | version = "1.0.25" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" 1322 | 1323 | [[package]] 1324 | name = "serde" 1325 | version = "1.0.217" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 1328 | dependencies = [ 1329 | "serde_derive", 1330 | ] 1331 | 1332 | [[package]] 1333 | name = "serde_derive" 1334 | version = "1.0.217" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 1337 | dependencies = [ 1338 | "proc-macro2", 1339 | "quote", 1340 | "syn", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "serde_json" 1345 | version = "1.0.138" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 1348 | dependencies = [ 1349 | "itoa", 1350 | "memchr", 1351 | "ryu", 1352 | "serde", 1353 | ] 1354 | 1355 | [[package]] 1356 | name = "serde_spanned" 1357 | version = "0.6.8" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1360 | dependencies = [ 1361 | "serde", 1362 | ] 1363 | 1364 | [[package]] 1365 | name = "serde_urlencoded" 1366 | version = "0.7.1" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1369 | dependencies = [ 1370 | "form_urlencoded", 1371 | "itoa", 1372 | "ryu", 1373 | "serde", 1374 | ] 1375 | 1376 | [[package]] 1377 | name = "serde_yaml" 1378 | version = "0.9.34+deprecated" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 1381 | dependencies = [ 1382 | "indexmap", 1383 | "itoa", 1384 | "ryu", 1385 | "serde", 1386 | "unsafe-libyaml", 1387 | ] 1388 | 1389 | [[package]] 1390 | name = "sha2" 1391 | version = "0.10.8" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1394 | dependencies = [ 1395 | "cfg-if", 1396 | "cpufeatures", 1397 | "digest", 1398 | ] 1399 | 1400 | [[package]] 1401 | name = "sharded-slab" 1402 | version = "0.1.7" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1405 | dependencies = [ 1406 | "lazy_static", 1407 | ] 1408 | 1409 | [[package]] 1410 | name = "shlex" 1411 | version = "1.3.0" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1414 | 1415 | [[package]] 1416 | name = "signal-hook" 1417 | version = "0.3.17" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1420 | dependencies = [ 1421 | "libc", 1422 | "signal-hook-registry", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "signal-hook-mio" 1427 | version = "0.2.4" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1430 | dependencies = [ 1431 | "libc", 1432 | "mio 0.8.11", 1433 | "mio 1.0.3", 1434 | "signal-hook", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "signal-hook-registry" 1439 | version = "1.4.2" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1442 | dependencies = [ 1443 | "libc", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "simple-ids" 1448 | version = "0.4.1" 1449 | dependencies = [ 1450 | "anyhow", 1451 | "clap", 1452 | "colored", 1453 | "crossterm 0.28.1", 1454 | "ctrlc", 1455 | "evectl", 1456 | "inquire", 1457 | "libc", 1458 | "regex", 1459 | "reqwest", 1460 | "semver", 1461 | "serde", 1462 | "serde_json", 1463 | "serde_yaml", 1464 | "sha2", 1465 | "tempfile", 1466 | "toml", 1467 | "tracing", 1468 | "tracing-subscriber", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "slab" 1473 | version = "0.4.9" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1476 | dependencies = [ 1477 | "autocfg", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "smallvec" 1482 | version = "1.13.2" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1485 | 1486 | [[package]] 1487 | name = "socket2" 1488 | version = "0.5.8" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1491 | dependencies = [ 1492 | "libc", 1493 | "windows-sys 0.52.0", 1494 | ] 1495 | 1496 | [[package]] 1497 | name = "spin" 1498 | version = "0.9.8" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1501 | 1502 | [[package]] 1503 | name = "stable_deref_trait" 1504 | version = "1.2.0" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1507 | 1508 | [[package]] 1509 | name = "strsim" 1510 | version = "0.11.1" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1513 | 1514 | [[package]] 1515 | name = "subtle" 1516 | version = "2.6.1" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1519 | 1520 | [[package]] 1521 | name = "syn" 1522 | version = "2.0.98" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 1525 | dependencies = [ 1526 | "proc-macro2", 1527 | "quote", 1528 | "unicode-ident", 1529 | ] 1530 | 1531 | [[package]] 1532 | name = "sync_wrapper" 1533 | version = "1.0.2" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1536 | dependencies = [ 1537 | "futures-core", 1538 | ] 1539 | 1540 | [[package]] 1541 | name = "synstructure" 1542 | version = "0.13.1" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1545 | dependencies = [ 1546 | "proc-macro2", 1547 | "quote", 1548 | "syn", 1549 | ] 1550 | 1551 | [[package]] 1552 | name = "tempfile" 1553 | version = "3.16.0" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" 1556 | dependencies = [ 1557 | "cfg-if", 1558 | "fastrand", 1559 | "getrandom 0.3.1", 1560 | "once_cell", 1561 | "rustix", 1562 | "windows-sys 0.59.0", 1563 | ] 1564 | 1565 | [[package]] 1566 | name = "thiserror" 1567 | version = "2.0.11" 1568 | source = "registry+https://github.com/rust-lang/crates.io-index" 1569 | checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" 1570 | dependencies = [ 1571 | "thiserror-impl", 1572 | ] 1573 | 1574 | [[package]] 1575 | name = "thiserror-impl" 1576 | version = "2.0.11" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" 1579 | dependencies = [ 1580 | "proc-macro2", 1581 | "quote", 1582 | "syn", 1583 | ] 1584 | 1585 | [[package]] 1586 | name = "thread_local" 1587 | version = "1.1.8" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1590 | dependencies = [ 1591 | "cfg-if", 1592 | "once_cell", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "tinystr" 1597 | version = "0.7.6" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1600 | dependencies = [ 1601 | "displaydoc", 1602 | "zerovec", 1603 | ] 1604 | 1605 | [[package]] 1606 | name = "tinyvec" 1607 | version = "1.8.1" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" 1610 | dependencies = [ 1611 | "tinyvec_macros", 1612 | ] 1613 | 1614 | [[package]] 1615 | name = "tinyvec_macros" 1616 | version = "0.1.1" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1619 | 1620 | [[package]] 1621 | name = "tokio" 1622 | version = "1.43.0" 1623 | source = "registry+https://github.com/rust-lang/crates.io-index" 1624 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" 1625 | dependencies = [ 1626 | "backtrace", 1627 | "bytes", 1628 | "libc", 1629 | "mio 1.0.3", 1630 | "pin-project-lite", 1631 | "socket2", 1632 | "windows-sys 0.52.0", 1633 | ] 1634 | 1635 | [[package]] 1636 | name = "tokio-rustls" 1637 | version = "0.26.1" 1638 | source = "registry+https://github.com/rust-lang/crates.io-index" 1639 | checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" 1640 | dependencies = [ 1641 | "rustls", 1642 | "tokio", 1643 | ] 1644 | 1645 | [[package]] 1646 | name = "toml" 1647 | version = "0.8.20" 1648 | source = "registry+https://github.com/rust-lang/crates.io-index" 1649 | checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" 1650 | dependencies = [ 1651 | "serde", 1652 | "serde_spanned", 1653 | "toml_datetime", 1654 | "toml_edit", 1655 | ] 1656 | 1657 | [[package]] 1658 | name = "toml_datetime" 1659 | version = "0.6.8" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1662 | dependencies = [ 1663 | "serde", 1664 | ] 1665 | 1666 | [[package]] 1667 | name = "toml_edit" 1668 | version = "0.22.23" 1669 | source = "registry+https://github.com/rust-lang/crates.io-index" 1670 | checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" 1671 | dependencies = [ 1672 | "indexmap", 1673 | "serde", 1674 | "serde_spanned", 1675 | "toml_datetime", 1676 | "winnow", 1677 | ] 1678 | 1679 | [[package]] 1680 | name = "tower" 1681 | version = "0.5.2" 1682 | source = "registry+https://github.com/rust-lang/crates.io-index" 1683 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1684 | dependencies = [ 1685 | "futures-core", 1686 | "futures-util", 1687 | "pin-project-lite", 1688 | "sync_wrapper", 1689 | "tokio", 1690 | "tower-layer", 1691 | "tower-service", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "tower-layer" 1696 | version = "0.3.3" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1699 | 1700 | [[package]] 1701 | name = "tower-service" 1702 | version = "0.3.3" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1705 | 1706 | [[package]] 1707 | name = "tracing" 1708 | version = "0.1.41" 1709 | source = "registry+https://github.com/rust-lang/crates.io-index" 1710 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1711 | dependencies = [ 1712 | "pin-project-lite", 1713 | "tracing-attributes", 1714 | "tracing-core", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "tracing-attributes" 1719 | version = "0.1.28" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 1722 | dependencies = [ 1723 | "proc-macro2", 1724 | "quote", 1725 | "syn", 1726 | ] 1727 | 1728 | [[package]] 1729 | name = "tracing-core" 1730 | version = "0.1.33" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1733 | dependencies = [ 1734 | "once_cell", 1735 | "valuable", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "tracing-log" 1740 | version = "0.2.0" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1743 | dependencies = [ 1744 | "log", 1745 | "once_cell", 1746 | "tracing-core", 1747 | ] 1748 | 1749 | [[package]] 1750 | name = "tracing-subscriber" 1751 | version = "0.3.19" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1754 | dependencies = [ 1755 | "nu-ansi-term", 1756 | "sharded-slab", 1757 | "smallvec", 1758 | "thread_local", 1759 | "tracing-core", 1760 | "tracing-log", 1761 | ] 1762 | 1763 | [[package]] 1764 | name = "try-lock" 1765 | version = "0.2.5" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1768 | 1769 | [[package]] 1770 | name = "typenum" 1771 | version = "1.17.0" 1772 | source = "registry+https://github.com/rust-lang/crates.io-index" 1773 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1774 | 1775 | [[package]] 1776 | name = "unicode-ident" 1777 | version = "1.0.16" 1778 | source = "registry+https://github.com/rust-lang/crates.io-index" 1779 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 1780 | 1781 | [[package]] 1782 | name = "unicode-segmentation" 1783 | version = "1.12.0" 1784 | source = "registry+https://github.com/rust-lang/crates.io-index" 1785 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1786 | 1787 | [[package]] 1788 | name = "unicode-width" 1789 | version = "0.1.14" 1790 | source = "registry+https://github.com/rust-lang/crates.io-index" 1791 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1792 | 1793 | [[package]] 1794 | name = "unsafe-libyaml" 1795 | version = "0.2.11" 1796 | source = "registry+https://github.com/rust-lang/crates.io-index" 1797 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 1798 | 1799 | [[package]] 1800 | name = "untrusted" 1801 | version = "0.9.0" 1802 | source = "registry+https://github.com/rust-lang/crates.io-index" 1803 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1804 | 1805 | [[package]] 1806 | name = "url" 1807 | version = "2.5.4" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1810 | dependencies = [ 1811 | "form_urlencoded", 1812 | "idna", 1813 | "percent-encoding", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "utf16_iter" 1818 | version = "1.0.5" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1821 | 1822 | [[package]] 1823 | name = "utf8_iter" 1824 | version = "1.0.4" 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" 1826 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1827 | 1828 | [[package]] 1829 | name = "utf8parse" 1830 | version = "0.2.2" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1833 | 1834 | [[package]] 1835 | name = "valuable" 1836 | version = "0.1.1" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 1839 | 1840 | [[package]] 1841 | name = "version_check" 1842 | version = "0.9.5" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1845 | 1846 | [[package]] 1847 | name = "want" 1848 | version = "0.3.1" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1851 | dependencies = [ 1852 | "try-lock", 1853 | ] 1854 | 1855 | [[package]] 1856 | name = "wasi" 1857 | version = "0.11.0+wasi-snapshot-preview1" 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" 1859 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1860 | 1861 | [[package]] 1862 | name = "wasi" 1863 | version = "0.13.3+wasi-0.2.2" 1864 | source = "registry+https://github.com/rust-lang/crates.io-index" 1865 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 1866 | dependencies = [ 1867 | "wit-bindgen-rt", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "wasm-bindgen" 1872 | version = "0.2.100" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1875 | dependencies = [ 1876 | "cfg-if", 1877 | "once_cell", 1878 | "rustversion", 1879 | "wasm-bindgen-macro", 1880 | ] 1881 | 1882 | [[package]] 1883 | name = "wasm-bindgen-backend" 1884 | version = "0.2.100" 1885 | source = "registry+https://github.com/rust-lang/crates.io-index" 1886 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1887 | dependencies = [ 1888 | "bumpalo", 1889 | "log", 1890 | "proc-macro2", 1891 | "quote", 1892 | "syn", 1893 | "wasm-bindgen-shared", 1894 | ] 1895 | 1896 | [[package]] 1897 | name = "wasm-bindgen-futures" 1898 | version = "0.4.50" 1899 | source = "registry+https://github.com/rust-lang/crates.io-index" 1900 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 1901 | dependencies = [ 1902 | "cfg-if", 1903 | "js-sys", 1904 | "once_cell", 1905 | "wasm-bindgen", 1906 | "web-sys", 1907 | ] 1908 | 1909 | [[package]] 1910 | name = "wasm-bindgen-macro" 1911 | version = "0.2.100" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1914 | dependencies = [ 1915 | "quote", 1916 | "wasm-bindgen-macro-support", 1917 | ] 1918 | 1919 | [[package]] 1920 | name = "wasm-bindgen-macro-support" 1921 | version = "0.2.100" 1922 | source = "registry+https://github.com/rust-lang/crates.io-index" 1923 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1924 | dependencies = [ 1925 | "proc-macro2", 1926 | "quote", 1927 | "syn", 1928 | "wasm-bindgen-backend", 1929 | "wasm-bindgen-shared", 1930 | ] 1931 | 1932 | [[package]] 1933 | name = "wasm-bindgen-shared" 1934 | version = "0.2.100" 1935 | source = "registry+https://github.com/rust-lang/crates.io-index" 1936 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1937 | dependencies = [ 1938 | "unicode-ident", 1939 | ] 1940 | 1941 | [[package]] 1942 | name = "web-sys" 1943 | version = "0.3.77" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 1946 | dependencies = [ 1947 | "js-sys", 1948 | "wasm-bindgen", 1949 | ] 1950 | 1951 | [[package]] 1952 | name = "web-time" 1953 | version = "1.1.0" 1954 | source = "registry+https://github.com/rust-lang/crates.io-index" 1955 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 1956 | dependencies = [ 1957 | "js-sys", 1958 | "wasm-bindgen", 1959 | ] 1960 | 1961 | [[package]] 1962 | name = "webpki-roots" 1963 | version = "0.26.8" 1964 | source = "registry+https://github.com/rust-lang/crates.io-index" 1965 | checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" 1966 | dependencies = [ 1967 | "rustls-pki-types", 1968 | ] 1969 | 1970 | [[package]] 1971 | name = "winapi" 1972 | version = "0.3.9" 1973 | source = "registry+https://github.com/rust-lang/crates.io-index" 1974 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1975 | dependencies = [ 1976 | "winapi-i686-pc-windows-gnu", 1977 | "winapi-x86_64-pc-windows-gnu", 1978 | ] 1979 | 1980 | [[package]] 1981 | name = "winapi-i686-pc-windows-gnu" 1982 | version = "0.4.0" 1983 | source = "registry+https://github.com/rust-lang/crates.io-index" 1984 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1985 | 1986 | [[package]] 1987 | name = "winapi-x86_64-pc-windows-gnu" 1988 | version = "0.4.0" 1989 | source = "registry+https://github.com/rust-lang/crates.io-index" 1990 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1991 | 1992 | [[package]] 1993 | name = "windows-registry" 1994 | version = "0.2.0" 1995 | source = "registry+https://github.com/rust-lang/crates.io-index" 1996 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1997 | dependencies = [ 1998 | "windows-result", 1999 | "windows-strings", 2000 | "windows-targets 0.52.6", 2001 | ] 2002 | 2003 | [[package]] 2004 | name = "windows-result" 2005 | version = "0.2.0" 2006 | source = "registry+https://github.com/rust-lang/crates.io-index" 2007 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 2008 | dependencies = [ 2009 | "windows-targets 0.52.6", 2010 | ] 2011 | 2012 | [[package]] 2013 | name = "windows-strings" 2014 | version = "0.1.0" 2015 | source = "registry+https://github.com/rust-lang/crates.io-index" 2016 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 2017 | dependencies = [ 2018 | "windows-result", 2019 | "windows-targets 0.52.6", 2020 | ] 2021 | 2022 | [[package]] 2023 | name = "windows-sys" 2024 | version = "0.48.0" 2025 | source = "registry+https://github.com/rust-lang/crates.io-index" 2026 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2027 | dependencies = [ 2028 | "windows-targets 0.48.5", 2029 | ] 2030 | 2031 | [[package]] 2032 | name = "windows-sys" 2033 | version = "0.52.0" 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" 2035 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2036 | dependencies = [ 2037 | "windows-targets 0.52.6", 2038 | ] 2039 | 2040 | [[package]] 2041 | name = "windows-sys" 2042 | version = "0.59.0" 2043 | source = "registry+https://github.com/rust-lang/crates.io-index" 2044 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2045 | dependencies = [ 2046 | "windows-targets 0.52.6", 2047 | ] 2048 | 2049 | [[package]] 2050 | name = "windows-targets" 2051 | version = "0.48.5" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2054 | dependencies = [ 2055 | "windows_aarch64_gnullvm 0.48.5", 2056 | "windows_aarch64_msvc 0.48.5", 2057 | "windows_i686_gnu 0.48.5", 2058 | "windows_i686_msvc 0.48.5", 2059 | "windows_x86_64_gnu 0.48.5", 2060 | "windows_x86_64_gnullvm 0.48.5", 2061 | "windows_x86_64_msvc 0.48.5", 2062 | ] 2063 | 2064 | [[package]] 2065 | name = "windows-targets" 2066 | version = "0.52.6" 2067 | source = "registry+https://github.com/rust-lang/crates.io-index" 2068 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2069 | dependencies = [ 2070 | "windows_aarch64_gnullvm 0.52.6", 2071 | "windows_aarch64_msvc 0.52.6", 2072 | "windows_i686_gnu 0.52.6", 2073 | "windows_i686_gnullvm", 2074 | "windows_i686_msvc 0.52.6", 2075 | "windows_x86_64_gnu 0.52.6", 2076 | "windows_x86_64_gnullvm 0.52.6", 2077 | "windows_x86_64_msvc 0.52.6", 2078 | ] 2079 | 2080 | [[package]] 2081 | name = "windows_aarch64_gnullvm" 2082 | version = "0.48.5" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2085 | 2086 | [[package]] 2087 | name = "windows_aarch64_gnullvm" 2088 | version = "0.52.6" 2089 | source = "registry+https://github.com/rust-lang/crates.io-index" 2090 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2091 | 2092 | [[package]] 2093 | name = "windows_aarch64_msvc" 2094 | version = "0.48.5" 2095 | source = "registry+https://github.com/rust-lang/crates.io-index" 2096 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2097 | 2098 | [[package]] 2099 | name = "windows_aarch64_msvc" 2100 | version = "0.52.6" 2101 | source = "registry+https://github.com/rust-lang/crates.io-index" 2102 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2103 | 2104 | [[package]] 2105 | name = "windows_i686_gnu" 2106 | version = "0.48.5" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2109 | 2110 | [[package]] 2111 | name = "windows_i686_gnu" 2112 | version = "0.52.6" 2113 | source = "registry+https://github.com/rust-lang/crates.io-index" 2114 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2115 | 2116 | [[package]] 2117 | name = "windows_i686_gnullvm" 2118 | version = "0.52.6" 2119 | source = "registry+https://github.com/rust-lang/crates.io-index" 2120 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2121 | 2122 | [[package]] 2123 | name = "windows_i686_msvc" 2124 | version = "0.48.5" 2125 | source = "registry+https://github.com/rust-lang/crates.io-index" 2126 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2127 | 2128 | [[package]] 2129 | name = "windows_i686_msvc" 2130 | version = "0.52.6" 2131 | source = "registry+https://github.com/rust-lang/crates.io-index" 2132 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2133 | 2134 | [[package]] 2135 | name = "windows_x86_64_gnu" 2136 | version = "0.48.5" 2137 | source = "registry+https://github.com/rust-lang/crates.io-index" 2138 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2139 | 2140 | [[package]] 2141 | name = "windows_x86_64_gnu" 2142 | version = "0.52.6" 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" 2144 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2145 | 2146 | [[package]] 2147 | name = "windows_x86_64_gnullvm" 2148 | version = "0.48.5" 2149 | source = "registry+https://github.com/rust-lang/crates.io-index" 2150 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2151 | 2152 | [[package]] 2153 | name = "windows_x86_64_gnullvm" 2154 | version = "0.52.6" 2155 | source = "registry+https://github.com/rust-lang/crates.io-index" 2156 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2157 | 2158 | [[package]] 2159 | name = "windows_x86_64_msvc" 2160 | version = "0.48.5" 2161 | source = "registry+https://github.com/rust-lang/crates.io-index" 2162 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2163 | 2164 | [[package]] 2165 | name = "windows_x86_64_msvc" 2166 | version = "0.52.6" 2167 | source = "registry+https://github.com/rust-lang/crates.io-index" 2168 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2169 | 2170 | [[package]] 2171 | name = "winnow" 2172 | version = "0.7.1" 2173 | source = "registry+https://github.com/rust-lang/crates.io-index" 2174 | checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" 2175 | dependencies = [ 2176 | "memchr", 2177 | ] 2178 | 2179 | [[package]] 2180 | name = "wit-bindgen-rt" 2181 | version = "0.33.0" 2182 | source = "registry+https://github.com/rust-lang/crates.io-index" 2183 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 2184 | dependencies = [ 2185 | "bitflags 2.8.0", 2186 | ] 2187 | 2188 | [[package]] 2189 | name = "write16" 2190 | version = "1.0.0" 2191 | source = "registry+https://github.com/rust-lang/crates.io-index" 2192 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2193 | 2194 | [[package]] 2195 | name = "writeable" 2196 | version = "0.5.5" 2197 | source = "registry+https://github.com/rust-lang/crates.io-index" 2198 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2199 | 2200 | [[package]] 2201 | name = "yoke" 2202 | version = "0.7.5" 2203 | source = "registry+https://github.com/rust-lang/crates.io-index" 2204 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2205 | dependencies = [ 2206 | "serde", 2207 | "stable_deref_trait", 2208 | "yoke-derive", 2209 | "zerofrom", 2210 | ] 2211 | 2212 | [[package]] 2213 | name = "yoke-derive" 2214 | version = "0.7.5" 2215 | source = "registry+https://github.com/rust-lang/crates.io-index" 2216 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2217 | dependencies = [ 2218 | "proc-macro2", 2219 | "quote", 2220 | "syn", 2221 | "synstructure", 2222 | ] 2223 | 2224 | [[package]] 2225 | name = "zerocopy" 2226 | version = "0.7.35" 2227 | source = "registry+https://github.com/rust-lang/crates.io-index" 2228 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2229 | dependencies = [ 2230 | "byteorder", 2231 | "zerocopy-derive", 2232 | ] 2233 | 2234 | [[package]] 2235 | name = "zerocopy-derive" 2236 | version = "0.7.35" 2237 | source = "registry+https://github.com/rust-lang/crates.io-index" 2238 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2239 | dependencies = [ 2240 | "proc-macro2", 2241 | "quote", 2242 | "syn", 2243 | ] 2244 | 2245 | [[package]] 2246 | name = "zerofrom" 2247 | version = "0.1.5" 2248 | source = "registry+https://github.com/rust-lang/crates.io-index" 2249 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 2250 | dependencies = [ 2251 | "zerofrom-derive", 2252 | ] 2253 | 2254 | [[package]] 2255 | name = "zerofrom-derive" 2256 | version = "0.1.5" 2257 | source = "registry+https://github.com/rust-lang/crates.io-index" 2258 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 2259 | dependencies = [ 2260 | "proc-macro2", 2261 | "quote", 2262 | "syn", 2263 | "synstructure", 2264 | ] 2265 | 2266 | [[package]] 2267 | name = "zeroize" 2268 | version = "1.8.1" 2269 | source = "registry+https://github.com/rust-lang/crates.io-index" 2270 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2271 | 2272 | [[package]] 2273 | name = "zerovec" 2274 | version = "0.10.4" 2275 | source = "registry+https://github.com/rust-lang/crates.io-index" 2276 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2277 | dependencies = [ 2278 | "yoke", 2279 | "zerofrom", 2280 | "zerovec-derive", 2281 | ] 2282 | 2283 | [[package]] 2284 | name = "zerovec-derive" 2285 | version = "0.10.3" 2286 | source = "registry+https://github.com/rust-lang/crates.io-index" 2287 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2288 | dependencies = [ 2289 | "proc-macro2", 2290 | "quote", 2291 | "syn", 2292 | ] 2293 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple-ids" 3 | version = "0.4.1" 4 | authors = ["Jason Ish "] 5 | edition = "2021" 6 | description = "Simple-IDS with Suricata and EveBox" 7 | homepage = "https://evebox.org/simple-ids/" 8 | repository = "https://github.com/jasonish/simple-ids" 9 | license = "MIT" 10 | 11 | # Some tweaks to reduce binary size. 12 | [profile.release] 13 | opt-level = "z" 14 | lto = true 15 | codegen-units = 1 16 | 17 | [dependencies] 18 | anyhow = "1.0.79" 19 | clap = { version = "4.5.0", features = ["derive", "color"] } 20 | colored = "3.0.0" 21 | crossterm = "0.28.1" 22 | ctrlc = "3.4.2" 23 | inquire = "0.7.5" 24 | libc = "0.2.153" 25 | regex = "1.10.3" 26 | reqwest = { version = "0.12.12", default-features = false, features = ["blocking", "rustls-tls"] } 27 | semver = "1.0.21" 28 | serde = { version = "1.0.196", default-features = false, features = ["derive"] } 29 | serde_json = "1.0.113" 30 | serde_yaml = "0.9.31" 31 | sha2 = "0.10.8" 32 | tempfile = "3.10.0" 33 | toml = "0.8.14" 34 | tracing = "0.1.40" 35 | tracing-subscriber = "0.3.18" 36 | 37 | evectl = { version = "0.1.0-alpha.7" } 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Jason Ish 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cross build --release --target aarch64-unknown-linux-musl 3 | cross build --release --target x86_64-unknown-linux-musl 4 | 5 | clean: 6 | find . -name \*~ -delete 7 | cargo clean 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple-IDS - Suricata/EveBox 2 | 3 | **Maintence of Simple-IDS has stalled. It has been replaced by EveCtl, a tool 4 | that allows more complex configurations, yet keeps a simple standalone mode -- 5 | https://github.com/jasonish/evectl** 6 | 7 | Simple-IDS is a tool to easily run Suricata and EveBox Linux systems 8 | using Docker or Podman. 9 | 10 | This program is considered experimental and many things may change, 11 | break, change name (I'm thinking simpleids is better), change repo, 12 | etc, etc... And I might even force push! 13 | 14 | ## System Requirements 15 | 16 | - An x86_64 or Aarch64 based Linux distribution with Docker or 17 | Podman. This includes most Linux distributions available today 18 | including Raspberry Pi OS (with 64 bit update applied). 19 | - Root access. 20 | 21 | ## Installation the Easy Way 22 | 23 | ``` 24 | mkdir ~/simple-ids 25 | curl -sSf https://evebox.org/simple-ids.sh | sh 26 | ``` 27 | 28 | Or download directly from https://evebox.org/files/simple-ids/. 29 | 30 | Once you have the program downloaded, run it: 31 | 32 | ``` 33 | ./simple-ids 34 | ``` 35 | 36 | Under the configure menu select your network interface, then select 37 | "Start" from the main menu. 38 | 39 | ## Building 40 | 41 | If you just want to use Simple-IDS you can download a pre-compiled 42 | binary. The following is only for those who wish to compile Simple-IDS 43 | themselves. 44 | 45 | ### For Host OS 46 | 47 | ``` 48 | cargo build --release 49 | ``` 50 | 51 | ### Static Targets 52 | 53 | Static binaries for x86_64 and other platforms can be built with the 54 | `cross` tool. To install `cross`: 55 | 56 | ``` 57 | cargo install cross 58 | ``` 59 | 60 | #### x86_64 61 | 62 | ``` 63 | cross build --release --target x86_64-unknown-linux-musl 64 | ``` 65 | 66 | #### Aarch64 (Raspberry Pi 64 bit) 67 | 68 | ``` 69 | cross build --release --target aarch64-unknown-linux-musl 70 | ``` 71 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | fn main() { 5 | // Make the target triple available to the env! macro. This is used so the binary 6 | // can figure out its self-update URL. 7 | println!( 8 | "cargo:rustc-env=TARGET={}", 9 | std::env::var("TARGET").unwrap() 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Intentionally empty. 2 | -------------------------------------------------------------------------------- /src/actions.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::collections::HashSet; 5 | 6 | use anyhow::{bail, Result}; 7 | use tracing::error; 8 | 9 | use crate::container::{CommandExt, SuricataContainer}; 10 | use crate::context::Context; 11 | use crate::ruleindex::RuleIndex; 12 | use crate::SURICATA_CONTAINER_NAME; 13 | use crate::{build_evebox_command, EVEBOX_CONTAINER_NAME}; 14 | 15 | pub(crate) fn force_suricata_logrotate(context: &Context) { 16 | let _ = context 17 | .manager 18 | .command() 19 | .args([ 20 | "exec", 21 | SURICATA_CONTAINER_NAME, 22 | "logrotate", 23 | "-fv", 24 | "/etc/logrotate.d/suricata", 25 | ]) 26 | .status(); 27 | } 28 | 29 | pub(crate) fn load_rule_index(context: &Context) -> Result { 30 | let container = SuricataContainer::new(context.clone()); 31 | let output = container 32 | .run() 33 | .rm() 34 | .args(&["cat", "/var/lib/suricata/update/cache/index.yaml"]) 35 | .build() 36 | .status_output()?; 37 | let index: RuleIndex = serde_yaml::from_slice(&output)?; 38 | Ok(index) 39 | } 40 | 41 | pub(crate) fn get_enabled_ruleset(context: &Context) -> Result> { 42 | let mut enabled: HashSet = HashSet::new(); 43 | let container = SuricataContainer::new(context.clone()); 44 | let output = container 45 | .run() 46 | .args(&["suricata-update", "list-sources", "--enabled"]) 47 | .build() 48 | .output()?; 49 | let stdout = String::from_utf8_lossy(&output.stdout); 50 | let re = regex::Regex::new(r"^[\s]*\-\s*(.*)").unwrap(); 51 | for line in stdout.lines() { 52 | if let Some(caps) = re.captures(line) { 53 | enabled.insert(String::from(&caps[1])); 54 | } 55 | } 56 | Ok(enabled) 57 | } 58 | 59 | pub(crate) fn enable_ruleset(context: &Context, ruleset: &str) -> Result<()> { 60 | let container = SuricataContainer::new(context.clone()); 61 | container 62 | .run() 63 | .args(&["suricata-update", "enable-source", ruleset]) 64 | .build() 65 | .status_ok()?; 66 | Ok(()) 67 | } 68 | 69 | pub(crate) fn disable_ruleset(context: &Context, ruleset: &str) -> Result<()> { 70 | let container = SuricataContainer::new(context.clone()); 71 | container 72 | .run() 73 | .args(&["suricata-update", "disable-source", ruleset]) 74 | .build() 75 | .status_ok()?; 76 | Ok(()) 77 | } 78 | 79 | pub(crate) fn update_rules(context: &Context) -> Result<()> { 80 | let container = SuricataContainer::new(context.clone()); 81 | 82 | let mut volumes = vec![]; 83 | 84 | if let Ok(cdir) = std::env::current_dir() { 85 | for filename in ["enable.conf", "disable.conf", "modify.conf"] { 86 | if cdir.join(filename).exists() { 87 | volumes.push(format!( 88 | "{}/{}:/etc/suricata/{}", 89 | cdir.display(), 90 | filename, 91 | filename, 92 | )); 93 | } 94 | } 95 | } 96 | 97 | if let Err(err) = container 98 | .run() 99 | .rm() 100 | .it() 101 | .args(&["suricata-update", "update-sources"]) 102 | .build() 103 | .status_ok() 104 | { 105 | error!("Rule source update did not complete successfully: {err}"); 106 | } 107 | if let Err(err) = container 108 | .run() 109 | .rm() 110 | .it() 111 | .volumes(&volumes) 112 | .args(&["suricata-update"]) 113 | .build() 114 | .status_ok() 115 | { 116 | error!("Rule update did not complete successfully: {err}"); 117 | } 118 | Ok(()) 119 | } 120 | 121 | pub(crate) fn start_evebox(context: &Context) -> Result<()> { 122 | context.manager.quiet_rm(EVEBOX_CONTAINER_NAME); 123 | let mut command = build_evebox_command(context, true); 124 | let output = command.output()?; 125 | if !output.status.success() { 126 | bail!(String::from_utf8_lossy(&output.stderr).to_string()); 127 | } 128 | Ok(()) 129 | } 130 | 131 | pub(crate) fn stop_evebox(context: &Context) -> Result<()> { 132 | context.manager.stop(EVEBOX_CONTAINER_NAME, Some("SIGINT")) 133 | } 134 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::io::{Read, Write}; 5 | 6 | use anyhow::Result; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | use crate::prelude::*; 10 | 11 | const YAML_FILENAME: &str = "simple-ids.yml"; 12 | const TOML_FILENAME: &str = "simple-ids.toml"; 13 | 14 | #[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] 15 | pub(crate) struct Config { 16 | pub suricata: SuricataConfig, 17 | 18 | #[serde(default)] 19 | pub evebox: EveBoxConfig, 20 | } 21 | 22 | #[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] 23 | pub(crate) struct SuricataConfig { 24 | #[serde(skip_serializing_if = "Vec::is_empty")] 25 | pub interfaces: Vec, 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | pub image: Option, 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | pub bpf: Option, 30 | } 31 | 32 | #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] 33 | pub(crate) struct EveBoxConfig { 34 | #[serde(rename = "allow-remote")] 35 | pub allow_remote: bool, 36 | #[serde(rename = "no-tls", default)] 37 | pub no_tls: bool, 38 | #[serde(rename = "no-auth", default)] 39 | pub no_auth: bool, 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub image: Option, 42 | } 43 | 44 | impl Default for EveBoxConfig { 45 | fn default() -> Self { 46 | Self { 47 | allow_remote: false, 48 | no_tls: true, 49 | no_auth: true, 50 | image: None, 51 | } 52 | } 53 | } 54 | 55 | impl Config { 56 | pub(crate) fn new() -> Self { 57 | if let Ok(buf) = Self::read_file(TOML_FILENAME) { 58 | match Self::parse_toml(&buf) { 59 | Err(err) => { 60 | error!("Failed to parse configuration file: {}", err); 61 | } 62 | Ok(config) => return config, 63 | } 64 | } 65 | 66 | if let Ok(config) = Self::read_file(YAML_FILENAME) { 67 | match Self::parse_yaml(&config) { 68 | Err(err) => { 69 | error!("Failed to parse configuration file: {}", err); 70 | } 71 | Ok(config) => return config, 72 | } 73 | } 74 | 75 | Self::default() 76 | } 77 | 78 | pub(crate) fn save(&self) -> Result<()> { 79 | let mut file = std::fs::File::create(TOML_FILENAME)?; 80 | let config = toml::to_string(self)?; 81 | file.write_all(config.as_bytes())?; 82 | 83 | // Delete YAML_FILENAME if exists. 84 | if std::fs::metadata(YAML_FILENAME).is_ok() { 85 | std::fs::remove_file(YAML_FILENAME)?; 86 | } 87 | 88 | Ok(()) 89 | } 90 | 91 | fn read_file(filename: &str) -> Result { 92 | let mut file = std::fs::File::open(filename)?; 93 | let mut buffer = String::new(); 94 | file.read_to_string(&mut buffer)?; 95 | Ok(buffer) 96 | } 97 | 98 | fn parse_yaml(buf: &str) -> Result { 99 | Ok(serde_yaml::from_str(buf)?) 100 | } 101 | 102 | fn parse_toml(buf: &str) -> Result { 103 | Ok(toml::from_str(buf)?) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/container.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use anyhow::{bail, Result}; 5 | use serde::Deserialize; 6 | use std::process::Command; 7 | use tracing::{debug, error, info}; 8 | 9 | use crate::{ 10 | context::Context, EVEBOX_VOLUME_LIB, SURICATA_VOLUME_LIB, SURICATA_VOLUME_LOG, 11 | SURICATA_VOLUME_RUN, 12 | }; 13 | 14 | pub const DEFAULT_SURICATA_IMAGE: &str = "docker.io/jasonish/suricata:latest"; 15 | pub const DEFAULT_EVEBOX_IMAGE: &str = "docker.io/jasonish/evebox:master"; 16 | 17 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 18 | pub(crate) enum ContainerManager { 19 | Docker(DockerManager), 20 | Podman(PodmanManager), 21 | } 22 | 23 | impl std::fmt::Display for ContainerManager { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | let name = match self { 26 | ContainerManager::Docker(_) => "Docker", 27 | ContainerManager::Podman(_) => "Podman", 28 | }; 29 | write!(f, "{name}") 30 | } 31 | } 32 | 33 | impl ContainerManager { 34 | pub(crate) fn command(&self) -> Command { 35 | Command::new(self.bin()) 36 | } 37 | 38 | pub(crate) fn bin(&self) -> &str { 39 | match self { 40 | Self::Docker(docker) => docker.bin(), 41 | Self::Podman(podman) => podman.bin(), 42 | } 43 | } 44 | 45 | /// Test if a container manager exists. 46 | pub(crate) fn exists(&self) -> bool { 47 | Command::new(self.bin()) 48 | .stdout(std::process::Stdio::null()) 49 | .stderr(std::process::Stdio::null()) 50 | .status() 51 | .is_ok() 52 | } 53 | 54 | /// Return true if the container manager is Podman. 55 | pub(crate) fn is_podman(&self) -> bool { 56 | matches!(self, ContainerManager::Podman(_)) 57 | } 58 | 59 | /// Return true if the container manager is Docker. 60 | pub(crate) fn is_docker(&self) -> bool { 61 | matches!(self, ContainerManager::Docker(_)) 62 | } 63 | 64 | pub(crate) fn version(&self) -> Result { 65 | let output = self 66 | .command() 67 | .args(["version", "--format", "{{json . }}"]) 68 | .output()?; 69 | if !output.status.success() { 70 | bail!(String::from_utf8_lossy(&output.stderr).to_string()); 71 | } else if let Ok(json) = serde_json::from_slice::(&output.stdout) { 72 | if let Some(version) = json["Client"]["Version"].as_str() { 73 | return Ok(version.to_string()); 74 | } 75 | if let Some(version) = json["Version"].as_str() { 76 | return Ok(version.to_string()); 77 | } 78 | } 79 | bail!( 80 | "Failed to find {} version in output: {}", 81 | self.to_string(), 82 | String::from_utf8_lossy(&output.stdout).to_string() 83 | ); 84 | } 85 | 86 | /// Quietly remove container. 87 | pub(crate) fn quiet_rm(&self, name: &str) { 88 | let mut args = vec!["rm"]; 89 | 90 | // Podman needs to be a little more agressive here. 91 | if self.is_podman() { 92 | args.push("--force"); 93 | } 94 | 95 | args.push(name); 96 | let _ = self.command().args(&args).output(); 97 | } 98 | 99 | pub(crate) fn stop(&self, name: &str, signal: Option<&str>) -> Result<()> { 100 | let mut cmd = self.command(); 101 | cmd.arg("stop"); 102 | 103 | // Custom stop signals are not supported on Podman. 104 | if self.is_docker() { 105 | cmd.args(["--signal", signal.unwrap_or("SIGTERM")]); 106 | } 107 | cmd.arg(name); 108 | let output = cmd.output()?; 109 | if !output.status.success() { 110 | bail!(String::from_utf8_lossy(&output.stderr).to_string()); 111 | } 112 | Ok(()) 113 | } 114 | 115 | pub(crate) fn pull(&self, image: &str) -> Result<()> { 116 | let status = self.command().args(["pull", image]).status()?; 117 | if status.success() { 118 | Ok(()) 119 | } else { 120 | bail!("Pull did not exit successfully") 121 | } 122 | } 123 | 124 | pub(crate) fn inspect_first(&self, name: &str) -> Result { 125 | let mut command = self.command(); 126 | command.args(["inspect", name]); 127 | let mut entries: Vec = command_json(&mut command)?; 128 | if entries.is_empty() { 129 | bail!("{} returned unexpected empty inspect array", self); 130 | } else { 131 | Ok(entries.swap_remove(0)) 132 | } 133 | } 134 | 135 | pub(crate) fn has_image(&self, name: &str) -> bool { 136 | self.inspect_first(name).is_ok() 137 | } 138 | 139 | pub(crate) fn is_running(&self, name: &str) -> bool { 140 | if let Ok(state) = self.state(name) { 141 | return state.running; 142 | } 143 | false 144 | } 145 | 146 | /// Return the Inspect.State object for a container. 147 | /// 148 | /// If the container doesn't exist an error is returned. 149 | pub(crate) fn state(&self, name: &str) -> Result { 150 | match self.inspect_first(name)?.state { 151 | Some(state) => Ok(state), 152 | None => bail!("not a container"), 153 | } 154 | } 155 | 156 | /// Test if a container exists. 157 | /// 158 | /// Any failure results in false. 159 | pub(crate) fn container_exists(&self, name: &str) -> bool { 160 | if let Ok(output) = self.command().args(["inspect", name]).output() { 161 | return output.status.success(); 162 | } 163 | false 164 | } 165 | } 166 | 167 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 168 | pub(crate) struct PodmanManager {} 169 | 170 | impl PodmanManager { 171 | pub(crate) fn new() -> Self { 172 | Self {} 173 | } 174 | 175 | pub(crate) fn bin(&self) -> &str { 176 | "podman" 177 | } 178 | } 179 | 180 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 181 | pub(crate) struct DockerManager {} 182 | 183 | impl DockerManager { 184 | pub(crate) fn new() -> Self { 185 | Self {} 186 | } 187 | 188 | pub(crate) fn bin(&self) -> &str { 189 | "docker" 190 | } 191 | } 192 | 193 | /// Command extensions useful for containers. 194 | pub(crate) trait CommandExt { 195 | /// Like `Command::output`, but return an error on command failure 196 | /// as well as non-successful exit code. 197 | fn status_output(&mut self) -> anyhow::Result>; 198 | 199 | /// Like `Command::status` but will also fail if the command did 200 | /// not exit successfully. 201 | fn status_ok(&mut self) -> Result<()>; 202 | } 203 | 204 | impl CommandExt for std::process::Command { 205 | fn status_output(&mut self) -> Result> { 206 | let output = self.output()?; 207 | if output.status.success() { 208 | Ok(output.stdout) 209 | } else { 210 | bail!(String::from_utf8_lossy(&output.stderr).to_string()) 211 | } 212 | } 213 | 214 | fn status_ok(&mut self) -> Result<()> { 215 | let status = self.status()?; 216 | if status.success() { 217 | Ok(()) 218 | } else { 219 | bail!("Failed with exit code {:?}", status.code()) 220 | } 221 | } 222 | } 223 | 224 | #[derive(Debug, Deserialize)] 225 | pub(crate) struct InspectEntry { 226 | #[serde(rename = "Id")] 227 | _id: String, 228 | 229 | // Only found when inspecting containers. 230 | #[serde(rename = "State")] 231 | state: Option, 232 | 233 | // Only found when inspecting images. 234 | #[serde(rename = "RepoTags")] 235 | _repo_tags: Option>, 236 | } 237 | 238 | #[derive(Debug, Deserialize)] 239 | pub(crate) struct InspectState { 240 | #[serde(rename = "Status")] 241 | pub status: String, 242 | 243 | #[serde(rename = "Running")] 244 | pub running: bool, 245 | 246 | #[serde(rename = "Error")] 247 | pub _error: String, 248 | 249 | #[serde(rename = "ExitCode")] 250 | pub _exit_code: i32, 251 | } 252 | 253 | fn command_json(command: &mut Command) -> Result 254 | where 255 | T: serde::de::DeserializeOwned + std::fmt::Debug, 256 | { 257 | let output = command.output()?; 258 | if !output.status.success() { 259 | if output.stderr.is_empty() { 260 | bail!("Command failed with no stderr output"); 261 | } else { 262 | bail!(String::from_utf8_lossy(&output.stderr).to_string()); 263 | } 264 | } else { 265 | Ok(serde_json::from_slice(&output.stdout)?) 266 | } 267 | } 268 | 269 | pub(crate) fn find_manager(podman: bool) -> Option { 270 | if !podman { 271 | debug!("Looking for Docker container engine"); 272 | 273 | let manager = ContainerManager::Docker(DockerManager::new()); 274 | if manager.exists() { 275 | info!("Found Docker container engine"); 276 | if let Ok(version) = manager.version() { 277 | debug!("Found Docker version {version}"); 278 | return Some(manager); 279 | } 280 | } else { 281 | info!("Docker not found"); 282 | } 283 | }; 284 | 285 | debug!("Looking for Podman container engine"); 286 | let manager = ContainerManager::Podman(PodmanManager::new()); 287 | if manager.exists() { 288 | info!("Found Podman container engine"); 289 | if let Ok(version) = manager.version() { 290 | debug!("Found Podman version {version}"); 291 | match semver::Version::parse(&version) { 292 | Ok(version) => { 293 | if version.major < 4 || (version.major == 4 && version.minor < 6) { 294 | error!("Podman version must be at least 4.7.0"); 295 | } else { 296 | return Some(manager); 297 | } 298 | } 299 | Err(_) => { 300 | error!("Failed to parse Podman version"); 301 | } 302 | } 303 | } 304 | } else { 305 | info!("Podman not found"); 306 | } 307 | 308 | None 309 | } 310 | 311 | #[derive(Debug)] 312 | #[allow(dead_code)] 313 | pub(crate) enum Container { 314 | Suricata, 315 | EveBox, 316 | } 317 | 318 | impl Container { 319 | pub(crate) fn volumes(&self) -> Vec { 320 | match self { 321 | Container::Suricata => { 322 | vec![format!("{}:/var/log/suricata", SURICATA_VOLUME_LOG)] 323 | } 324 | Container::EveBox => { 325 | vec![ 326 | format!("{}:/var/log/suricata", SURICATA_VOLUME_LOG), 327 | format!("{}:/var/lib/evebox", EVEBOX_VOLUME_LIB), 328 | ] 329 | } 330 | } 331 | } 332 | } 333 | 334 | pub(crate) struct SuricataContainer { 335 | context: Context, 336 | } 337 | 338 | impl SuricataContainer { 339 | pub(crate) fn new(context: Context) -> Self { 340 | Self { context } 341 | } 342 | 343 | pub(crate) fn volumes(&self) -> Vec { 344 | vec![ 345 | format!("{}:/var/log/suricata", SURICATA_VOLUME_LOG), 346 | format!("{}:/var/lib/suricata", SURICATA_VOLUME_LIB), 347 | format!("{}:/var/run/suricata", SURICATA_VOLUME_RUN), 348 | ] 349 | } 350 | 351 | pub(crate) fn run(&self) -> RunCommandBuilder { 352 | let mut builder = RunCommandBuilder::new( 353 | self.context.manager, 354 | self.context.image_name(Container::Suricata), 355 | ); 356 | builder.volumes(&self.volumes()); 357 | builder 358 | } 359 | } 360 | 361 | pub(crate) struct RunCommandBuilder { 362 | manager: ContainerManager, 363 | image: String, 364 | rm: bool, 365 | it: bool, 366 | volumes: Vec, 367 | name: Option, 368 | args: Vec, 369 | } 370 | 371 | impl RunCommandBuilder { 372 | pub(crate) fn new(manager: ContainerManager, image: impl ToString) -> Self { 373 | Self { 374 | manager, 375 | image: image.to_string(), 376 | rm: false, 377 | it: false, 378 | volumes: vec![], 379 | name: None, 380 | args: vec![], 381 | } 382 | } 383 | 384 | pub(crate) fn rm(&mut self) -> &mut Self { 385 | self.rm = true; 386 | self 387 | } 388 | 389 | pub(crate) fn it(&mut self) -> &mut Self { 390 | self.it = true; 391 | self 392 | } 393 | 394 | pub(crate) fn _name(&mut self, name: impl ToString) -> &mut Self { 395 | self.name = Some(name.to_string()); 396 | self 397 | } 398 | 399 | pub(crate) fn _arg(&mut self, arg: impl ToString) -> &mut Self { 400 | self.args.push(arg.to_string()); 401 | self 402 | } 403 | 404 | pub(crate) fn args(&mut self, args: &[impl ToString]) -> &mut Self { 405 | for arg in args { 406 | self.args.push(arg.to_string()); 407 | } 408 | self 409 | } 410 | 411 | pub(crate) fn volumes(&mut self, volumes: &[impl ToString]) -> &mut Self { 412 | for volume in volumes { 413 | self.volumes.push(volume.to_string()); 414 | } 415 | self 416 | } 417 | 418 | pub(crate) fn build(&self) -> Command { 419 | let mut command = self.manager.command(); 420 | command.arg("run"); 421 | if self.it { 422 | command.arg("-it"); 423 | } 424 | if self.rm { 425 | command.arg("--rm"); 426 | } 427 | if let Some(name) = &self.name { 428 | command.arg(format!("--name={}", name)); 429 | } 430 | for volume in &self.volumes { 431 | command.arg(format!("--volume={}", volume)); 432 | } 433 | command.arg(&self.image); 434 | command.args(&self.args); 435 | command 436 | } 437 | } 438 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2024 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use crate::{ 5 | config::Config, 6 | container::{Container, ContainerManager, DEFAULT_EVEBOX_IMAGE, DEFAULT_SURICATA_IMAGE}, 7 | }; 8 | 9 | #[derive(Clone)] 10 | pub(crate) struct Context { 11 | pub config: Config, 12 | pub manager: ContainerManager, 13 | 14 | // Stash some image names for easy access. 15 | pub suricata_image: String, 16 | pub evebox_image: String, 17 | 18 | // Don't apply Suricata fixups. 19 | pub no_fixups: bool, 20 | } 21 | 22 | impl Context { 23 | pub(crate) fn new(config: Config, manager: ContainerManager, no_fixups: bool) -> Self { 24 | let suricata_image = image_name(&config, Container::Suricata); 25 | let evebox_image = image_name(&config, Container::EveBox); 26 | Self { 27 | config, 28 | manager, 29 | suricata_image, 30 | evebox_image, 31 | no_fixups, 32 | } 33 | } 34 | 35 | /// Given a container type, return the image name. 36 | /// 37 | /// Normally this will be the hardcoded default, but we do allow 38 | /// it to be overridden in the configuration. 39 | pub(crate) fn image_name(&self, container: Container) -> String { 40 | image_name(&self.config, container) 41 | } 42 | } 43 | 44 | /// Given a container type, return the image name. 45 | /// 46 | /// Normally this will be the hardcoded default, but we do allow 47 | /// it to be overridden in the configuration. 48 | pub(crate) fn image_name(config: &Config, container: Container) -> String { 49 | match container { 50 | Container::Suricata => config 51 | .suricata 52 | .image 53 | .as_deref() 54 | .unwrap_or(DEFAULT_SURICATA_IMAGE) 55 | .to_string(), 56 | Container::EveBox => config 57 | .evebox 58 | .image 59 | .as_deref() 60 | .unwrap_or(DEFAULT_EVEBOX_IMAGE) 61 | .to_string(), 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/logs.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2023 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ 5 | io::{BufRead, BufReader, Read}, 6 | process::Stdio, 7 | thread, 8 | }; 9 | 10 | use clap::Parser; 11 | use regex::Regex; 12 | 13 | use crate::{context::Context, EVEBOX_CONTAINER_NAME, SURICATA_CONTAINER_NAME}; 14 | 15 | #[derive(Parser, Debug)] 16 | pub(crate) struct LogArgs { 17 | #[arg(short, long, help = "Follow log output")] 18 | follow: bool, 19 | #[arg(help = "Service to display logs for, default = all")] 20 | services: Vec, 21 | } 22 | 23 | pub(crate) fn logs(ctx: &Context, args: LogArgs) { 24 | let containers = [SURICATA_CONTAINER_NAME, EVEBOX_CONTAINER_NAME]; 25 | let max_container_name_len = containers.iter().map(|s| s.len()).max().unwrap_or(0); 26 | let mut handles = vec![]; 27 | 28 | for container in containers { 29 | if !args.services.is_empty() { 30 | match container { 31 | SURICATA_CONTAINER_NAME => { 32 | if !args.services.contains(&"suricata".to_string()) { 33 | continue; 34 | } 35 | } 36 | EVEBOX_CONTAINER_NAME => { 37 | if !args.services.contains(&"evebox".to_string()) { 38 | continue; 39 | } 40 | } 41 | _ => unimplemented!(), 42 | } 43 | } 44 | 45 | let mut command = ctx.manager.command(); 46 | command.arg("logs"); 47 | command.arg("--timestamps"); 48 | if args.follow { 49 | command.arg("--follow"); 50 | } 51 | command.arg(container); 52 | let handle = thread::spawn(move || { 53 | match command 54 | .stdout(Stdio::piped()) 55 | .stderr(Stdio::piped()) 56 | .spawn() 57 | { 58 | Ok(mut output) => { 59 | let mut handles = vec![]; 60 | 61 | let stdout = output.stdout.take().unwrap(); 62 | 63 | let handle = thread::spawn(move || { 64 | log_line_printer( 65 | format!( 66 | "{:width$} | stdout", 67 | container, 68 | width = max_container_name_len 69 | ), 70 | stdout, 71 | ); 72 | }); 73 | handles.push(handle); 74 | 75 | let stderr = output.stderr.take().unwrap(); 76 | let handle = thread::spawn(move || { 77 | log_line_printer( 78 | format!( 79 | "{:width$} | stderr", 80 | container, 81 | width = max_container_name_len 82 | ), 83 | stderr, 84 | ); 85 | }); 86 | handles.push(handle); 87 | 88 | for handle in handles { 89 | let _ = handle.join(); 90 | } 91 | } 92 | Err(err) => { 93 | panic!("{}", err); 94 | } 95 | } 96 | }); 97 | handles.push(handle); 98 | } 99 | 100 | for handle in handles { 101 | let _ = handle.join(); 102 | } 103 | } 104 | 105 | fn log_line_printer(prefix: String, output: R) { 106 | let evebox_ts_pattern = r".....\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}....."; 107 | let re = Regex::new(evebox_ts_pattern).unwrap(); 108 | 109 | let reader = BufReader::new(output).lines(); 110 | for line in reader { 111 | if let Ok(line) = line { 112 | let line = re.replace_all(&line, ""); 113 | println!("{} | {}", prefix, line); 114 | } else { 115 | return; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ 5 | io::{BufRead, BufReader, Read, Write}, 6 | process::{self, Stdio}, 7 | sync::mpsc::Sender, 8 | thread, 9 | }; 10 | 11 | use anyhow::{bail, Result}; 12 | use clap::{Parser, Subcommand}; 13 | use colored::Colorize; 14 | use container::{Container, SuricataContainer}; 15 | use logs::LogArgs; 16 | use tracing::{debug, error, info, Level}; 17 | 18 | use crate::context::Context; 19 | 20 | mod actions; 21 | mod config; 22 | mod container; 23 | mod context; 24 | mod logs; 25 | mod menu; 26 | mod menus; 27 | mod prelude; 28 | mod ruleindex; 29 | mod selfupdate; 30 | mod term; 31 | 32 | const SURICATA_CONTAINER_NAME: &str = "simple-ids-suricata"; 33 | const EVEBOX_CONTAINER_NAME: &str = "simple-ids-evebox"; 34 | 35 | const SURICATA_VOLUME_LOG: &str = "simple-ids-suricata-log"; 36 | const SURICATA_VOLUME_LIB: &str = "simple-ids-suricata-lib"; 37 | const SURICATA_VOLUME_RUN: &str = "simple-ids-suricata-run"; 38 | 39 | const EVEBOX_VOLUME_LIB: &str = "simple-ids-evebox-lib"; 40 | 41 | fn get_clap_style() -> clap::builder::Styles { 42 | clap::builder::Styles::styled() 43 | .header(clap::builder::styling::AnsiColor::Yellow.on_default()) 44 | .usage(clap::builder::styling::AnsiColor::Green.on_default()) 45 | .literal(clap::builder::styling::AnsiColor::Green.on_default()) 46 | .placeholder(clap::builder::styling::AnsiColor::Green.on_default()) 47 | } 48 | 49 | #[derive(Parser, Debug)] 50 | #[command(styles=get_clap_style())] 51 | struct Args { 52 | /// Use Podman, by default Docker is used if found 53 | #[arg(long)] 54 | podman: bool, 55 | 56 | #[arg(long)] 57 | no_root: bool, 58 | 59 | #[arg(long, short, global = true, action = clap::ArgAction::Count)] 60 | verbose: u8, 61 | 62 | #[arg(long, help = "Don't apply Suricata fix-ups")] 63 | no_fixups: bool, 64 | 65 | #[command(subcommand)] 66 | command: Option, 67 | } 68 | 69 | #[derive(Subcommand, Debug)] 70 | enum Commands { 71 | Start { 72 | /// Run in the foreground, mainly for debugging 73 | #[arg(long, short)] 74 | debug: bool, 75 | }, 76 | Stop, 77 | Restart, 78 | Status, 79 | UpdateRules, 80 | Update, 81 | 82 | /// View the container logs 83 | Logs(LogArgs), 84 | 85 | // Commands to jump to specific menus. 86 | ConfigureMenu, 87 | 88 | Menu { 89 | menu: String, 90 | }, 91 | 92 | /// Remove containers and data. 93 | Remove, 94 | } 95 | 96 | fn is_interactive(command: &Option) -> bool { 97 | match command { 98 | Some(command) => match command { 99 | Commands::Start { debug: _ } => false, 100 | Commands::Stop => false, 101 | Commands::Restart => false, 102 | Commands::Status => false, 103 | Commands::UpdateRules => false, 104 | Commands::Update => false, 105 | Commands::Logs(_) => false, 106 | Commands::ConfigureMenu => true, 107 | Commands::Menu { menu: _ } => true, 108 | Commands::Remove => false, 109 | }, 110 | None => true, 111 | } 112 | } 113 | 114 | fn confirm(msg: &str) -> bool { 115 | inquire::Confirm::new(msg).prompt().unwrap_or(false) 116 | } 117 | 118 | fn wizard(context: &mut Context) { 119 | if context.config.suricata.interfaces.is_empty() 120 | && confirm("No network interface configured, configure now?") 121 | { 122 | select_interface(context); 123 | } 124 | } 125 | 126 | fn main() -> Result<()> { 127 | // Mainly for use when developing... 128 | let _ = std::process::Command::new("stty").args(["sane"]).status(); 129 | 130 | let args = Args::parse(); 131 | let is_interactive = is_interactive(&args.command); 132 | 133 | let log_level = if args.verbose > 0 { 134 | Level::DEBUG 135 | } else { 136 | Level::INFO 137 | }; 138 | 139 | if is_interactive { 140 | tracing_subscriber::fmt() 141 | .with_max_level(log_level) 142 | .without_time() 143 | .with_target(false) 144 | .init(); 145 | } else { 146 | tracing_subscriber::fmt().with_max_level(log_level).init(); 147 | } 148 | 149 | let config = config::Config::new(); 150 | 151 | let manager = match container::find_manager(args.podman) { 152 | Some(manager) => manager, 153 | None => { 154 | error!("No container manager found. Docker or Podman must be available."); 155 | error!("See https://evebox.org/runtimes/ for more info."); 156 | std::process::exit(1); 157 | } 158 | }; 159 | if manager.is_podman() && evectl::system::getuid() != 0 && !args.no_root { 160 | error!("The Podman container manager requires running as root"); 161 | std::process::exit(1); 162 | } 163 | info!("Found container manager {manager}"); 164 | 165 | let mut context = Context::new(config, manager, args.no_fixups); 166 | 167 | let prompt_for_update = { 168 | if let Some(Commands::Remove) = args.command { 169 | false 170 | } else { 171 | let mut not_found = false; 172 | if !manager.has_image(&context.suricata_image) { 173 | info!("Suricata image {} not found", &context.suricata_image); 174 | not_found = true; 175 | } 176 | if !manager.has_image(&context.evebox_image) { 177 | info!("EveBox image {} not found", &context.evebox_image); 178 | not_found = true 179 | } 180 | not_found 181 | } 182 | }; 183 | 184 | if prompt_for_update { 185 | if let Ok(true) = 186 | inquire::Confirm::new("Required container images not found, download now?") 187 | .with_default(true) 188 | .prompt() 189 | { 190 | if !update(&context) { 191 | error!("Failed to downloading container images"); 192 | evectl::prompt::enter(); 193 | } 194 | } 195 | } 196 | 197 | if let Some(command) = args.command { 198 | let code = match command { 199 | Commands::Start { debug: detach } => command_start(&context, detach), 200 | Commands::Stop => { 201 | if stop(&context) { 202 | 0 203 | } else { 204 | 1 205 | } 206 | } 207 | Commands::Restart => { 208 | stop(&context); 209 | command_start(&context, true) 210 | } 211 | Commands::Status => command_status(&context), 212 | Commands::UpdateRules => { 213 | if actions::update_rules(&context).is_ok() { 214 | 0 215 | } else { 216 | 1 217 | } 218 | } 219 | Commands::Update => { 220 | if update(&context) { 221 | 0 222 | } else { 223 | 1 224 | } 225 | } 226 | Commands::ConfigureMenu => { 227 | menu::configure::main(&mut context)?; 228 | 0 229 | } 230 | Commands::Logs(args) => { 231 | logs::logs(&context, args); 232 | 0 233 | } 234 | Commands::Menu { menu } => match menu.as_str() { 235 | "configure.advanced" => { 236 | menu::advanced::advanced_menu(&mut context); 237 | 0 238 | } 239 | _ => panic!("Unhandled menu: {}", menu), 240 | }, 241 | Commands::Remove => { 242 | remove(&context); 243 | 0 244 | } 245 | }; 246 | std::process::exit(code); 247 | } else { 248 | menu_main(context)?; 249 | } 250 | 251 | Ok(()) 252 | } 253 | 254 | fn process_output_handler( 255 | output: R, 256 | label: &'static str, 257 | tx: Sender, 258 | ) { 259 | let reader = BufReader::new(output).lines(); 260 | thread::spawn(move || { 261 | for line in reader { 262 | if let Ok(line) = line { 263 | // Add some coloring to the Suricata output as it 264 | // doesn't add its own color when writing to a 265 | // non-interactive terminal. 266 | let line = if line.starts_with("Info") { 267 | line.green().to_string() 268 | } else if line.starts_with("Error") { 269 | line.red().to_string() 270 | } else if line.starts_with("Notice") { 271 | line.magenta().to_string() 272 | } else if line.starts_with("Warn") { 273 | line.yellow().to_string() 274 | } else { 275 | line.to_string() 276 | }; 277 | let mut stdout = std::io::stdout().lock(); 278 | let _ = writeln!(&mut stdout, "{}: {}", label, line); 279 | let _ = stdout.flush(); 280 | } else { 281 | debug!("{}: EOF", label); 282 | break; 283 | } 284 | } 285 | let _ = tx.send(true); 286 | }); 287 | } 288 | 289 | /// Run when "start" is run from the command line. 290 | fn command_start(context: &Context, debug: bool) -> i32 { 291 | if debug { 292 | start_foreground(context) 293 | } else { 294 | start(context); 295 | 0 296 | } 297 | } 298 | 299 | /// Start Simple-IDS in the foreground. 300 | /// 301 | /// Typically not done from the menus but instead the command line. 302 | fn start_foreground(context: &Context) -> i32 { 303 | context.manager.quiet_rm(SURICATA_CONTAINER_NAME); 304 | context.manager.quiet_rm(EVEBOX_CONTAINER_NAME); 305 | 306 | let (tx, rx) = std::sync::mpsc::channel::(); 307 | let mut suricata_command = match build_suricata_command(context, false, true) { 308 | Ok(command) => command, 309 | Err(err) => { 310 | error!("Invalid Suricata configuration: {}", err); 311 | return 1; 312 | } 313 | }; 314 | 315 | let mut suricata_process = match suricata_command 316 | .stdout(Stdio::piped()) 317 | .stderr(Stdio::piped()) 318 | .spawn() 319 | { 320 | Ok(process) => process, 321 | Err(err) => { 322 | error!("Failed to spawn Suricata process: {}", err); 323 | return 1; 324 | } 325 | }; 326 | 327 | let mut evebox_command = build_evebox_command(context, false); 328 | let mut evebox_process = match evebox_command 329 | .stdout(Stdio::piped()) 330 | .stderr(Stdio::piped()) 331 | .spawn() 332 | { 333 | Ok(process) => process, 334 | Err(err) => { 335 | error!("Failed to spawn EveBox process: {}", err); 336 | return 1; 337 | } 338 | }; 339 | 340 | { 341 | let tx = tx.clone(); 342 | if let Err(err) = ctrlc::set_handler(move || { 343 | info!("Received Ctrl-C, stopping containers"); 344 | let _ = tx.send(true); 345 | }) { 346 | error!("Failed to setup Ctrl-C handler: {}", err); 347 | } 348 | } 349 | 350 | let now = std::time::Instant::now(); 351 | loop { 352 | if !context.manager.is_running(SURICATA_CONTAINER_NAME) { 353 | if now.elapsed().as_secs() > 3 { 354 | error!("Timed out waiting for the Suricata container to start running, not starting log rotation"); 355 | break; 356 | } else { 357 | continue; 358 | } 359 | } 360 | 361 | if let Err(err) = start_suricata_logrotate(context) { 362 | error!("Failed to start Suricata log rotation: {err}"); 363 | } 364 | break; 365 | } 366 | 367 | if let Some(output) = suricata_process.stdout.take() { 368 | process_output_handler(output, "suricata", tx.clone()); 369 | } 370 | if let Some(output) = suricata_process.stderr.take() { 371 | process_output_handler(output, "suricata", tx.clone()); 372 | } 373 | 374 | if let Some(output) = evebox_process.stdout.take() { 375 | process_output_handler(output, "evebox", tx.clone()); 376 | } 377 | if let Some(output) = evebox_process.stderr.take() { 378 | process_output_handler(output, "evebox", tx.clone()); 379 | } 380 | 381 | let _ = rx.recv(); 382 | let _ = context.manager.stop(SURICATA_CONTAINER_NAME, None); 383 | let _ = context.manager.stop(EVEBOX_CONTAINER_NAME, Some("SIGINT")); 384 | let status = suricata_process.wait(); 385 | debug!("Suricata exit status: {:?}", status); 386 | let status = evebox_process.wait(); 387 | debug!("EveBox exit status: {:?}", status); 388 | 0 389 | } 390 | 391 | fn stop(context: &Context) -> bool { 392 | let mut ok = true; 393 | 394 | if context.manager.container_exists(SURICATA_CONTAINER_NAME) { 395 | info!("Stopping {SURICATA_CONTAINER_NAME}"); 396 | if let Err(err) = context.manager.stop(SURICATA_CONTAINER_NAME, None) { 397 | error!( 398 | "Failed to stop container {SURICATA_CONTAINER_NAME}: {}", 399 | err 400 | ); 401 | ok = false; 402 | } 403 | context.manager.quiet_rm(SURICATA_CONTAINER_NAME); 404 | } else { 405 | info!("Container {SURICATA_CONTAINER_NAME} is not running"); 406 | } 407 | if context.manager.container_exists(EVEBOX_CONTAINER_NAME) { 408 | info!("Stopping {EVEBOX_CONTAINER_NAME}"); 409 | if let Err(err) = context.manager.stop(EVEBOX_CONTAINER_NAME, Some("SIGINT")) { 410 | error!("Failed to stop container {EVEBOX_CONTAINER_NAME}: {}", err); 411 | ok = false; 412 | } 413 | context.manager.quiet_rm(EVEBOX_CONTAINER_NAME); 414 | } else { 415 | info!("Container {EVEBOX_CONTAINER_NAME} is not running"); 416 | } 417 | 418 | ok 419 | } 420 | 421 | fn command_status(context: &Context) -> i32 { 422 | let mut code = 0; 423 | match context.manager.state(SURICATA_CONTAINER_NAME) { 424 | Ok(state) => info!("suricata: {}", state.status), 425 | Err(err) => { 426 | let err = format!("{}", err); 427 | error!("suricata: {}", err.trim_end()); 428 | code = 1; 429 | } 430 | } 431 | match context.manager.state(EVEBOX_CONTAINER_NAME) { 432 | Ok(state) => info!("evebox: {}", state.status), 433 | Err(err) => { 434 | let err = format!("{}", err); 435 | error!("evebox: {}", err.trim_end()); 436 | code = 1; 437 | } 438 | } 439 | code 440 | } 441 | 442 | fn guess_evebox_url(context: &Context) -> String { 443 | let scheme = if context.config.evebox.no_tls { 444 | "http" 445 | } else { 446 | "https" 447 | }; 448 | 449 | if !context.config.evebox.allow_remote { 450 | format!("{}://127.0.0.1:5636", scheme) 451 | } else { 452 | let interfaces = match evectl::system::get_interfaces() { 453 | Ok(interfaces) => interfaces, 454 | Err(err) => { 455 | error!("Failed to get system interfaces: {err}"); 456 | return format!("{}://127.0.0.1:5636", scheme); 457 | } 458 | }; 459 | 460 | // Find the first interface that is up... 461 | let mut addr: Option<&String> = None; 462 | 463 | for interface in &interfaces { 464 | // Only consider IPv4 addresses for now. 465 | if interface.addr4.is_empty() { 466 | continue; 467 | } 468 | if interface.name == "lo" && addr.is_none() { 469 | addr = interface.addr4.first(); 470 | } else if interface.status == "UP" { 471 | match addr { 472 | Some(previous) => { 473 | if previous.starts_with("127") { 474 | addr = interface.addr4.first(); 475 | } 476 | } 477 | None => { 478 | addr = interface.addr4.first(); 479 | } 480 | } 481 | } 482 | } 483 | 484 | format!( 485 | "{}://{}:5636", 486 | scheme, 487 | addr.unwrap_or(&"127.0.0.1".to_string()) 488 | ) 489 | } 490 | } 491 | 492 | fn menu_main(mut context: Context) -> Result<()> { 493 | let mut first = true; 494 | loop { 495 | term::title("Simple-IDS: Main Menu"); 496 | 497 | if first { 498 | first = false; 499 | wizard(&mut context); 500 | } 501 | 502 | let evebox_url = guess_evebox_url(&context); 503 | 504 | let suricata_state = context 505 | .manager 506 | .state(SURICATA_CONTAINER_NAME) 507 | .map(|state| state.status) 508 | .unwrap_or_else(|_| "not running".to_string()); 509 | let evebox_state = context 510 | .manager 511 | .state(EVEBOX_CONTAINER_NAME) 512 | .map(|state| { 513 | if state.status == "running" { 514 | format!("{} {}", state.status, evebox_url,) 515 | } else { 516 | state.status 517 | } 518 | }) 519 | .unwrap_or_else(|_| "not running".to_string()); 520 | 521 | let running = context.manager.is_running(SURICATA_CONTAINER_NAME) 522 | || context.manager.is_running(EVEBOX_CONTAINER_NAME); 523 | 524 | println!( 525 | "{} Suricata: {} {} EveBox: {}", 526 | ">>>".cyan(), 527 | suricata_state, 528 | ">>>".cyan(), 529 | evebox_state 530 | ); 531 | println!(); 532 | 533 | let interface = context 534 | .config 535 | .suricata 536 | .interfaces 537 | .first() 538 | .map(String::from) 539 | .unwrap_or_default(); 540 | 541 | let mut selections = evectl::prompt::Selections::with_index(); 542 | selections.push("refresh", "Refresh Status"); 543 | if running { 544 | selections.push("restart", "Restart"); 545 | selections.push("stop", "Stop"); 546 | } else { 547 | selections.push("start", "Start"); 548 | } 549 | selections.push("interface", format!("Select Interface [{interface}]")); 550 | selections.push("update-rules", "Update Rules"); 551 | selections.push("update", "Update"); 552 | selections.push("configure", "Configure"); 553 | selections.push("other", "Other"); 554 | selections.push("exit", "Exit"); 555 | 556 | let response = inquire::Select::new("Select a menu option", selections.to_vec()) 557 | .with_page_size(12) 558 | .prompt(); 559 | match response { 560 | Ok(selection) => match selection.tag { 561 | "refresh" => {} 562 | "start" => { 563 | if !start(&context) { 564 | evectl::prompt::enter(); 565 | } 566 | } 567 | "stop" => { 568 | if !stop(&context) { 569 | evectl::prompt::enter(); 570 | } 571 | } 572 | "restart" => { 573 | stop(&context); 574 | if !start(&context) { 575 | evectl::prompt::enter(); 576 | } 577 | } 578 | "interface" => select_interface(&mut context), 579 | "update" => { 580 | update(&context); 581 | evectl::prompt::enter(); 582 | } 583 | "other" => menus::other(&context), 584 | "configure" => menu::configure::main(&mut context)?, 585 | "update-rules" => { 586 | if let Err(err) = actions::update_rules(&context) { 587 | error!("{}", err); 588 | } 589 | evectl::prompt::enter(); 590 | } 591 | "exit" => break, 592 | _ => panic!("Unhandled selection: {}", selection.tag), 593 | }, 594 | Err(_) => break, 595 | } 596 | } 597 | 598 | Ok(()) 599 | } 600 | 601 | /// Returns true if everything started successfully, otherwise false 602 | /// is return. 603 | fn start(context: &Context) -> bool { 604 | let mut ok = true; 605 | info!("Starting Suricata"); 606 | if let Err(err) = start_suricata_detached(context) { 607 | error!("Failed to start Suricata: {}", err); 608 | ok = false; 609 | } 610 | info!("Starting EveBox"); 611 | if let Err(err) = start_evebox_detached(context) { 612 | error!("Failed to start EveBox: {}", err); 613 | ok = false; 614 | } 615 | ok 616 | } 617 | 618 | fn build_suricata_command(context: &Context, detached: bool, stubs: bool) -> Result { 619 | let interface = match context.config.suricata.interfaces.first() { 620 | Some(interface) => interface, 621 | None => bail!("no network interface set"), 622 | }; 623 | 624 | let mut args = ArgBuilder::from(&[ 625 | "run", 626 | "--name", 627 | SURICATA_CONTAINER_NAME, 628 | "--net=host", 629 | "--cap-add=sys_nice", 630 | "--cap-add=net_admin", 631 | "--cap-add=net_raw", 632 | ]); 633 | 634 | if detached { 635 | args.add("-d"); 636 | } 637 | 638 | if !context.no_fixups && stubs { 639 | // Write out af-packet stub for fixed af-packet. 640 | let path = std::env::current_dir()?.join("af-packet.yaml"); 641 | evectl::configs::write_af_packet_stub(&path)?; 642 | args.add(format!( 643 | "--volume={}:/config/af-packet.yaml", 644 | path.display() 645 | )); 646 | } 647 | 648 | for volume in SuricataContainer::new(context.clone()).volumes() { 649 | args.add(format!("--volume={}", volume)); 650 | } 651 | 652 | args.add(context.image_name(Container::Suricata)); 653 | args.extend(&["-v", "-i", interface]); 654 | 655 | if !context.no_fixups || stubs { 656 | args.add("--include"); 657 | args.add("/config/af-packet.yaml"); 658 | } 659 | 660 | if let Some(bpf) = &context.config.suricata.bpf { 661 | args.add(bpf); 662 | } 663 | 664 | let mut command = context.manager.command(); 665 | command.args(&args.args); 666 | Ok(command) 667 | } 668 | 669 | fn suricata_dump_config(context: &Context) -> Result> { 670 | context.manager.quiet_rm(SURICATA_CONTAINER_NAME); 671 | let mut command = build_suricata_command(context, false, false)?; 672 | command.arg("--dump-config"); 673 | let output = command.output()?; 674 | if output.status.success() { 675 | let stdout = std::str::from_utf8(&output.stdout)?; 676 | let lines: Vec = stdout.lines().map(|s| s.to_string()).collect(); 677 | Ok(lines) 678 | } else { 679 | bail!("Failed to run --dump-config for Suricata") 680 | } 681 | } 682 | 683 | fn start_suricata_detached(context: &Context) -> Result<()> { 684 | let config = suricata_dump_config(context)?; 685 | let mut set_args: Vec = vec![ 686 | "app-layer.protocols.tls.ja4-fingerprints=true".to_string(), 687 | "app-layer.protocols.quic.ja4-fingerprints=true".to_string(), 688 | ]; 689 | let patterns = &[ 690 | regex::Regex::new(r"(outputs\.\d+\.eve-log\.types\.\d+\.tls)\s")?, 691 | regex::Regex::new(r"(outputs\.\d+\.eve-log\.types\.\d+\.quic)\s")?, 692 | ]; 693 | for line in &config { 694 | for r in patterns { 695 | if let Some(c) = r.captures(line) { 696 | set_args.push(format!("{}.ja4=true", &c[1])); 697 | } 698 | } 699 | } 700 | 701 | context.manager.quiet_rm(SURICATA_CONTAINER_NAME); 702 | let mut command = build_suricata_command(context, true, true)?; 703 | for s in &set_args { 704 | command.arg("--set"); 705 | command.arg(s); 706 | } 707 | let output = command.output()?; 708 | if !output.status.success() { 709 | bail!(String::from_utf8_lossy(&output.stderr).to_string()); 710 | } 711 | 712 | if let Err(err) = start_suricata_logrotate(context) { 713 | error!("{}", err); 714 | } 715 | Ok(()) 716 | } 717 | 718 | fn start_suricata_logrotate(context: &Context) -> Result<()> { 719 | info!("Starting Suricata log rotation"); 720 | match context 721 | .manager 722 | .command() 723 | .args([ 724 | "exec", 725 | "-d", 726 | SURICATA_CONTAINER_NAME, 727 | "bash", 728 | "-c", 729 | "while true; do logrotate -v /etc/logrotate.d/suricata > /tmp/last_logrotate 2>&1; sleep 600; done", 730 | ]) 731 | .output() 732 | { 733 | Ok(output) => { 734 | if !output.status.success() { 735 | bail!(String::from_utf8_lossy(&output.stderr).to_string()); 736 | } 737 | } 738 | Err(err) => bail!("Failed to initialize log rotation: {err}"), 739 | } 740 | Ok(()) 741 | } 742 | 743 | fn build_evebox_command(context: &Context, daemon: bool) -> process::Command { 744 | let mut args = ArgBuilder::from(&[ 745 | "run", 746 | "--name", 747 | EVEBOX_CONTAINER_NAME, 748 | // "--restart=unless-stopped", 749 | ]); 750 | if context.config.evebox.allow_remote { 751 | args.add("--publish=5636:5636"); 752 | } else { 753 | args.add("--publish=127.0.0.1:5636:5636"); 754 | } 755 | if daemon { 756 | args.add("-d"); 757 | } 758 | 759 | for volume in Container::EveBox.volumes() { 760 | args.add(format!("--volume={}", volume)); 761 | } 762 | 763 | args.add(context.image_name(Container::EveBox)); 764 | args.extend(&["evebox", "server"]); 765 | 766 | if context.config.evebox.no_tls { 767 | args.add("--no-tls"); 768 | } 769 | 770 | if context.config.evebox.no_auth { 771 | args.add("--no-auth"); 772 | } 773 | 774 | args.extend(&["--host=[::0]", "--sqlite", "/var/log/suricata/eve.json"]); 775 | let mut command = context.manager.command(); 776 | command.args(&args.args); 777 | command 778 | } 779 | 780 | fn start_evebox_detached(context: &Context) -> Result<()> { 781 | actions::start_evebox(context) 782 | } 783 | 784 | fn select_interface(context: &mut Context) { 785 | let interfaces = evectl::system::get_interfaces().unwrap(); 786 | let current_if = context.config.suricata.interfaces.first(); 787 | let index = interfaces 788 | .iter() 789 | .position(|interface| Some(&interface.name) == current_if) 790 | .unwrap_or(0); 791 | 792 | let mut selections = evectl::prompt::Selections::with_index(); 793 | 794 | for interface in &interfaces { 795 | let address = interface 796 | .addr4 797 | .first() 798 | .map(|s| format!("-- {}", s.green().italic())) 799 | .unwrap_or("".to_string()); 800 | selections.push( 801 | interface.name.to_string(), 802 | format!("{} {}", &interface.name, address), 803 | ); 804 | } 805 | 806 | match inquire::Select::new("Select interface", selections.to_vec()) 807 | .with_starting_cursor(index) 808 | .with_page_size(12) 809 | .prompt() 810 | { 811 | Err(_) => {} 812 | Ok(selection) => { 813 | context.config.suricata.interfaces = vec![selection.tag.to_string()]; 814 | let _ = context.config.save(); 815 | } 816 | } 817 | } 818 | 819 | fn update(context: &Context) -> bool { 820 | let mut ok = true; 821 | for image in [ 822 | context.image_name(Container::Suricata), 823 | context.image_name(Container::EveBox), 824 | ] { 825 | if let Err(err) = context.manager.pull(&image) { 826 | error!("Failed to pull {image}: {err}"); 827 | ok = false; 828 | } 829 | } 830 | if let Err(err) = selfupdate::self_update() { 831 | error!("Failed to update Simple-IDS: {err}"); 832 | ok = false; 833 | } 834 | ok 835 | } 836 | 837 | fn remove(context: &Context) { 838 | info!("Stopping Suricata..."); 839 | if let Err(err) = context.manager.stop(SURICATA_CONTAINER_NAME, None) { 840 | error!("Failed to stop Suricata: {}", err.to_string().trim()); 841 | } 842 | info!("Stopping EveBox..."); 843 | if let Err(err) = context.manager.stop(EVEBOX_CONTAINER_NAME, None) { 844 | error!("Failed to stop EveBox: {}", err.to_string().trim()); 845 | } 846 | info!("Removing Suricata container"); 847 | context.manager.quiet_rm(SURICATA_CONTAINER_NAME); 848 | info!("Removing EveBox container"); 849 | context.manager.quiet_rm(EVEBOX_CONTAINER_NAME); 850 | 851 | let volumes = [ 852 | "simple-ids-evebox-lib", 853 | "simple-ids-suricata-lib", 854 | "simple-ids-suricata-log", 855 | "simple-ids-suricata-run", 856 | ]; 857 | for volume in &volumes { 858 | info!("Removing volume {volume}"); 859 | match context 860 | .manager 861 | .command() 862 | .args(["volume", "rm", volume]) 863 | .status() 864 | { 865 | Ok(_status) => {} 866 | Err(err) => { 867 | error!("Failed to remove volume {volume}: {err}"); 868 | } 869 | } 870 | } 871 | 872 | for image in [ 873 | context.image_name(Container::Suricata), 874 | context.image_name(Container::EveBox), 875 | ] { 876 | info!("Removing image {image}"); 877 | match context 878 | .manager 879 | .command() 880 | .args(["image", "rmi", &image]) 881 | .status() 882 | { 883 | Ok(_status) => {} 884 | Err(err) => { 885 | error!("Failed to remove image {image}: {err}"); 886 | } 887 | } 888 | } 889 | 890 | println!(); 891 | info!("Simple-IDS containers and data have been removed."); 892 | info!("You may now remove the Simple-IDS program and configuration file (simple-ids.toml)."); 893 | } 894 | 895 | /// Utility for building arguments for commands. 896 | #[derive(Debug, Default)] 897 | struct ArgBuilder { 898 | args: Vec, 899 | } 900 | 901 | impl ArgBuilder { 902 | fn new() -> Self { 903 | Self::default() 904 | } 905 | 906 | fn from>(args: &[S]) -> Self { 907 | let mut builder = Self::default(); 908 | builder.extend(args); 909 | builder 910 | } 911 | 912 | fn add(&mut self, arg: impl Into) -> &mut Self { 913 | self.args.push(arg.into()); 914 | self 915 | } 916 | 917 | fn extend>(&mut self, args: &[S]) -> &mut Self { 918 | for arg in args { 919 | self.args.push(arg.as_ref().to_string()); 920 | } 921 | self 922 | } 923 | } 924 | -------------------------------------------------------------------------------- /src/menu/advanced.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2023 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use crate::{container::Container, context::Context}; 5 | 6 | pub(crate) fn advanced_menu(context: &mut Context) { 7 | loop { 8 | crate::term::title("Simple-IDS: Advanced Configuration"); 9 | 10 | let suricata_image_name = context.image_name(Container::Suricata); 11 | let evebox_image_name = context.image_name(Container::EveBox); 12 | 13 | let selections = evectl::prompt::Selections::new() 14 | .push( 15 | "suricata", 16 | format!("Suricata Container: {}", suricata_image_name), 17 | ) 18 | .push("evebox", format!("EveBox Container: {}", evebox_image_name)) 19 | .push("return", "Return") 20 | .to_vec(); 21 | 22 | match inquire::Select::new("Select container to configure", selections).prompt() { 23 | Ok(selection) => match selection.tag { 24 | "suricata" => { 25 | set_suricata_image(context, &suricata_image_name); 26 | } 27 | "evebox" => { 28 | set_evebox_image(context, &evebox_image_name); 29 | } 30 | "return" => return, 31 | _ => unimplemented!(), 32 | }, 33 | Err(_) => return, 34 | } 35 | } 36 | } 37 | 38 | fn set_suricata_image(context: &mut Context, default: &str) { 39 | match inquire::Text::new("Enter Suricata image name") 40 | .with_default(default) 41 | .with_help_message("Enter to keep current, ESC to reset to default") 42 | .prompt() 43 | { 44 | Ok(image) => { 45 | context.config.suricata.image = Some(image); 46 | } 47 | Err(_) => { 48 | context.config.suricata.image = None; 49 | } 50 | } 51 | context.config.save().unwrap(); 52 | } 53 | 54 | fn set_evebox_image(context: &mut Context, default: &str) { 55 | match inquire::Text::new("Enter EveBox image name") 56 | .with_default(default) 57 | .with_help_message("Enter to keep current, ESC to reset to default") 58 | .prompt() 59 | { 60 | Ok(image) => { 61 | context.config.evebox.image = Some(image); 62 | } 63 | Err(_) => { 64 | context.config.evebox.image = None; 65 | } 66 | } 67 | context.config.save().unwrap(); 68 | } 69 | -------------------------------------------------------------------------------- /src/menu/configure.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use anyhow::Result; 5 | 6 | use crate::{context::Context, term}; 7 | 8 | /// Main configure menu. 9 | pub(crate) fn main(context: &mut Context) -> Result<()> { 10 | loop { 11 | term::title("Simple-IDS: Configure"); 12 | 13 | let mut selections = evectl::prompt::Selections::with_index(); 14 | selections.push("suricata", "Suricata Configuration"); 15 | selections.push("suricata-update", "Suricata-Update Configuration"); 16 | selections.push("evebox", "EveBox Configuration"); 17 | selections.push("advanced", "Advanced"); 18 | selections.push("return", "Return"); 19 | 20 | match inquire::Select::new("Select menu option", selections.to_vec()).prompt() { 21 | Ok(selection) => match selection.tag { 22 | "suricata" => crate::menu::suricata::menu(context), 23 | "suricata-update" => crate::menu::suricata_update::menu(context)?, 24 | "evebox" => crate::menu::evebox::configure(context), 25 | "advanced" => crate::menu::advanced::advanced_menu(context), 26 | "return" => return Ok(()), 27 | _ => unimplemented!(), 28 | }, 29 | Err(_) => break, 30 | } 31 | } 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /src/menu/evebox.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2023 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use tracing::{error, info, warn}; 5 | 6 | use crate::{ 7 | actions, config::EveBoxConfig, container::Container, context::Context, term, ArgBuilder, 8 | EVEBOX_CONTAINER_NAME, 9 | }; 10 | 11 | pub(crate) fn configure(context: &mut Context) { 12 | let original_config = context.config.clone(); 13 | let mut restart_required; 14 | loop { 15 | term::title("Simple-IDS: Configure EveBox"); 16 | 17 | let is_running = context.manager.is_running(EVEBOX_CONTAINER_NAME); 18 | restart_required = is_running && original_config != context.config; 19 | 20 | let mut selections = evectl::prompt::Selections::with_index(); 21 | if context.config.evebox.allow_remote { 22 | selections.push("disable-remote", "Disable Remote Access"); 23 | } else { 24 | selections.push("enable-remote", "Enable Remote Access"); 25 | } 26 | selections.push( 27 | "toggle-tls", 28 | format!( 29 | "Toggle TLS (Currently {})", 30 | if context.config.evebox.no_tls { 31 | "disabled" 32 | } else { 33 | "enabled" 34 | } 35 | ), 36 | ); 37 | selections.push( 38 | "toggle-auth", 39 | format!( 40 | "Toggle authentication (Currently {})", 41 | if context.config.evebox.no_auth { 42 | "disabled" 43 | } else { 44 | "enabled" 45 | } 46 | ), 47 | ); 48 | selections.push("reset-password", "Reset Admin Password"); 49 | selections.push( 50 | "return", 51 | if restart_required { 52 | "Restart and Return" 53 | } else { 54 | "Return" 55 | }, 56 | ); 57 | 58 | if let Ok(selection) = 59 | inquire::Select::new("Select menu option", selections.to_vec()).prompt() 60 | { 61 | match selection.tag { 62 | "toggle-tls" => toggle_tls(&mut context.config.evebox), 63 | "toggle-auth" => toggle_auth(&mut context.config.evebox), 64 | "reset-password" => reset_password(context), 65 | "enable-remote" => enable_remote_access(context), 66 | "disable-remote" => disable_remote_access(context), 67 | "return" => break, 68 | _ => {} 69 | } 70 | } else { 71 | break; 72 | } 73 | } 74 | 75 | if original_config != context.config { 76 | info!("Saving configuration changes"); 77 | if let Err(err) = context.config.save() { 78 | error!("Failed to save configuration changes: {err}"); 79 | evectl::prompt::enter(); 80 | } 81 | } 82 | if restart_required { 83 | info!("Restarting Evebox"); 84 | let _ = actions::stop_evebox(context); 85 | let _ = actions::start_evebox(context); 86 | } 87 | } 88 | 89 | fn toggle_tls(config: &mut EveBoxConfig) { 90 | if config.no_tls { 91 | config.no_tls = false; 92 | } else { 93 | if config.allow_remote { 94 | match inquire::Confirm::new( 95 | "Remote access is enabled, are you sure you want to disable TLS", 96 | ) 97 | .with_default(false) 98 | .prompt() 99 | { 100 | Ok(true) => {} 101 | Ok(false) | Err(_) => return, 102 | } 103 | } 104 | config.no_tls = true; 105 | } 106 | } 107 | 108 | fn toggle_auth(config: &mut EveBoxConfig) { 109 | if config.no_auth { 110 | config.no_tls = false; 111 | } else { 112 | if config.allow_remote { 113 | match inquire::Confirm::new( 114 | "Remote access is enabled, are you sure you want to disable authentication", 115 | ) 116 | .with_default(false) 117 | .prompt() 118 | { 119 | Ok(true) => {} 120 | Ok(false) | Err(_) => return, 121 | } 122 | } 123 | config.no_auth = true; 124 | } 125 | } 126 | 127 | fn enable_remote_access(context: &mut Context) { 128 | if context.config.evebox.no_tls { 129 | warn!("Enabling TLS"); 130 | context.config.evebox.no_tls = false; 131 | } 132 | if context.config.evebox.no_auth { 133 | warn!("Enabling authentication"); 134 | context.config.evebox.no_auth = false; 135 | } 136 | context.config.evebox.allow_remote = true; 137 | 138 | if let Ok(true) = inquire::Confirm::new("Do you wish to reset the admin password") 139 | .with_default(true) 140 | .prompt() 141 | { 142 | reset_password(context); 143 | } 144 | 145 | if context.manager.is_running(EVEBOX_CONTAINER_NAME) { 146 | info!("Restarting EveBox"); 147 | let _ = actions::stop_evebox(context); 148 | let _ = actions::start_evebox(context); 149 | } 150 | evectl::prompt::enter_with_prefix("EveBox remote access has been enabled"); 151 | } 152 | 153 | fn disable_remote_access(context: &mut Context) { 154 | context.config.evebox.allow_remote = false; 155 | } 156 | 157 | fn reset_password(context: &mut Context) { 158 | let image = context.image_name(Container::EveBox); 159 | let mut args = ArgBuilder::new(); 160 | args.add("run"); 161 | for volume in Container::EveBox.volumes() { 162 | args.add("-v"); 163 | args.add(volume); 164 | } 165 | args.extend(&[ 166 | "--rm", "-it", &image, "evebox", "config", "users", "rm", "admin", 167 | ]); 168 | let _ = context.manager.command().args(&args.args).status(); 169 | 170 | let mut args = ArgBuilder::new(); 171 | args.add("run"); 172 | for volume in Container::EveBox.volumes() { 173 | args.add("-v"); 174 | args.add(volume); 175 | } 176 | args.extend(&[ 177 | "--rm", 178 | "-it", 179 | &image, 180 | "evebox", 181 | "config", 182 | "users", 183 | "add", 184 | "--username", 185 | "admin", 186 | ]); 187 | let _ = context.manager.command().args(&args.args).status(); 188 | } 189 | -------------------------------------------------------------------------------- /src/menu/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | pub(crate) mod advanced; 5 | pub(crate) mod configure; 6 | pub(crate) mod evebox; 7 | pub(crate) mod suricata; 8 | pub(crate) mod suricata_update; 9 | -------------------------------------------------------------------------------- /src/menu/suricata.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2024 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use crate::{context::Context, term}; 5 | 6 | pub(crate) fn menu(context: &mut Context) { 7 | loop { 8 | term::title("Simple IDS: Configure Suricata"); 9 | 10 | let current_bpf = if let Some(bpf) = &context.config.suricata.bpf { 11 | format!(" [{}]", bpf) 12 | } else { 13 | "".to_string() 14 | }; 15 | 16 | let mut selections = evectl::prompt::Selections::with_index(); 17 | selections.push("bpf-filter", format!("BPF filter{}", current_bpf)); 18 | selections.push("return", "Return"); 19 | 20 | match inquire::Select::new("Select an option", selections.to_vec()).prompt() { 21 | Ok(selection) => match selection.tag { 22 | "bpf-filter" => set_bpf_filter(context), 23 | _ => return, 24 | }, 25 | Err(_) => return, 26 | } 27 | } 28 | } 29 | 30 | fn set_bpf_filter(context: &mut Context) { 31 | let default = context 32 | .config 33 | .suricata 34 | .bpf 35 | .as_ref() 36 | .map(|s| s.to_string()) 37 | .unwrap_or("".to_string()); 38 | if let Ok(filter) = inquire::Text::new("Enter BPF filter") 39 | .with_default(&default) 40 | .prompt() 41 | { 42 | context.config.suricata.bpf = Some(filter); 43 | context.config.save().unwrap(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/menu/suricata_update.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use crate::{ 5 | container::{CommandExt, Container, RunCommandBuilder}, 6 | context::Context, 7 | term, 8 | }; 9 | use anyhow::Result; 10 | use colored::Colorize; 11 | use std::{io::Write, path::PathBuf}; 12 | use tracing::error; 13 | 14 | /// Suricata configure menu. 15 | pub(crate) fn menu(context: &mut Context) -> Result<()> { 16 | loop { 17 | term::title("Simple-IDS: Configure Suricata-Update"); 18 | 19 | let selections = evectl::prompt::Selections::with_index() 20 | .push("enable-conf", "Edit enable.conf") 21 | .push("disable-conf", "Edit disable.conf") 22 | .push("modify-conf", "Edit modify.conf") 23 | .push("enable-ruleset", "Enable a Ruleset") 24 | .push("disable-ruleset", "Disable a Ruleset") 25 | .push("return", "Return") 26 | .to_vec(); 27 | 28 | match inquire::Select::new("Select menu option", selections).prompt() { 29 | Ok(selection) => match selection.tag { 30 | "disable-conf" => edit_file(context, "disable.conf"), 31 | "enable-conf" => edit_file(context, "enable.conf"), 32 | "modify-conf" => edit_file(context, "modify.conf"), 33 | "enable-ruleset" => enable_ruleset(context).unwrap(), 34 | "disable-ruleset" => disable_ruleset(context).unwrap(), 35 | _ => break, 36 | }, 37 | Err(_) => break, 38 | } 39 | } 40 | 41 | Ok(()) 42 | } 43 | 44 | fn disable_ruleset(context: &Context) -> Result<()> { 45 | let enabled = crate::actions::get_enabled_ruleset(context).unwrap(); 46 | if enabled.is_empty() { 47 | evectl::prompt::enter_with_prefix("No rulesets enabled"); 48 | return Ok(()); 49 | } 50 | 51 | let index = crate::actions::load_rule_index(context).unwrap(); 52 | let mut selections = evectl::prompt::Selections::new(); 53 | 54 | for (id, source) in &index.sources { 55 | if enabled.contains(id) { 56 | let message = format!("{}: {}", id, source.summary.green().italic()); 57 | selections.push(id, message); 58 | } 59 | } 60 | 61 | if let Ok(selection) = inquire::Select::new( 62 | "Choose a ruleset to DISABLE or ESC to exit", 63 | selections.to_vec(), 64 | ) 65 | .with_page_size(16) 66 | .prompt() 67 | { 68 | let _ = crate::actions::disable_ruleset(context, selection.tag); 69 | 70 | if evectl::prompt::confirm( 71 | "Would you like to update your rules now?", 72 | Some("A rule update is required to complete disabling this ruleset"), 73 | ) { 74 | crate::actions::update_rules(context)?; 75 | } 76 | 77 | evectl::prompt::enter(); 78 | } 79 | 80 | Ok(()) 81 | } 82 | 83 | fn enable_ruleset(context: &Context) -> Result<()> { 84 | let index = crate::actions::load_rule_index(context).unwrap(); 85 | let enabled = crate::actions::get_enabled_ruleset(context).unwrap(); 86 | let mut selections = evectl::prompt::Selections::new(); 87 | 88 | for (id, source) in &index.sources { 89 | if source.obsolete.is_some() { 90 | continue; 91 | } 92 | if source.parameters.is_some() { 93 | continue; 94 | } 95 | if enabled.contains(id) { 96 | continue; 97 | } 98 | 99 | let message = format!("{}: {}", id, source.summary.green().italic()); 100 | 101 | selections.push(id, message); 102 | } 103 | 104 | if let Ok(selection) = inquire::Select::new( 105 | "Choose a ruleset to enable or ESC to exit", 106 | selections.to_vec(), 107 | ) 108 | .with_page_size(16) 109 | .prompt() 110 | { 111 | let _ = crate::actions::enable_ruleset(context, selection.tag); 112 | 113 | if evectl::prompt::confirm( 114 | "Would you like to update your rules now?", 115 | Some("A rule update is require to make the new ruleset active"), 116 | ) { 117 | crate::actions::update_rules(context)?; 118 | } 119 | 120 | evectl::prompt::enter(); 121 | } 122 | 123 | Ok(()) 124 | } 125 | 126 | fn copy_suricata_update_template(context: &Context, filename: &str) -> Result<()> { 127 | let source = format!( 128 | "/usr/lib/suricata/python/suricata/update/configs/{}", 129 | filename 130 | ); 131 | let image = context.image_name(Container::Suricata); 132 | let output = RunCommandBuilder::new(context.manager, image) 133 | .rm() 134 | .args(&["cat", &source]) 135 | .build() 136 | .status_output()?; 137 | let mut target = std::fs::File::create(filename)?; 138 | target.write_all(&output)?; 139 | Ok(()) 140 | } 141 | 142 | fn edit_file(context: &Context, filename: &str) { 143 | let path = PathBuf::from(filename); 144 | if !path.exists() { 145 | if let Ok(true) = inquire::Confirm::new(&format!( 146 | "Would you like to start with a {} template", 147 | filename 148 | )) 149 | .with_default(true) 150 | .prompt() 151 | { 152 | if let Err(err) = copy_suricata_update_template(context, filename) { 153 | error!( 154 | "Sorry, an error occurred copying the template for {}: {}", 155 | filename, err 156 | ); 157 | evectl::prompt::enter(); 158 | } 159 | } 160 | } 161 | let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".into()); 162 | if let Err(err) = std::process::Command::new(&editor).arg(filename).status() { 163 | error!("Failed to load {} in editor {}: {}", filename, editor, err); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/menus.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use crate::{actions, context::Context, term, EVEBOX_CONTAINER_NAME, SURICATA_CONTAINER_NAME}; 5 | 6 | pub(crate) fn other(context: &Context) { 7 | loop { 8 | term::title("Simple-IDS: Other Menu Items"); 9 | 10 | let selections = evectl::prompt::Selections::with_index() 11 | .push("rotate", "Force Log Rotation") 12 | .push("suricata-shell", "Suricata Shell") 13 | .push("evebox-shell", "EveBox Shell") 14 | .push("remove", "Remove Simple-IDS data") 15 | .push("return", "Return") 16 | .to_vec(); 17 | 18 | match inquire::Select::new("Select menu option", selections).prompt() { 19 | Err(_) => return, 20 | Ok(selection) => match selection.tag { 21 | "return" => return, 22 | "rotate" => { 23 | actions::force_suricata_logrotate(context); 24 | evectl::prompt::enter(); 25 | } 26 | "suricata-shell" => { 27 | let _ = context 28 | .manager 29 | .command() 30 | .args([ 31 | "exec", 32 | "-it", 33 | "-e", 34 | "PS1=[\\u@suricata \\W]\\$ ", 35 | SURICATA_CONTAINER_NAME, 36 | "bash", 37 | ]) 38 | .status(); 39 | } 40 | "evebox-shell" => { 41 | let _ = context 42 | .manager 43 | .command() 44 | .args([ 45 | "exec", 46 | "-it", 47 | "-e", 48 | "PS1=[\\u@evebox \\W]\\$ ", 49 | EVEBOX_CONTAINER_NAME, 50 | "/bin/sh", 51 | ]) 52 | .status(); 53 | } 54 | "remove" => { 55 | if inquire::Confirm::new("Are you sure you want to remove Simple-IDS data?") 56 | .with_default(false) 57 | .prompt_skippable() 58 | .unwrap() 59 | .unwrap_or(false) 60 | { 61 | crate::remove(context); 62 | std::process::exit(0); 63 | } 64 | } 65 | _ => {} 66 | }, 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2024 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | pub(crate) use tracing::error; 5 | -------------------------------------------------------------------------------- /src/ruleindex.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2023 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use serde::Deserialize; 5 | use std::collections::HashMap; 6 | 7 | #[derive(Debug, Deserialize)] 8 | pub(crate) struct RuleIndex { 9 | #[serde(rename = "version")] 10 | pub _version: u8, 11 | pub sources: HashMap, 12 | } 13 | 14 | #[derive(Debug, Deserialize)] 15 | pub(crate) struct RuleSource { 16 | pub summary: String, 17 | pub obsolete: Option, 18 | pub parameters: Option>, 19 | } 20 | -------------------------------------------------------------------------------- /src/selfupdate.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use std::{ 5 | env, 6 | fs::{self, File}, 7 | io::{self, Seek, SeekFrom}, 8 | os::unix::prelude::PermissionsExt, 9 | path::Path, 10 | process, 11 | }; 12 | 13 | use anyhow::{bail, Result}; 14 | use sha2::{Digest, Sha256}; 15 | use tracing::{debug, error, info, warn}; 16 | 17 | // Ok, the return type is a bit odd as this handles a lot of the error 18 | // handling itself. An `Err` is an error that should be logged by the 19 | // caller. Ok(true) is success, but Ok(false) is an error that was 20 | // logged by this function. 21 | pub(crate) fn self_update() -> Result<()> { 22 | // If we're running from cargo, don't self update. 23 | if env::var("CARGO").is_ok() { 24 | info!("Not self updating as we are running from Cargo"); 25 | return Ok(()); 26 | } 27 | 28 | let target = env!("TARGET"); 29 | let url = format!("https://evebox.org/files/simple-ids/{}/simple-ids", target); 30 | let hash_url = format!("{}.sha256", url); 31 | let current_exe = if let Ok(exe) = env::current_exe() { 32 | exe 33 | } else { 34 | bail!("Failed to determine executable name, cannot self-update"); 35 | }; 36 | 37 | info!("Current running executable: {}", current_exe.display()); 38 | 39 | info!("Calculating checksum of current executable"); 40 | let current_hash = match current_checksum(¤t_exe) { 41 | Err(err) => { 42 | tracing::warn!("Failed to calculate checksum of current exec: {}", err); 43 | None 44 | } 45 | Ok(checksum) => Some(checksum), 46 | }; 47 | 48 | info!("Downloading {}", &hash_url); 49 | let response = reqwest::blocking::get(&hash_url)?; 50 | if response.status().as_u16() != 200 { 51 | error!( 52 | "Failed to fetch remote checksum: HTTP status code={}", 53 | response.status(), 54 | ); 55 | return Ok(()); 56 | } 57 | let remote_hash = response.text()?.trim().to_lowercase(); 58 | debug!("Remote SHA256 checksum: {}", &remote_hash); 59 | 60 | match current_hash { 61 | None => { 62 | info!("Failed to determine checksum of current exe, updating"); 63 | } 64 | Some(checksum) => { 65 | if checksum != remote_hash { 66 | info!("Remote checksum different than current exe, will update"); 67 | } else { 68 | info!("No update available"); 69 | return Ok(()); 70 | } 71 | } 72 | } 73 | 74 | info!("Downloading {}", &url); 75 | let mut download_exe = download_release(&url)?; 76 | 77 | // Verify the checksum. 78 | let hash = file_checksum(&mut download_exe)?; 79 | debug!( 80 | "Locally calculated SHA256 checksum for downloaded file: {}", 81 | &hash 82 | ); 83 | if hash != remote_hash { 84 | tracing::error!("Downloaded file has invalid checksum, not updating"); 85 | tracing::error!("- Expected {}", remote_hash); 86 | return Ok(()); 87 | } 88 | 89 | info!("Replacing current executable"); 90 | download_exe.seek(SeekFrom::Start(0))?; 91 | if let Err(err) = fs::remove_file(¤t_exe) { 92 | tracing::warn!( 93 | "Failed to remove current exe: {}: {}", 94 | current_exe.display(), 95 | err 96 | ); 97 | } 98 | let mut final_exec = fs::File::create(¤t_exe)?; 99 | io::copy(&mut download_exe, &mut final_exec)?; 100 | fs::set_permissions(¤t_exe, fs::Permissions::from_mode(0o0755))?; 101 | warn!("The Simple-IDS program has been updated. Please restart."); 102 | process::exit(0); 103 | } 104 | 105 | fn download_release(url: &str) -> Result { 106 | let mut response = reqwest::blocking::get(url)?; 107 | let mut dest = tempfile::tempfile()?; 108 | io::copy(&mut response, &mut dest)?; 109 | dest.seek(SeekFrom::Start(0))?; 110 | Ok(dest) 111 | } 112 | 113 | fn file_checksum(file: &mut File) -> Result { 114 | let mut hash = Sha256::new(); 115 | io::copy(file, &mut hash)?; 116 | let hash = hash.finalize(); 117 | Ok(format!("{:x}", hash)) 118 | } 119 | 120 | fn current_checksum(path: &Path) -> Result { 121 | let mut file = fs::File::open(path)?; 122 | file_checksum(&mut file) 123 | } 124 | -------------------------------------------------------------------------------- /src/term.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2021 Jason Ish 2 | // SPDX-License-Identifier: MIT 3 | 4 | use crossterm::{ 5 | cursor, execute, style, 6 | terminal::{Clear, ClearType}, 7 | }; 8 | use std::io::Write; 9 | 10 | pub(crate) fn title(title: &str) { 11 | let no_clear = std::env::var("NO_CLEAR").map(|_| true).unwrap_or(false); 12 | if no_clear { 13 | println!("{}\n", title); 14 | } else { 15 | let mut stdout = std::io::stdout().lock(); 16 | let _ = execute!( 17 | stdout, 18 | Clear(ClearType::All), 19 | cursor::MoveTo(0, 0), 20 | style::Print(title), 21 | cursor::MoveToNextLine(2) 22 | ); 23 | let _ = stdout.flush(); 24 | } 25 | } 26 | --------------------------------------------------------------------------------