├── .gitignore ├── INSTALL.sh ├── LICENSE ├── README.md ├── UPDATE.sh ├── build ├── AIRGAP_INSTALL.sh ├── build.sh ├── conf │ ├── redis-server.service │ ├── sign.json.template │ ├── tracker.json.template │ └── valkey.service ├── systemd │ ├── setup.sh │ ├── update.sh │ └── updatetracker.service ├── templates │ ├── misp_info.json │ └── modules_info.json └── update_tracker.py └── docs └── logo ├── logo-large.png ├── logo.png └── logo.svg /.gitignore: -------------------------------------------------------------------------------- 1 | images/ 2 | build/conf/sign.json 3 | build/conf/tracker.json 4 | logs/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MISP airgap 2 | 3 | ![MISP airgap](https://raw.githubusercontent.com/MISP/misp-airgap/d5d93af547c2d90c34f36469d6edaaa7b72e67a5/docs/logo/logo.png) 4 | 5 | 6 | MISP airgap is a project that facilitates the deployment and maintenance of [MISP](https://github.com/MISP/MISP), in air-gapped environments. It utilizes [LXD](https://ubuntu.com/lxd), a popular Linux containerization platform, to create and manage isolated containers for MISP and its associated databases. Additionally, this approach is adaptable for standard networked environments, allowing for the deployment of MISP in LXD in a broader range of operational contexts. 7 | 8 | ## Key Features 9 | 10 | - Automated setup and configuration of MISP in a secure, isolated environment. 11 | - Containerized approach using LXD for easy management and isolation. 12 | - Support for both interactive and non-interactive installation modes. 13 | - Comprehensive validation and security checks, ensuring secure deployment. 14 | - Modular setup allowing for easy updates and maintenance. 15 | 16 | ## Requirements 17 | 18 | Before setting up your environment, ensure that you meet the following prerequisites on your host system: 19 | 20 | - **Operating System:** 21 | - [Ubuntu 22.04](https://releases.ubuntu.com/jammy/) 22 | - **Containerization:** 23 | - [LXD 5.19](https://documentation.ubuntu.com/lxd/en/latest/installing/) 24 | - **Additional Software:** 25 | - [jq 1.6](https://jqlang.github.io/jq/download/) 26 | - [yq 4.35.2](https://github.com/mikefarah/yq/#install) 27 | 28 | ## Hardware Requirements 29 | 30 | To run all containers set up by the installation script, the following hardware specifications are recommended: 31 | 32 | - **CPU**: 33 | - Minimum: 4 cores 34 | - Recommended for optimal performance: 4 or more cores 35 | 36 | - **Memory (RAM)**: 37 | - Minimum: 8 GB 38 | - Recommended: 16 GB or more for better performance 39 | 40 | - **Storage**: 41 | - Minimum: 50 GB 42 | - Recommended: 100 GB or more, SSD preferred for better performance 43 | 44 | 45 | ## Installation 46 | First, [install LXD](https://ubuntu.com/lxd/install) on your air-gapped host system. Additionally, install the [additional software](#requirements) needed. 47 | 48 | After installation, proceed with the following steps: 49 | 50 | 1. **Download Images** 51 | 52 | You can download the images from the [MISP images page](https://images.misp-project.org/). It is recommended to use the latest version of the images. For a minimal air-gapped setup, you need the following images: 53 | - `MISP` 54 | - `MySQL` 55 | - `Redis` 56 | 57 | If you want to use MISP Modules, you also need the `Modules` image. 58 | 59 | 2. **Verify Signature** 60 | 61 | Download the signature file for the images you want to use. You can find the signature files in the same directory as the images. Verify the signature using GPG: 62 | 63 | You can find the public key for verifying the images on CIRCL's [PGP key server](https://openpgp.circl.lu/pks/lookup?op=get&search=0xec1862fc82cdaf7aebabc002287b725897d881d2). 64 | 65 | Import the MISP-airgap public key: 66 | ```bash 67 | gpg --import /path/to/misp-airgap.asc 68 | ``` 69 | Verify the signature using GPG: 70 | ```bash 71 | gpg --verify /path/to/file.sig /path/to/file 72 | ``` 73 | 74 | 3. **Transfer images and repo to air-gapped system**: 75 | 76 | Transfer the exported images and the whole repo to your air gapped system. 77 | 78 | ### Interactive Mode 79 | 80 | Run the `INSTALL.sh` script with the `--interactive` flag to enter the interactive mode, which guides you through the configuration process: 81 | 82 | ```bash 83 | bash INSTALL.sh --interactive 84 | ``` 85 | 86 | ### Non-Interactive Mode 87 | For a non-interactive setup, use command-line arguments to set configurations: 88 | 89 | **Example:** 90 | ```bash 91 | bash INSTALL.sh --misp-image --mysql-image --redis-image --no-modules 92 | ``` 93 | 94 | Below is the table summarizing the script flags and variables: 95 | 96 | | Variable | Default Value | Flag | Description | 97 | | --------------------- | ------------------------------ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | 98 | | `INTERACTIVE_MODE` | N/A | `-i`, `--interactive` | Activates an interactive installation process. | 99 | | `PROJECT_NAME` | `misp-project-` | `--project ` | Name of the LXD project used to organize and run the containers. | 100 | | `MISP_IMAGE` | `` | `--misp-image ` | The exported image file containing the configuration and setup of the MISP instance. | 101 | | `MISP_CONTAINER` | `misp-` | `--misp-name ` | The name of the container responsible for running the MISP application. | 102 | | `MYSQL_IMAGE` | `` | `--mysql-image ` | The exported image file of a MariaDB instance, containing the necessary configurations. | 103 | | `MYSQL_CONTAINER` | `mysql-` | `--mysql-name ` | The name of the container running the MariaDB database for MISP. | 104 | | `MYSQL_DATABASE` | `misp` | `--mysql-db ` | The name of the database used by the MISP application. | 105 | | `MYSQL_USER` | `misp` | `--mysql-user ` | The database user for MISP to interact with the MariaDB database. | 106 | | `MYSQL_PASSWORD` | `misp` | `--mysql-pwd ` | The password associated with the MISP database user. | 107 | | `MYSQL_ROOT_PASSWORD` | `misp` | `--mysql-root-pwd ` | The root user password for MariaDB. | 108 | | `REDIS_IMAGE` | `` | `--redis-image ` | The exported image file for the Redis instance, including necessary configurations. | 109 | | `REDIS_CONTAINER` | `redis-` | `--redis-name ` | The name of the container running the Redis server for MISP. | 110 | | `MODULES` | `yes` | `--no-modules` | If set, a container with MISP Modules gets set up. | 111 | | `MODULES_IMAGE` | `` | `--modules-image ` | The exported image file of a MISP Modules instance, containing the necessary configurations. | 112 | | `MODULES_CONTAINER` | `modules-` | `--modules-name ` | The name of the container running MISP Modules. | 113 | | `APP_PARTITION` | `` | `--app-partition ` | Dedicated partition for the storage of the MISP container. | 114 | | `DB_PARTITION` | `` | `--db-partition ` | Dedicated partition for the storage of the database container(s). | 115 | | `PROD` | `no` | `-p`, `--production` | If set to true, the MISP application runs in production mode, activating the `islive` option and adjusting settings accordingly. | 116 | 117 | 118 | >**Note**: It is crucial to **modify all default credentials** when using this installation in a production environment. Specifically, if the PROD variable is set to true, the installer will not accept default values. 119 | 120 | After completing these steps, MISP should be up and running. You can add the IP address of the MISP container to your `/etc/hosts` file to be able to access it through the set domain (default: misp.local). The IP address is displayed when the installing process has finished. Alternatively, you can identify the IP addresses of all running containers within the project by executing the command `lxc list`. 121 | 122 | ## Update 123 | 1. **Download Images** 124 | 125 | You need to dowload the images for the components you want to update. You can download the images from the [MISP images page](https://images.misp-project.org/). It is recommended to use the latest version of the images. 126 | 127 | 2. **Verify Signature** 128 | 129 | Download the signature file for the images you want to use. You can find the signature files in the same directory as the images. Verify the signature using GPG: 130 | 131 | You can find the public key for verifying the images on CIRCL's [PGP key server](https://openpgp.circl.lu/pks/lookup?op=get&search=0xec1862fc82cdaf7aebabc002287b725897d881d2). 132 | 133 | Import the MISP-airgap public key: 134 | ```bash 135 | gpg --import /path/to/misp-airgap.asc 136 | ``` 137 | Verify the signature using GPG: 138 | ```bash 139 | gpg --verify /path/to/file.sig /path/to/file 140 | ``` 141 | 142 | 3. **Transfer images to air-gapped system**: 143 | 144 | Transfer the exported images to your air gapped system. 145 | 146 | ### Interactive Mode 147 | 148 | Run the `UPDATE.sh` script with the `--interactive` flag to enter the interactive mode, which guides you through the configuration process: 149 | 150 | ```bash 151 | bash UPDATE.sh --interactive 152 | ``` 153 | 154 | >Note: It is recommende to use new names for the newly created containers. However if you want to keep the current container names, you can enter the same name for the new container as the current one. Please refer to the [Container Naming Conventions](#container-naming-conventions) section for more information. 155 | 156 | ### Non-Interactive Mode 157 | For a non-interactive setup, use command-line arguments to set configurations: 158 | 159 | **Example:** 160 | ```bash 161 | bash UPDATE.sh --current-misp --update-misp -p --misp-image 162 | ``` 163 | 164 | Below is the table summarizing the script flags and variables: 165 | 166 | 167 | | Variable | Default Value | Flag | Description | 168 | | --------------------- | ------------------------- | --------------------------- | ---------------------------------------------------- | 169 | | `INTERACTIVE_MODE` | N/A | `-i`, `--interactive` | Activates an interactive installation process. | 170 | | `MYSQL_ROOT_PASSWORD` | User Input Required | `-p`, `--passwd ` | Set the MySQL root password. | 171 | | `MISP` | `no` | `--update-misp` | Update MISP container. | 172 | | `MISP_IMAGE` | `` | `--misp-image ` | Specify the MISP image. | 173 | | `CURRENT_MISP` | `` | `--current-misp ` | Specify the current MISP container name (Mandatory). | 174 | | `NEW_MISP` | `misp-` | `--new-misp ` | Specify the new MISP container name. | 175 | | Multiple | N/A | `-a`, `--all` | Apply updates to all components. | 176 | | `MYSQL` | `no` | `--update-mysql` | Update MySQL container. | 177 | | `MYSQL_IMAGE` | `` | `--mysql-image ` | Specify the MySQL image. | 178 | | `NEW_MYSQL` | `mysql-` | `--new-mysql ` | Specify the new MySQL container name. | 179 | | `REDIS` | `no` | `--update-redis` | Update Redis container. | 180 | | `REDIS_IMAGE` | `` | `--redis-image ` | Specify the Redis image. | 181 | | `NEW_REDIS` | `redis-` | `--new-redis ` | Specify the new Redis container name. | 182 | | `MODULES` | `no` | `--update-modules` | Update modules container. | 183 | | `MODULES_IMAGE` | `` | `--modules-image ` | Specify the modules image. | 184 | | `NEW_MODULES` | `modules-` | `--new-modules ` | Specify the new modules container name. | 185 | 186 | ### Container Naming Conventions 187 | In LXD container names are used to identify containers for various operations including networking. Therefore, it is important to use a consistent naming convention for containers. 188 | 189 | As LXD does not allow containers with the same name, the script will automatically rename the old container if the new container name is the same as the old one. In iteractive mode the script will ask you if you are sure you want to rename the container. In non-interactive mode the script will automatically rename the container. 190 | 191 | The script will use the following naming convention for renamed containers: 192 | 193 | `updated--` 194 | 195 | All changes made by the script will be logged in the `naming.log` file in the `logs/` directory. 196 | 197 | ## Container Management 198 | Installing and updating MISP using the MISP airgap project creates and manages containers using LXD. The following commands can be used to manage the containers created by the installation script. If you are not familiar with LXD, you can find more information in the [LXD documentation](https://documentation.ubuntu.com/lxd/en/latest/). 199 | 200 | | Command | Description | 201 | | ------------------------------------------ | ------------------------------------------- | 202 | | `lxc list` | List all containers in the current project. | 203 | | `lxc start ` | Start a container. | 204 | | `lxc stop ` | Stop a container. | 205 | | `lxc project list` | List all projects. | 206 | | `lxc project switch ` | Switch to a different project. | 207 | | `lxc snapshot ` | Create a snapshot of a container. | 208 | | `lxc restore ` | Restore a container to a snapshot. | 209 | 210 | ## Build 211 | If you want to build the images yourself, you can use the `build.sh` script in the `build/` directory. This is completely optional, as the images are already built and available for download. 212 | 213 | **Requirements**: 214 | 215 | - jq 1.6 216 | - curl 7.81.0 217 | - gpg (GnuPG) 2.2.27 218 | 219 | You can build the images using the build script: 220 | ```bash 221 | bash build.sh [OPTIONS] 222 | ``` 223 | 224 | Below is the table summarizing the script options: 225 | 226 | | Variable | Default Value | Flag | Description | 227 | | --------------- | ------------- | --------------------------- | ------------------------------------------------ | 228 | | `MISP` | `false` | `--misp` | Create a MISP image. | 229 | | `MYSQL` | `false` | `--mysql` | Create a MySQL image. | 230 | | `REDIS` | `false` | `--redis` | Create a Redis image. | 231 | | `MODULES` | `false` | `--modules` | Create a Modules image. | 232 | | `MISP_IMAGE` | `MISP` | `--misp-name ` | Specify a custom name for the MISP image. | 233 | | `MYSQL_IMAGE` | `MySQL` | `--mysql-name ` | Specify a custom name for the MySQL image. | 234 | | `REDIS_IMAGE` | `Redis` | `--redis-name ` | Specify a custom name for the Redis image. | 235 | | `MODULES_IMAGE` | `Modules` | `--modules-name ` | Specify a custom name for the Modules image. | 236 | | `REDIS_VERSION` | N/A | `--redis-version ` | Specify a Redis version to build. | 237 | | `MYSQL_VERSION` | N/A | `--mysql-version ` | Specify a MySQL version to build. | 238 | | `OUTPUTDIR` | N/A | `-o`, `--outputdir ` | Specify the output directory for created images. | 239 | | `SIGN` | `false` | `-s`, `--sign` | Sign the created images. | 240 | 241 | ### Signing 242 | When the `-s` or `--sign` flag is used, the `build.sh` script will sign the created images using GPG. To utilize this feature, first configure your signing keys in the `/conf/sign.json` file. You can use the provided template file as a starting point: 243 | ```bash 244 | cd ./build/conf 245 | cp sign.json.template sign.json 246 | ``` 247 | If no key with the specified ID is found in your GPG keyring, the script will automatically generate a new key. 248 | 249 | ### Running Image Creation with systemd 250 | 251 | This section describes how to run the image creation process as a systemd service on a Linux system. The service is designed to periodically check for updates in specified GitHub repositories and execute an image creation process using the `build.sh` script if new updates are found. 252 | 253 | **Prerequisites** 254 | 255 | - Ubuntu 22.04 system with systemd. 256 | - Python 3 and the requests module installed. 257 | - Access to GitHub repositories (internet connection required). 258 | 259 | **Config** 260 | 261 | Edit the `tracker.json` configuration file in `build/conf/` to specify the GitHub repositories to track, the build arguments, and the check interval. You can use the provided template file as a starting point: 262 | ```bash 263 | cd ./build/conf 264 | cp tracker.json.template tracker.json 265 | ``` 266 | 267 | If your build process requires GPG signing, edit the `sign.json` configuration file in `build/conf/` by copying the template and modifying the default values: 268 | ```bash 269 | cd ./build/conf 270 | cp sign.json.template sign.json 271 | ``` 272 | 273 | **Setup** 274 | 275 | To setup the service run the `setup.sh` script in the `systemd/` directory. This script performs several tasks: 276 | 277 | - Creates a dedicated user for the service. 278 | - Copies necessary files to appropriate locations. 279 | - Sets required permissions. 280 | - Automatically configures and enables the systemd service. 281 | 282 | To run the script, execute: 283 | ```bash 284 | cd build/systemd 285 | sudo bash setup.sh 286 | ``` 287 | 288 | **Monitoring** 289 | 290 | To check the status of the service, use: 291 | ```bash 292 | systemctl status updatetracker.service 293 | ``` 294 | For debugging or monitoring, access the service logs with: 295 | ```bash 296 | journalctl -u updatetracker.service 297 | ``` 298 | 299 | **Modifying** 300 | 301 | If you need to modify the service (e.g., changing the repositories to track or the check interval), update the `tracker.json` file in `/opt/misp_airgap/build/conf/` and restart the service: 302 | ```bash 303 | systemctl restart updatetracker.service 304 | ``` 305 | Alternatively, you can use the `update.sh` script in the `systemd/` directory to automatically update the service configuration and restart the service: 306 | ```bash 307 | sudo bash update.sh 308 | ``` 309 | This can be helpfull if there are changes to the scripts used by the service such as `build.sh` or `updatetracker.py`. 310 | 311 | 312 | *Alternative Method Using `update.sh` Script:* 313 | 314 | If your modifications include changes to the service's operational scripts (like `build.sh` or `updatetracker.py`), it's recommended to use the `update.sh` script. This script ensures that the service configuration is updated and the service is restarted to reflect the changes. 315 | 316 | To use the update.sh script: 317 | 318 | ```bash 319 | cd /path/to/systemd/ 320 | sudo bash update.sh 321 | ``` 322 | 323 | > Note: Using the update.sh script is especially useful for comprehensive updates, as it automates the process of applying configuration changes and restarting the service. 324 | 325 | ## Maintenance 326 | 327 | A few useful commands to manage the LXC images. 328 | 329 | * Open a shell on the MISP image: 330 | 331 | ```bash 332 | lxc exec misp- bash 333 | ``` 334 | 335 | * reset the password of the MISP user: 336 | 337 | ```bash 338 | sudo -u www-data /var/www/MISP/app/Console/cake user change_pw admin@admin.test Password1234 339 | ``` 340 | -------------------------------------------------------------------------------- /UPDATE.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Color codes 4 | GREEN='\033[0;32m' 5 | YELLOW='\033[1;33m' 6 | BLUE='\033[0;34m' 7 | RED='\033[0;31m' 8 | VIOLET='\033[0;35m' 9 | NC='\033[0m' # No Color 10 | 11 | setVars(){ 12 | MISP_IMAGE_NAME=$(generateName "misp") 13 | MYSQL_IMAGE_NAME=$(generateName "mysql") 14 | REDIS_IMAGE_NAME=$(generateName "redis") 15 | MODULES_IMAGE_NAME=$(generateName "modules") 16 | 17 | PATH_TO_MISP="/var/www/MISP/" 18 | 19 | SNAP_NAME=$(generateName "update") 20 | 21 | NAMING_LOG_FILE="./logs/naming.log" 22 | 23 | MISP_NEW_EQ_OLD=false 24 | MYSQL_NEW_EQ_OLD=false 25 | REDIS_NEW_EQ_OLD=false 26 | MODULES_NEW_EQ_OLD=false 27 | RENAMED_MISP=false 28 | RENAMED_MYSQL=false 29 | RENAMED_REDIS=false 30 | RENAMED_MODULES=false 31 | } 32 | 33 | setDefaultArgs(){ 34 | default_misp="no" 35 | default_misp_img="" 36 | default_current_misp="" 37 | default_new_misp=$(generateName "misp") 38 | 39 | default_mysql="no" 40 | default_mysql_img="" 41 | default_new_mysql=$(generateName "mysql") 42 | 43 | default_redis="no" 44 | default_redis_img="" 45 | default_new_redis=$(generateName "redis") 46 | 47 | default_modules="no" 48 | default_modules_img="" 49 | default_new_modules=$(generateName "modules") 50 | } 51 | 52 | error() { 53 | local msg=$1 54 | echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] Error: $msg${NC}" > /dev/tty 55 | } 56 | 57 | warn() { 58 | local msg=$1 59 | echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] Warning: $msg${NC}" > /dev/tty 60 | } 61 | 62 | okay() { 63 | local msg=$1 64 | echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] Success: $msg${NC}" > /dev/tty 65 | } 66 | 67 | info() { 68 | local msg=$1 69 | echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] Info: $msg${NC}" > /dev/tty 70 | } 71 | 72 | generateName(){ 73 | local name="$1" 74 | echo "${name}-$(date +%Y-%m-%d-%H-%M-%S)" 75 | } 76 | 77 | checkResourceExists() { 78 | local resource_type="$1" 79 | local resource_name="$2" 80 | 81 | case "$resource_type" in 82 | "container") 83 | lxc info "$resource_name" &>/dev/null 84 | ;; 85 | "image") 86 | lxc image list --format=json | jq -e --arg alias "$resource_name" '.[] | select(.aliases[].name == $alias) | .fingerprint' &>/dev/null 87 | ;; 88 | "project") 89 | lxc project list --format=json | jq -e --arg name "$resource_name" '.[] | select(.name == $name) | .name' &>/dev/null 90 | ;; 91 | "storage") 92 | lxc storage list --format=json | jq -e --arg name "$resource_name" '.[] | select(.name == $name) | .name' &>/dev/null 93 | ;; 94 | "network") 95 | lxc network list --format=json | jq -e --arg name "$resource_name" '.[] | select(.name == $name) | .name' &>/dev/null 96 | ;; 97 | "profile") 98 | lxc profile list --format=json | jq -e --arg name "$resource_name" '.[] | select(.name == $name) | .name' &>/dev/null 99 | ;; 100 | esac 101 | 102 | return $? 103 | } 104 | 105 | checkSoftwareDependencies() { 106 | local dependencies=("jq" "yq") 107 | 108 | for dep in "${dependencies[@]}"; do 109 | if ! command -v "$dep" &> /dev/null; then 110 | error "$dep is not installed." 111 | exit 1 112 | fi 113 | done 114 | } 115 | 116 | 117 | usage() { 118 | echo "Usage: $0 [options]" 119 | echo 120 | echo "Options:" 121 | echo " -h, --help Show this help message and exit." 122 | echo " -p, --passwd Set the MySQL root password." 123 | echo " --update-misp Update MISP container." 124 | echo " --misp-image Specify the MISP image." 125 | echo " --current-misp Specify the current MISP container name (Mandatory)." 126 | echo " --new-misp Specify the new MISP container name." 127 | echo " -a, --all Apply updates to all components." 128 | echo " --update-mysql Update MySQL container." 129 | echo " --mysql-image Specify the MySQL image." 130 | echo " --new-mysql Specify the new MySQL container name." 131 | echo " --update-redis Update Redis container." 132 | echo " --redis-image Specify the Redis image." 133 | echo " --new-redis Specify the new Redis container name." 134 | echo " --update-modules Update modules container." 135 | echo " --modules-image Specify the modules image." 136 | echo " --new-modules Specify the new modules container name." 137 | echo 138 | echo "Examples:" 139 | echo " $0 --update-misp --misp-image new_misp_image.tar --current-misp current_misp_container --new-misp new_misp_container -p password" 140 | echo " $0 --update-mysql --current-misp current_misp_container --mysql-image new_mysql_image.tar --new-mysql new_mysql_container -p password" 141 | echo 142 | echo "Note:" 143 | echo " - The script updates specified containers to new versions using provided images." 144 | echo " - Mandatory fields must be specified for each component you wish to update." 145 | echo " - Use only alphanumeric characters and hyphens for container names." 146 | } 147 | 148 | 149 | nonInteractiveConfig(){ 150 | VALID_ARGS=$(getopt -o hap: --long help,passwd:,update-misp,misp-image:,current-misp:,new-misp:,all,update-mysql,mysql-image:,new-mysql:,update-redis,redis-image:,new-redis:,update-modules,modules-image:,new-modules: -- "$@") 151 | if [[ $? -ne 0 ]]; then 152 | exit 1; 153 | fi 154 | 155 | eval set -- "$VALID_ARGS" 156 | while [ $# -gt 0 ]; do 157 | case "$1" in 158 | -h | --help) 159 | usage 160 | exit 0 161 | ;; 162 | -p | --passswd) 163 | MYSQL_ROOT_PASSWORD=$2 164 | shift 2 165 | ;; 166 | --update-misp) 167 | misp="y" 168 | shift 169 | ;; 170 | --misp-image) 171 | misp_img=$2 172 | shift 2 173 | ;; 174 | --current-misp) 175 | current_misp=$2 176 | shift 2 177 | ;; 178 | --new-misp) 179 | new_misp=$2 180 | shift 2 181 | ;; 182 | -a | --all) 183 | misp="y" 184 | mysql="y" 185 | redis="y" 186 | modules="y" 187 | shift 188 | ;; 189 | --update-mysql) 190 | mysql="y" 191 | shift 192 | ;; 193 | --mysql-image) 194 | mysql_img=$2 195 | shift 2 196 | ;; 197 | --new-mysql) 198 | new_mysql=$2 199 | shift 2 200 | ;; 201 | --update-redis) 202 | redis="y" 203 | shift 204 | ;; 205 | --redis-image) 206 | redis_img=$2 207 | shift 2 208 | ;; 209 | --new-redis) 210 | new_redis=$2 211 | shift 2 212 | ;; 213 | --update-modules) 214 | modules="y" 215 | shift 216 | ;; 217 | --modules-image) 218 | modules_img=$2 219 | shift 2 220 | ;; 221 | --new-modules) 222 | new_modules=$2 223 | shift 2 224 | ;; 225 | *) 226 | break 227 | ;; 228 | esac 229 | done 230 | 231 | # Set global values 232 | misp=${misp:-$default_misp} 233 | MISP=$(echo "$misp" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 234 | MISP_IMAGE=${misp_img:-$default_misp_img} 235 | CURRENT_MISP=${current_misp:-$default_current_misp} 236 | NEW_MISP=${new_misp:-$default_new_misp} 237 | 238 | mysql=${mysql:-$default_mysql} 239 | MYSQL=$(echo "$mysql" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 240 | MYSQL_IMAGE=${mysql_img:-$default_mysql_img} 241 | NEW_MYSQL=${new_mysql:-$default_new_mysql} 242 | 243 | redis=${redis:-$default_redis} 244 | REDIS=$(echo "$redis" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 245 | REDIS_IMAGE=${redis_img:-$default_redis_img} 246 | NEW_REDIS=${new_redis:-$default_new_redis} 247 | 248 | modules=${modules:-$default_modules} 249 | MODULES=$(echo "$modules" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 250 | MODULES_IMAGE=${modules_img:-$default_modules_img} 251 | NEW_MODULES=${new_modules:-$default_new_modules} 252 | } 253 | 254 | checkNamingConvention(){ 255 | local input="$1" 256 | local pattern="^[a-zA-Z0-9-]+$" 257 | 258 | if ! [[ "$input" =~ $pattern ]]; then 259 | return 1 260 | fi 261 | return 0 262 | } 263 | 264 | 265 | validateArgs(){ 266 | local mandatory=("CURRENT_MISP") 267 | local image=() 268 | 269 | # Check images and if args set 270 | if ! checkResourceExists "container" "$CURRENT_MISP"; then 271 | error "Container $CURRENT_MISP could not be found!" 272 | exit 1 273 | fi 274 | if $MISP; then 275 | mandatory+=("MISP_IMAGE" "MYSQL_ROOT_PASSWORD") 276 | image+=("MISP_IMAGE") 277 | fi 278 | if $MYSQL; then 279 | mandatory+=("MYSQL_IMAGE" "MYSQL_ROOT_PASSWORD") 280 | image+=("MYSQL_IMAGE") 281 | fi 282 | if $REDIS; then 283 | mandatory+=("REDIS_IMAGE") 284 | image+=("REDIS_IMAGE") 285 | fi 286 | if $MODULES; then 287 | mandatory+=("MODULES_IMAGE") 288 | image+=("MODULES_IMAGE") 289 | fi 290 | 291 | for arg in "${mandatory[@]}"; do 292 | if [ -z "${!arg}" ]; then 293 | error "$arg is not set!" 294 | exit 1 295 | fi 296 | done 297 | for img in "${image[@]}"; do 298 | if [ ! -f "${!img}" ]; then 299 | error "Image ${!img} is not a file or could not be found!" 300 | exit 1 301 | fi 302 | done 303 | 304 | # Check if update is set 305 | if ! $MISP && ! $MYSQL && ! $REDIS && ! $MODULES; then 306 | error "No update set for any component. Cancel update ..." 307 | exit 1 308 | fi 309 | 310 | # Check if components are running 311 | if ! checkLXDContainerRunning "$CURRENT_MISP"; then 312 | error "Container $CURRENT_MISP is not running!" 313 | exit 1 314 | fi 315 | 316 | validateNewContainerSetup "$NEW_MISP" "$MISP" "MISP" "MISP_IMAGE" 317 | validateNewContainerSetup "$NEW_MYSQL" "$MYSQL" "MySQL" "MYSQL_IMAGE" 318 | validateNewContainerSetup "$NEW_REDIS" "$REDIS" "Redis" "REDIS_IMAGE" 319 | validateNewContainerSetup "$NEW_MODULES" "$MODULES" "Modules" "MODULES_IMAGE" 320 | 321 | # Check for duplicate names 322 | declare -A name_counts 323 | 324 | if $MISP; then 325 | ((name_counts["$NEW_MISP"]++)) 326 | fi 327 | if $MYSQL;then 328 | ((name_counts["$NEW_MYSQL"]++)) 329 | fi 330 | if $REDIS;then 331 | ((name_counts["$NEW_REDIS"]++)) 332 | fi 333 | if $MODULES;then 334 | ((name_counts["$NEW_MODULES"]++)) 335 | fi 336 | 337 | for name in "${!name_counts[@]}"; do 338 | if ((name_counts["$name"] >= 2)); then 339 | error "At least two container have the same new name: $name" 340 | exit 1 341 | fi 342 | done 343 | } 344 | 345 | checkLXDContainerRunning() { 346 | local container_name=$1 347 | local state 348 | state=$(lxc list "^${container_name}$" --format csv -c s) 349 | 350 | if [ "$state" == "RUNNING" ]; then 351 | return 0 352 | fi 353 | return 1 354 | } 355 | 356 | validateNewContainerSetup() { 357 | local name=$1 358 | local flag=$2 359 | local type=$3 360 | local image_var_name=$4 361 | 362 | if [ "$flag" = true ]; then 363 | if ! checkNamingConvention "$name"; then 364 | error "Name $name for $type container is not valid. Please use only alphanumeric characters and hyphens." 365 | exit 1 366 | fi 367 | 368 | if [ ! -f "${!image_var_name}" ]; then 369 | if [ -z "${!image_var_name}" ]; then 370 | error "No update image for $type specified!" 371 | exit 1 372 | fi 373 | error "The specified image ${!image_var_name} for $type does not exist" 374 | exit 1 375 | fi 376 | fi 377 | } 378 | 379 | err() { 380 | local parent_lineno="$1" 381 | local message="$2" 382 | local code="${3:-1}" 383 | 384 | if [[ -n "$message" ]] ; then 385 | error "Line ${parent_lineno}: ${message}: exiting with status ${code}" 386 | else 387 | error "Line ${parent_lineno}: exiting with status ${code}" 388 | fi 389 | 390 | reset 391 | exit "${code}" 392 | } 393 | 394 | interrupt() { 395 | warn "Script interrupted by user!" 396 | reset 397 | exit 130 398 | } 399 | 400 | reset(){ 401 | local new_containers=() 402 | local backup_container=("$BACKUP_MISP" "$BACKUP_MYSQL" "$BACKUP_REDIS" "$BACKUP_MODULES") 403 | local images=("$MISP_IMAGE_NAME" "$MYSQL_IMAGE_NAME" "$REDIS_IMAGE_NAME" "$MODULES_IMAGE_NAME") 404 | 405 | if $RENAMED_MISP || ! $MISP_NEW_EQ_OLD; then 406 | new_containers+=("$NEW_MISP") 407 | fi 408 | if $RENAMED_MYSQL || ! $MYSQL_NEW_EQ_OLD; then 409 | new_containers+=("$NEW_MYSQL") 410 | fi 411 | if $RENAMED_REDIS || ! $REDIS_NEW_EQ_OLD; then 412 | new_containers+=("$NEW_REDIS") 413 | fi 414 | if $RENAMED_MODULES || ! $MODULES_NEW_EQ_OLD; then 415 | new_containers+=("$NEW_MODULES") 416 | fi 417 | 418 | warn "Reset to state before updating ..." 419 | sleep 5 420 | for container in "${new_containers[@]}"; do 421 | for i in $(lxc list --format=json | jq -r '.[].name'); do 422 | if [ "$i" == "$container" ]; then 423 | lxc delete "$container" --force 424 | fi 425 | done 426 | done 427 | 428 | for image in "${images[@]}";do 429 | for i in $(lxc image list --format=json | jq -r '.[].aliases[].name'); do 430 | if [ "$i" == "$image" ]; then 431 | lxc image delete "$image" 432 | fi 433 | done 434 | done 435 | 436 | if $RENAMED_MISP; then 437 | renameContainer "$CURRENT_MISP" "$ORIGINAL_MISP" 438 | fi 439 | if $RENAMED_MYSQL; then 440 | renameContainer "$CURRENT_MYSQL" "$ORIGINAL_MYSQL" 441 | fi 442 | if $RENAMED_REDIS; then 443 | renameContainer "$CURRENT_REDIS" "$ORIGINAL_REDIS" 444 | fi 445 | if $RENAMED_MODULES; then 446 | renameContainer "$CURRENT_MODULES" "$ORIGINAL_MODULES" 447 | fi 448 | 449 | warn "Restore snapshots ..." 450 | lxc restore "$BACKUP_MISP" "$SNAP_NAME" 451 | lxc restore "$BACKUP_MYSQL" "$SNAP_NAME" 452 | lxc restore "$BACKUP_REDIS" "$SNAP_NAME" 453 | if $CUSTOM_MODULES; then 454 | lxc restore "$BACKUP_MODULES" "$SNAP_NAME" 455 | fi 456 | 457 | for container in "${backup_container[@]}"; do 458 | local status 459 | status=$(lxc list --format=json | jq -e --arg name "$container" '.[] | select(.name == $name) | .status') 460 | if [ -n "$container" ] && [ "$status" == "\"Stopped\"" ]; then 461 | lxc start "$container" 462 | fi 463 | done 464 | 465 | # Delete snapshots 466 | lxc delete "$BACKUP_MISP"/"$SNAP_NAME" 467 | lxc delete "$BACKUP_MYSQL"/"$SNAP_NAME" 468 | lxc delete "$BACKUP_REDIS"/"$SNAP_NAME" 469 | if $CUSTOM_MODULES; then 470 | lxc delete "$BACKUP_MODULES"/"$SNAP_NAME" 471 | fi 472 | } 473 | 474 | restoreBackup(){ 475 | local container=$1 476 | local backup=$2 477 | 478 | if ! checkResourceExists "container" "$container"; then 479 | error "Container $container could not be found!" 480 | exit 1 481 | fi 482 | if lxc info "$container" | grep -q "$backup"; then 483 | error "Backup $backup for container $container does not exist!" 484 | exit 1 485 | fi 486 | lxc restore "$container" "$backup" 487 | } 488 | 489 | getAdditionalContainers(){ 490 | CURRENT_MYSQL=$(lxc exec "$CURRENT_MISP" -- bash -c "grep \"'host' =>\" /var/www/MISP/app/Config/database.php | sed -E \"s/.*'host' => '([^']+)\.lxd'.*/\1/\"") 491 | 492 | if ! checkLXDContainerRunning "$CURRENT_MYSQL"; then 493 | error "Container $CURRENT_MYSQL is not running!" 494 | fi 495 | 496 | # Check root pwd 497 | if $MISP || $MYSQL; then 498 | if ! lxc exec "$CURRENT_MYSQL" -- mysql -u "root" -p"$MYSQL_ROOT_PASSWORD" -e "quit" > /dev/null 2>&1; then 499 | error "MySQL root pwd is invalid!" 500 | exit 1 501 | fi 502 | fi 503 | 504 | local redis_host 505 | redis_host=$(lxc exec "$CURRENT_MISP" -- bash -c 'sudo -u "www-data" -H sh -c "/var/www/MISP/app/Console/cake Admin getSetting MISP.redis_host"') 506 | CURRENT_REDIS=$(echo "$redis_host" | jq -r '.value' | sed "s/\\.lxd$//") 507 | 508 | if ! checkLXDContainerRunning "$CURRENT_REDIS"; then 509 | error "Container $CURRENT_REDIS is not running!" 510 | fi 511 | 512 | 513 | CUSTOM_MODULES=true 514 | local modules_host 515 | modules_host=$(lxc exec "$CURRENT_MISP" -- bash -c 'sudo -u "www-data" -H sh -c "/var/www/MISP/app/Console/cake Admin getSetting Plugin.Enrichment_services_url"') 516 | CURRENT_MODULES=$(echo "$modules_host" | jq -r '.value' | sed "s/\\.lxd$//") 517 | if [ "$CURRENT_MODULES" == "http://127.0.0.1" ]; then 518 | CUSTOM_MODULES=false 519 | fi 520 | if $CUSTOM_MODULES; then 521 | if ! checkLXDContainerRunning "$CURRENT_MODULES"; then 522 | error "Container $CURRENT_MODULES is not running!" 523 | fi 524 | fi 525 | 526 | # Save original names 527 | ORIGINAL_MISP=$CURRENT_MISP 528 | ORIGINAL_MYSQL=$CURRENT_MYSQL 529 | ORIGINAL_REDIS=$CURRENT_REDIS 530 | if $CUSTOM_MODULES; then 531 | ORIGINAL_MODULES=$CURRENT_MODULES 532 | fi 533 | } 534 | 535 | cleanupMISP(){ 536 | lxc image delete "$MISP_IMAGE_NAME" 537 | rm -r "$TEMP" 538 | } 539 | 540 | createRedisSocket(){ 541 | trap 'err ${LINENO}' ERR 542 | local file_path="/etc/redis/redis.conf" 543 | local lines_to_add="# create a unix domain socket to listen on\nunixsocket /var/run/redis/redis.sock\n# set permissions for the socket\nunixsocketperm 775" 544 | 545 | lxc exec "$NEW_MISP" -- usermod -g www-data redis 546 | lxc exec "$NEW_MISP" -- mkdir -p /var/run/redis/ 547 | lxc exec "$NEW_MISP" -- chown -R redis:www-data /var/run/redis 548 | lxc exec "$NEW_MISP" -- cp "$file_path" "$file_path.bak" 549 | lxc exec "$NEW_MISP" -- bash -c "echo -e \"$lines_to_add\" | cat - \"$file_path\" >tempfile && mv tempfile \"$file_path\"" 550 | lxc exec "$NEW_MISP" -- usermod -aG redis www-data 551 | lxc exec "$NEW_MISP" -- service redis-server restart 552 | 553 | # Modify php.ini 554 | local php_ini_path="/etc/php/$PHP_VERSION/apache2/php.ini" 555 | local socket_path="/var/run/redis/redis.sock" 556 | lxc exec "$NEW_MISP" -- sed -i "s|;session.save_path = \"/var/lib/php/sessions\"|session.save_path = \"$socket_path\"|; s|session.save_handler = files|session.save_handler = redis|" "$php_ini_path" 557 | lxc exec "$NEW_MISP" -- sudo service apache2 restart 558 | } 559 | 560 | updateMISP(){ 561 | trap 'err ${LINENO}' ERR 562 | info "Update MISP..." 563 | TEMP=$(mktemp -d) 564 | 565 | # Check if the directory was created successfully 566 | if [ -z "$TEMP" ]; then 567 | error "Creating temporary directory." 568 | exit 1 569 | fi 570 | okay "Created temporary directory $TEMP." 571 | 572 | # Pull config 573 | info "Extract files from current MISP..." 574 | lxc file pull -r "$CURRENT_MISP"/var/www/MISP/app/files "$TEMP" -v 575 | lxc file pull -r "$CURRENT_MISP"/var/www/MISP/app/tmp "$TEMP" -v 576 | lxc file pull -r "$CURRENT_MISP"/var/www/MISP/app/Config "$TEMP" -v 577 | lxc file pull -r "$CURRENT_MISP"/var/www/MISP/app/webroot/img "$TEMP"/webroot/ -v 578 | lxc file pull "$CURRENT_MISP"/var/www/MISP/app/webroot/gpg.asc "$TEMP"/webroot/ -v 579 | lxc file pull -r "$CURRENT_MISP"/var/www/MISP/app/View/Emails/html/Custom "$TEMP"/View/Emails/html/ -v 580 | lxc file pull -r "$CURRENT_MISP"/var/www/MISP/app/View/Emails/text/Custom "$TEMP"/View/Emails/text/ -v 581 | lxc file pull "$CURRENT_MISP"/var/www/MISP/app/Plugin/CakeResque/Config/config.php "$TEMP"/Plugin/CakeResque/Config/ -v 582 | lxc file pull -r "$CURRENT_MISP"/var/www/MISP/.gnupg/openpgp-revocs.d "$TEMP"/.gnupg/ -v 583 | lxc file pull -r "$CURRENT_MISP"/var/www/MISP/.gnupg/private-keys-v1.d "$TEMP"/.gnupg/ -v 584 | lxc file pull "$CURRENT_MISP"/var/www/MISP/.gnupg/pubring.kbx "$TEMP"/.gnupg/ -v 585 | lxc file pull "$CURRENT_MISP"/var/www/MISP/.gnupg/pubring.kbx~ "$TEMP"/.gnupg/ -v 586 | lxc file pull "$CURRENT_MISP"/var/www/MISP/.gnupg/trustdb.gpg "$TEMP"/.gnupg/ -v 587 | # Supervisor conf 588 | lxc file pull "$CURRENT_MISP"/etc/supervisor/supervisord.conf "$TEMP"/supervisor/ -v 589 | 590 | info "Get additional config..." 591 | MYSQL_USER=$(lxc exec "$CURRENT_MISP" -- bash -c "grep 'login' /var/www/MISP/app/Config/database.php | awk '{print \$3}' | sed 's/[^a-zA-Z0-9]//g'") 592 | PHP_VERSION=$(lxc exec "$CURRENT_MISP" -- bash -c "php -v | head -n 1 | awk '{print \$2}' | cut -d '.' -f 1,2") 593 | PHP_MEMORY_LIMIT=$(lxc exec "$CURRENT_MISP" -- sudo -H -u www-data -- grep "memory_limit" /etc/php/"$PHP_VERSION"/apache2/php.ini | awk -F' = ' '{print $2}') 594 | PHP_MAX_EXECUTION_TIME=$(lxc exec "$CURRENT_MISP" -- sudo -H -u www-data -- grep "max_execution_time" /etc/php/"$PHP_VERSION"/apache2/php.ini | awk -F' = ' '{print $2}') 595 | PHP_UPLOAD_MAX_FILESIZE=$(lxc exec "$CURRENT_MISP" -- sudo -H -u www-data -- grep "upload_max_filesize" /etc/php/"$PHP_VERSION"/apache2/php.ini | awk -F' = ' '{print $2}') 596 | PHP_POST_MAX_SIZE=$(lxc exec "$CURRENT_MISP" -- sudo -H -u www-data -- grep "post_max_size" /etc/php/"$PHP_VERSION"/apache2/php.ini | awk -F' = ' '{print $2}') 597 | 598 | # Import new image 599 | info "Import image..." 600 | lxc image import "$MISP_IMAGE" --alias "$MISP_IMAGE_NAME" 601 | 602 | # Create new instance 603 | info "Create new MISP instance..." 604 | local profile 605 | profile=$(lxc config show "$CURRENT_MISP" | yq '.profiles[0]' | tr -d '"') 606 | lxc launch "$MISP_IMAGE_NAME" "$NEW_MISP" --profile="$profile" 607 | 608 | # Transfer files to new instance 609 | echo "Push files to new MISP instance" 610 | lxc file push -r "$TEMP"/files "$NEW_MISP"/var/www/MISP/app/ -v 611 | lxc file push -r "$TEMP"/tmp "$NEW_MISP"/var/www/MISP/app/ -v 612 | lxc file push -r "$TEMP"/Config "$NEW_MISP"/var/www/MISP/app/ -v 613 | lxc file push -r "$TEMP"/webroot/img "$NEW_MISP"/var/www/MISP/app/webroot/ -v 614 | lxc file push "$TEMP"/webroot/gpg.asc "$NEW_MISP"/var/www/MISP/app/webroot/ -v 615 | lxc file push -r "$TEMP"/View/Emails/html/Custom "$NEW_MISP"/var/www/MISP/app/View/Emails/html/ -v 616 | lxc file push -r "$TEMP"/View/Emails/text/Custom "$NEW_MISP"/var/www/MISP/app/View/Emails/text/ -v 617 | lxc file push "$TEMP"/Plugin/CakeResque/Config/config.php "$NEW_MISP"/var/www/MISP/app/Plugin/CakeResque/Config/ -v 618 | lxc file push -r "$TEMP"/.gnupg "$NEW_MISP"/var/www/MISP/ -v 619 | # Supervisor conf 620 | lxc file push "$TEMP"/supervisor/supervisord.conf "$NEW_MISP"/etc/supervisor/ -v 621 | 622 | # Set permissions 623 | lxc exec "$NEW_MISP" -- sudo chown -R www-data:www-data $PATH_TO_MISP 624 | lxc exec "$NEW_MISP" -- sudo chmod -R 750 $PATH_TO_MISP 625 | lxc exec "$NEW_MISP" -- sudo chmod -R g+ws ${PATH_TO_MISP}/app/tmp 626 | lxc exec "$NEW_MISP" -- sudo chmod -R g+ws ${PATH_TO_MISP}/app/files 627 | lxc exec "$NEW_MISP" -- sudo chmod -R g+ws ${PATH_TO_MISP}/app/files/scripts/tmp 628 | 629 | # Change host address on MySQL 630 | if ! $MISP_NEW_EQ_OLD; then 631 | lxc exec "$CURRENT_MYSQL" -- mysql -u root -p"$MYSQL_ROOT_PASSWORD" -e "RENAME USER '$MYSQL_USER'@'$CURRENT_MISP.lxd' TO '$MYSQL_USER'@'$NEW_MISP.lxd';" 632 | fi 633 | 634 | # Apply php.ini config 635 | lxc exec "$NEW_MISP" -- bash -c "grep -q '^memory_limit' /etc/php/$PHP_VERSION/apache2/php.ini && sed -i 's/^memory_limit.*/memory_limit = $PHP_MEMORY_LIMIT/' /etc/php/$PHP_VERSION/apache2/php.ini || echo 'memory_limit = $PHP_MEMORY_LIMIT' >> /etc/php/$PHP_VERSION/apache2/php.ini" 636 | lxc exec "$NEW_MISP" -- bash -c "grep -q '^max_execution_time' /etc/php/$PHP_VERSION/apache2/php.ini && sed -i 's/^max_execution_time.*/max_execution_time = $PHP_MAX_EXECUTION_TIME/' /etc/php/$PHP_VERSION/apache2/php.ini || echo 'max_execution_time = $PHP_MAX_EXECUTION_TIME' >> /etc/php/$PHP_VERSION/apache2/php.ini" 637 | lxc exec "$NEW_MISP" -- bash -c "grep -q '^upload_max_filesize' /etc/php/$PHP_VERSION/apache2/php.ini && sed -i 's/^upload_max_filesize.*/upload_max_filesize = $PHP_UPLOAD_MAX_FILESIZE/' /etc/php/$PHP_VERSION/apache2/php.ini || echo 'upload_max_filesize = $PHP_UPLOAD_MAX_FILESIZE' >> /etc/php/$PHP_VERSION/apache2/php.ini" 638 | lxc exec "$NEW_MISP" -- bash -c "grep -q '^mempost_max_sizeory_limit' /etc/php/$PHP_VERSION/apache2/php.ini && sed -i 's/^post_max_size.*/post_max_size = $PHP_POST_MAX_SIZE/' /etc/php/$PHP_VERSION/apache2/php.ini || echo 'post_max_size = $PHP_POST_MAX_SIZE' >> /etc/php/$PHP_VERSION/apache2/php.ini" 639 | lxc exec "$NEW_MISP" -- sudo service apache2 restart 640 | 641 | createRedisSocket 642 | 643 | # Update 644 | lxc exec "$NEW_MISP" -- sudo -u www-data bash -c "$PATH_TO_MISP/app/Console/cake Admin runUpdates" 645 | 646 | info "Stopping current MISP instance..." 647 | lxc stop "$CURRENT_MISP" 648 | 649 | cleanupMISP 650 | } 651 | 652 | cleanupMySQL(){ 653 | lxc image delete "$MYSQL_IMAGE_NAME" 654 | rm -r "$TEMP" 655 | } 656 | 657 | editMySQLConf(){ 658 | local key=$1 659 | local value=$2 660 | local container=$3 661 | 662 | lxc exec "$container" -- bash -c "\ 663 | if grep -q '^$key' /etc/mysql/mariadb.conf.d/50-server.cnf; then \ 664 | sed -i 's/^$key.*/$key = $value/' /etc/mysql/mariadb.conf.d/50-server.cnf; \ 665 | else \ 666 | awk -v $key='$key = $value' \ 667 | '/^\[mysqld\]/ {print; print $key; next} {print}' \ 668 | /etc/mysql/mariadb.conf.d/50-server.cnf > /tmp/php.ini.modified && \ 669 | mv /tmp/php.ini.modified /etc/mysql/mariadb.conf.d/50-server.cnf; \ 670 | fi" 671 | } 672 | 673 | updateMySQL(){ 674 | trap 'err ${LINENO}' ERR 675 | info "Update MySQL..." 676 | local profile 677 | profile=$(lxc config show "$CURRENT_MYSQL" | yq '.profiles[0]'| tr -d '"') 678 | lxc image import "$MYSQL_IMAGE" --alias "$MYSQL_IMAGE_NAME" 679 | lxc launch "$MYSQL_IMAGE_NAME" "$NEW_MYSQL" --profile="$profile" 680 | sleep 2 681 | 682 | # Apply config 683 | lxc exec "$NEW_MYSQL" -- sed -i 's/bind-address = 127.0.0.1/bind-address = 0.0.0.0/' "/etc/mysql/mariadb.conf.d/50-server.cnf" 684 | INNODB_BUFFER_POOL_SIZE=$(lxc exec "$CURRENT_MYSQL" -- grep "innodb_buffer_pool_size" /etc/mysql/mariadb.conf.d/50-server.cnf | awk -F '=' '/^innodb_buffer_pool_size=/ {print $2}') 685 | INNODB_CHANGE_BUFFERING=$(lxc exec "$CURRENT_MYSQL" -- grep "innodb_change_buffering" /etc/mysql/mariadb.conf.d/50-server.cnf | awk -F '=' '/^innodb_change_buffering=/ {print $2}') 686 | INNODB_IO_CAPACITY=$(lxc exec "$CURRENT_MYSQL" -- grep "innodb_io_capacity" /etc/mysql/mariadb.conf.d/50-server.cnf | awk -F '=' '/^innodb_io_capacity=/ {print $2}') 687 | INNODB_IO_CAPACITY_MAX=$(lxc exec "$CURRENT_MYSQL" -- grep "innodb_io_capacity_max" /etc/mysql/mariadb.conf.d/50-server.cnf | awk -F '=' '/^innodb_io_capacity_max=/ {print $2}') 688 | INNODB_LOG_FILE_SIZE=$(lxc exec "$CURRENT_MYSQL" -- grep "innodb_log_file_size" /etc/mysql/mariadb.conf.d/50-server.cnf | awk -F '=' '/^innodb_log_file_size=/ {print $2}') 689 | INNODB_LOG_FILES_IN_GROUP=$(lxc exec "$CURRENT_MYSQL" -- grep "innodb_log_files_in_group" /etc/mysql/mariadb.conf.d/50-server.cnf | awk -F '=' '/^innodb_log_files_in_group=/ {print $2}') 690 | INNODB_READ_IO_THREADS=$(lxc exec "$CURRENT_MYSQL" -- grep "innodb_read_io_threads" /etc/mysql/mariadb.conf.d/50-server.cnf | awk -F '=' '/^innodb_read_io_threads=/ {print $2}') 691 | INNODB_STATS_PERISTENT=$(lxc exec "$CURRENT_MYSQL" -- grep "innodb_stats_persistent" /etc/mysql/mariadb.conf.d/50-server.cnf | awk -F '=' '/^innodb_stats_persistent=/ {print $2}') 692 | INNODB_WRITE_IO_THREADS=$(lxc exec "$CURRENT_MYSQL" -- grep "innodb_write_io_threads" /etc/mysql/mariadb.conf.d/50-server.cnf | awk -F '=' '/^innodb_write_io_threads=/ {print $2}') 693 | 694 | # Modify the configuration file in the new container 695 | if [ -n "$INNODB_WRITE_IO_THREADS" ]; then 696 | editMySQLConf "innodb_write_io_threads" "$INNODB_WRITE_IO_THREADS" "$NEW_MYSQL" 697 | fi 698 | if [ -n "$INNODB_STATS_PERISTENT" ]; then 699 | editMySQLConf "innodb_stats_persistent" "$INNODB_STATS_PERISTENT" "$NEW_MYSQL" 700 | fi 701 | if [ -n "$INNODB_READ_IO_THREADS" ]; then 702 | editMySQLConf "innodb_read_io_threads" "$INNODB_READ_IO_THREADS" "$NEW_MYSQL" 703 | fi 704 | if [ -n "$INNODB_LOG_FILES_IN_GROUP" ]; then 705 | editMySQLConf "innodb_log_files_in_group" "$INNODB_LOG_FILES_IN_GROUP" "$NEW_MYSQL" 706 | fi 707 | if [ -n "$INNODB_LOG_FILE_SIZE" ]; then 708 | editMySQLConf "innodb_log_file_size" "$INNODB_LOG_FILE_SIZE" "$NEW_MYSQL" 709 | fi 710 | if [ -n "$INNODB_IO_CAPACITY_MAX" ]; then 711 | editMySQLConf "innodb_io_capacity_max" "$INNODB_IO_CAPACITY_MAX" "$NEW_MYSQL" 712 | fi 713 | if [ -n "$INNODB_IO_CAPACITY" ]; then 714 | editMySQLConf "innodb_io_capacity" "$INNODB_IO_CAPACITY" "$NEW_MYSQL" 715 | fi 716 | if [ -n "$INNODB_CHANGE_BUFFERING" ]; then 717 | editMySQLConf "innodb_change_buffering" "$INNODB_CHANGE_BUFFERING" "$NEW_MYSQL" 718 | fi 719 | if [ -n "$INNODB_BUFFER_POOL_SIZE" ]; then 720 | editMySQLConf "innodb_buffer_pool_size" "$INNODB_BUFFER_POOL_SIZE" "$NEW_MYSQL" 721 | fi 722 | 723 | # Move data 724 | info "Copying data to new database ..." 725 | TEMP=$(mktemp -d) 726 | lxc exec "$CURRENT_MYSQL" -- mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases > "$TEMP"/backup.sql 727 | lxc exec "$NEW_MYSQL" -- mysql -u root -p"$MYSQL_ROOT_PASSWORD" < "$TEMP"/backup.sql 728 | 729 | lxc exec "$NEW_MYSQL" -- sudo systemctl restart mysql 730 | 731 | # Configure MISP 732 | lxc exec "$CURRENT_MISP" -- sed -i "s|$CURRENT_MYSQL.lxd|$NEW_MYSQL.lxd|" "$PATH_TO_MISP/app/Config/database.php" 733 | 734 | lxc stop "$CURRENT_MYSQL" 735 | 736 | cleanupMySQL 737 | } 738 | 739 | 740 | cleanupRedis(){ 741 | lxc image delete "$REDIS_IMAGE_NAME" 742 | } 743 | 744 | updateRedis(){ 745 | trap 'err ${LINENO}' ERR 746 | info "Update Redis..." 747 | local profile 748 | profile=$(lxc config show "$CURRENT_REDIS" | yq '.profiles[0]'| tr -d '"') 749 | lxc image import "$REDIS_IMAGE" --alias "$REDIS_IMAGE_NAME" 750 | lxc launch "$REDIS_IMAGE_NAME" "$NEW_REDIS" --profile="$profile" 751 | 752 | # Configure new Redis 753 | local port 754 | port=$(lxc exec "$CURRENT_REDIS" -- grep "port" /etc/redis/redis.conf | awk '/^port/ {print $2}') 755 | lxc exec "$NEW_REDIS" -- sed -i "s/^bind .*/bind 0.0.0.0/" "/etc/redis/redis.conf" 756 | lxc exec "$NEW_REDIS" -- sed -i "s/^port .*/port $port/" "/etc/redis/redis.conf" 757 | lxc exec "$NEW_REDIS" -- systemctl restart redis-server 758 | 759 | # Configure MISP 760 | lxc exec "$CURRENT_MISP" -- sed -i "s/'host' => '$CURRENT_REDIS.lxd'/'host' => '$NEW_REDIS.lxd'/; s/'port' => 6379/'port' => $port/" /var/www/MISP/app/Plugin/CakeResque/Config/config.php 761 | lxc exec "$CURRENT_MISP" -- sudo -H -u www-data -- /var/www/MISP/app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_host" "$NEW_REDIS.lxd" 762 | lxc exec "$CURRENT_MISP" -- sudo -H -u www-data -- /var/www/MISP/app/Console/cake Admin setSetting "MISP.redis_host" "$NEW_REDIS.lxd" 763 | 764 | lxc stop "$CURRENT_REDIS" 765 | 766 | cleanupRedis 767 | } 768 | 769 | cleanupModules(){ 770 | lxc image delete "$MODULES_IMAGE_NAME" 771 | } 772 | 773 | checkModules(){ 774 | local instance=$1 775 | if [ "$(lxc exec "$instance" -- systemctl is-active misp-modules)" = "active" ]; then 776 | okay "Service misp-modules is running." 777 | else 778 | error "Service misp-modules is not running." 779 | lxc stop "$instance" 780 | return 1 781 | fi 782 | } 783 | 784 | updateModules(){ 785 | trap 'err ${LINENO}' ERR 786 | info "Update Modules..." 787 | local profile 788 | profile=$(lxc config show "$CURRENT_MODULES" | yq '.profiles[0]'| tr -d '"') 789 | lxc image import "$MODULES_IMAGE" --alias "$MODULES_IMAGE_NAME" 790 | lxc launch "$MODULES_IMAGE_NAME" "$NEW_MODULES" --profile="$profile" 791 | sleep 5 792 | 793 | checkModules "$NEW_MODULES" 794 | 795 | # configure MISP 796 | lxc exec "$CURRENT_MISP" -- sudo -H -u www-data -- /var/www/MISP/app/Console/cake Admin setSetting "Plugin.Export_services_url" "$NEW_MODULES.lxd" 797 | lxc exec "$CURRENT_MISP" -- sudo -H -u www-data -- /var/www/MISP/app/Console/cake Admin setSetting "Plugin.Import_services_url" "$NEW_MODULES.lxd" 798 | lxc exec "$CURRENT_MISP" -- sudo -H -u www-data -- /var/www/MISP/app/Console/cake Admin setSetting "Plugin.Enrichment_services_url" "$NEW_MODULES.lxd" 799 | 800 | lxc stop "$CURRENT_MODULES" 801 | 802 | cleanupModules 803 | } 804 | 805 | renameContainer(){ 806 | local container=$1 807 | local new_name=$2 808 | if checkLXDContainerRunning "$container"; then 809 | lxc stop "$container" 810 | lxc mv "$container" "$new_name" 811 | lxc start "$new_name" 812 | else 813 | lxc mv "$container" "$new_name" 814 | fi 815 | } 816 | 817 | checkLXDContainerRunning() { 818 | local container_name=$1 819 | local state 820 | state=$(lxc list "^${container_name}$" --format csv -c s) 821 | 822 | if [ "$state" == "RUNNING" ]; then 823 | return 0 824 | fi 825 | return 1 826 | } 827 | 828 | log_to_file() { 829 | local log_message=$1 830 | local log_file=$2 831 | 832 | # Create dir and log file if it does not exist 833 | if [ ! -d "$(dirname "$log_file")" ]; then 834 | mkdir -p "$(dirname "$log_file")" 835 | fi 836 | 837 | local current_time 838 | current_time=$(date "+%Y-%m-%d %H:%M:%S") 839 | echo "[$current_time] $log_message" >> "$log_file" 840 | } 841 | 842 | interactiveConfig(){ 843 | echo 844 | echo "################################################################################" 845 | echo -e "# Welcome to the ${BLUE}MISP-airgap${NC} Update Script #" 846 | echo "#------------------------------------------------------------------------------#" 847 | echo -e "# This update script will guide you through the process of updating your #" 848 | echo -e "# ${BLUE}MISP${NC} installation within an LXD environment. #" 849 | echo -e "# #" 850 | echo -e "# ${VIOLET}Attention:${NC} #" 851 | echo -e "# ${VIOLET}It is recommended to back up your current configuration and data before ${NC}#" 852 | echo -e "# ${VIOLET}proceeding with the update to prevent any potential data loss. ${NC} #" 853 | echo -e "# #" 854 | echo -e "# ${VIOLET}The update process will try to retain your settings, but it is advisable to ${NC} #" 855 | echo -e "# ${VIOLET}review them post-update to ensure everything is configured as expected.${NC} #" 856 | echo -e "# #" 857 | echo "################################################################################" 858 | echo 859 | 860 | declare -A nameCheckArray 861 | 862 | # Ask for current MISP 863 | while true; do 864 | read -r -p "Name of the current misp container: " current_misp 865 | CURRENT_MISP=${current_misp:-$default_current_misp} 866 | if ! checkResourceExists "container" "$CURRENT_MISP"; then 867 | error "Container '$CURRENT_MISP' does not exist." 868 | continue 869 | fi 870 | break 871 | done 872 | 873 | # Ask for MISP update 874 | read -r -p "Do you want to update MISP (y/n, default: $default_misp): " misp 875 | misp=${misp:-$default_misp} 876 | MISP=$(echo "$misp" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 877 | if $MISP; then 878 | 879 | # Ask for MISP image 880 | while true; do 881 | read -r -e -p "What is the path to the MISP image (default: $default_misp_img): " misp_img 882 | misp_img=${misp_img:-$default_misp_img} 883 | if [ ! -f "$misp_img" ]; then 884 | error "The specified file does not exist." 885 | continue 886 | fi 887 | MISP_IMAGE=$misp_img 888 | break 889 | done 890 | 891 | # Ask for new MISP container name 892 | while true; do 893 | read -r -p "Name of the new MISP container (default: $default_new_misp): " new_misp 894 | NEW_MISP=${new_misp:-$default_new_misp} 895 | if [[ ${nameCheckArray[$NEW_MISP]+_} ]]; then 896 | error "Name '$NEW_MISP' has already been used. Please choose a different name." 897 | continue 898 | fi 899 | if ! checkNamingConvention "$NEW_MISP"; then 900 | continue 901 | fi 902 | if checkResourceExists "container" "$NEW_MISP"; then 903 | warn "Container '$NEW_MISP' already exists." 904 | read -r -p "Do you want to proceed anyways (y/n, default: n): " rename_misp 905 | RENAME=$(echo "$rename_misp" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 906 | if $RENAME; then 907 | nameCheckArray[$NEW_MISP]=1 908 | break 909 | fi 910 | continue 911 | fi 912 | nameCheckArray[$NEW_MISP]=1 913 | break 914 | done 915 | 916 | while true; do 917 | read -r -p "Please enter your MySQL root pwd: " root_pwd 918 | MYSQL_ROOT_PASSWORD=${root_pwd} 919 | # Add check? 920 | break 921 | done 922 | fi 923 | 924 | # Ask for MySQL update 925 | read -r -p "Do you want to update MySQL (y/n, default: $default_mysql): " mysql 926 | mysql=${mysql:-$default_mysql} 927 | MYSQL=$(echo "$mysql" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 928 | if $MYSQL; then 929 | 930 | # Ask for MySQL image 931 | while true; do 932 | read -r -e -p "What is the path to the MySQL image (default: $default_mysql_img): " mysql_img 933 | mysql_img=${mysql_img:-$default_mysql_img} 934 | if [ ! -f "$mysql_img" ]; then 935 | error "The specified file does not exist." 936 | continue 937 | fi 938 | MYSQL_IMAGE=$mysql_img 939 | break 940 | done 941 | 942 | # Ask for new MySQL container name 943 | while true; do 944 | read -r -p "Name of the new MySQL container (default: $default_new_mysql): " new_mysql 945 | NEW_MYSQL=${new_mysql:-$default_new_mysql} 946 | if [[ ${nameCheckArray[$NEW_MYSQL]+_} ]]; then 947 | error "Name '$NEW_MYSQL' has already been used. Please choose a different name." 948 | continue 949 | fi 950 | if ! checkNamingConvention "$NEW_MYSQL"; then 951 | continue 952 | fi 953 | if checkResourceExists "container" "$NEW_MYSQL"; then 954 | warn "Container '$NEW_MYSQL' already exists." 955 | read -r -p "Do you want to proceed anyways (y/n, default: n): " rename_mysql 956 | RENAME=$(echo "$rename_mysql" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 957 | if $RENAME; then 958 | nameCheckArray[$NEW_MYSQL]=1 959 | break 960 | fi 961 | continue 962 | fi 963 | 964 | nameCheckArray[$NEW_MYSQL]=1 965 | break 966 | done 967 | 968 | if ! $MISP; then 969 | while true; do 970 | read -r -p "Please enter your MySQL root pwd: " root_pwd 971 | MYSQL_ROOT_PASSWORD=${root_pwd} 972 | # Add check? 973 | break 974 | done 975 | fi 976 | fi 977 | 978 | read -r -p "Do you want to update Redis (y/n, default: $default_redis): " redis 979 | redis=${redis:-$default_redis} 980 | REDIS=$(echo "$redis" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 981 | if $REDIS; then 982 | 983 | # Ask for Redis image 984 | while true; do 985 | read -r -e -p "What is the path to the Redis image (default: $default_redis_img): " redis_img 986 | redis_img=${redis_img:-$default_redis_img} 987 | if [ ! -f "$redis_img" ]; then 988 | error "The specified file does not exist." 989 | continue 990 | fi 991 | REDIS_IMAGE=$redis_img 992 | break 993 | done 994 | 995 | # Ask for new Redis container name 996 | while true; do 997 | read -r -p "Name of the new Redis container (default: $default_new_redis): " new_redis 998 | NEW_REDIS=${new_redis:-$default_new_redis} 999 | if [[ ${nameCheckArray[$NEW_REDIS]+_} ]]; then 1000 | error "Name '$NEW_REDIS' has already been used. Please choose a different name." 1001 | continue 1002 | fi 1003 | if ! checkNamingConvention "$NEW_REDIS"; then 1004 | continue 1005 | fi 1006 | if checkResourceExists "container" "$NEW_REDIS"; then 1007 | warn "Container '$NEW_REDIS' already exists." 1008 | read -r -p "Do you want to proceed anyways (y/n, default: n): " rename_redis 1009 | RENAME=$(echo "$rename_redis" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 1010 | if $RENAME; then 1011 | nameCheckArray[$NEW_REDIS]=1 1012 | break 1013 | fi 1014 | continue 1015 | fi 1016 | nameCheckArray[$NEW_REDIS]=1 1017 | break 1018 | done 1019 | fi 1020 | 1021 | read -r -p "Do you want to update Modules (y/n, default: $default_modules): " modules 1022 | modules=${modules:-$default_modules} 1023 | MODULES=$(echo "$modules" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 1024 | if $MODULES; then 1025 | # Ask for Modules image 1026 | while true; do 1027 | read -r -e -p "What is the path to the Modules image (default: $default_modules_img): " modules_img 1028 | modules_img=${modules_img:-$default_modules_img} 1029 | if [ ! -f "$modules_img" ]; then 1030 | error "The specified file does not exist." 1031 | continue 1032 | fi 1033 | MODULES_IMAGE=$modules_img 1034 | break 1035 | done 1036 | 1037 | # Ask for new Modules container name 1038 | while true; do 1039 | read -r -p "Name of the new Modules container (default: $default_new_modules): " new_modules 1040 | NEW_MODULES=${new_modules:-$default_new_modules} 1041 | if [[ ${nameCheckArray[$NEW_MODULES]+_} ]]; then 1042 | error "Name '$NEW_MODULES' has already been used. Please choose a different name." 1043 | continue 1044 | fi 1045 | if ! checkNamingConvention "$NEW_MODULES"; then 1046 | continue 1047 | fi 1048 | if checkResourceExists "container" "$NEW_MODULES"; then 1049 | warn "Container '$NEW_MODULES' already exists." 1050 | read -r -p "Do you want to proceed anyways (y/n, default: n): " rename_modules 1051 | RENAME=$(echo "$rename_modules" | grep -iE '^y(es)?$' > /dev/null && echo true || echo false) 1052 | if $RENAME; then 1053 | nameCheckArray[$NEW_MODULES]=1 1054 | break 1055 | fi 1056 | continue 1057 | fi 1058 | nameCheckArray[$NEW_MODULES]=1 1059 | break 1060 | done 1061 | fi 1062 | 1063 | # Output values set by the user 1064 | echo -e "\nValues set:" 1065 | echo "--------------------------------------------------------------------------------------------------------------------" 1066 | echo -e "Current MISP Container: ${GREEN}$CURRENT_MISP${NC}" 1067 | echo -e "Update MISP: ${GREEN}$MISP${NC}" 1068 | if $MISP; then 1069 | echo -e "New MISP Image Path: ${GREEN}$MISP_IMAGE${NC}" 1070 | echo -e "New MISP Container Name: ${GREEN}$NEW_MISP${NC}" 1071 | echo -e "MySQL Root Password: ${GREEN}$MYSQL_ROOT_PASSWORD${NC}" 1072 | fi 1073 | echo "--------------------------------------------------------------------------------------------------------------------" 1074 | echo -e "Update MySQL: ${GREEN}$MYSQL${NC}" 1075 | if $MYSQL; then 1076 | echo -e "New MySQL Image Path: ${GREEN}$MYSQL_IMAGE${NC}" 1077 | echo -e "New MySQL Container Name: ${GREEN}$NEW_MYSQL${NC}" 1078 | echo -e "MySQL Root Password: ${GREEN}$MYSQL_ROOT_PASSWORD${NC}" 1079 | fi 1080 | echo "--------------------------------------------------------------------------------------------------------------------" 1081 | echo -e "Update Redis: ${GREEN}$REDIS${NC}" 1082 | if $REDIS; then 1083 | echo -e "New Redis Image Path: ${GREEN}$REDIS_IMAGE${NC}" 1084 | echo -e "New Redis Container Name: ${GREEN}$NEW_REDIS${NC}" 1085 | fi 1086 | echo "--------------------------------------------------------------------------------------------------------------------" 1087 | echo -e "Update Modules: ${GREEN}$MODULES${NC}" 1088 | if $MODULES; then 1089 | echo -e "New Modules Image Path: ${GREEN}$MODULES_IMAGE${NC}" 1090 | echo -e "New Modules Container Name: ${GREEN}$NEW_MODULES${NC}" 1091 | fi 1092 | echo "--------------------------------------------------------------------------------------------------------------------" 1093 | 1094 | # Ask for confirmation 1095 | read -r -p "Do you want to proceed with the installation? (y/n): " confirm 1096 | confirm=${confirm:-$default_confirm} 1097 | if [[ $confirm != "y" ]]; then 1098 | warn "Installation aborted." 1099 | exit 1 1100 | fi 1101 | } 1102 | 1103 | # --------- MAIN --------- 1104 | if [ -z "$1" ]; then 1105 | usage 1106 | exit 0 1107 | fi 1108 | checkSoftwareDependencies 1109 | setVars 1110 | setDefaultArgs 1111 | 1112 | # Check for interactive install 1113 | INTERACTIVE=false 1114 | for arg in "$@"; do 1115 | if [[ $arg == "-i" ]] || [[ $arg == "--interactive" ]]; then 1116 | INTERACTIVE=true 1117 | break 1118 | fi 1119 | done 1120 | 1121 | if [ "$INTERACTIVE" = true ]; then 1122 | interactiveConfig 1123 | else 1124 | nonInteractiveConfig "$@" 1125 | fi 1126 | 1127 | validateArgs 1128 | 1129 | info "Starting update script..." 1130 | 1131 | getAdditionalContainers 1132 | 1133 | info "Make Snapshot of current containers..." 1134 | BACKUP_MISP=$CURRENT_MISP 1135 | BACKUP_MYSQL=$CURRENT_MYSQL 1136 | BACKUP_REDIS=$CURRENT_REDIS 1137 | BACKUP_MODULES=$CURRENT_MODULES 1138 | 1139 | lxc snapshot "$BACKUP_MISP" "$SNAP_NAME" 1140 | lxc snapshot "$BACKUP_MYSQL" "$SNAP_NAME" 1141 | lxc snapshot "$BACKUP_REDIS" "$SNAP_NAME" 1142 | 1143 | if $CUSTOM_MODULES; then 1144 | lxc snapshot "$BACKUP_MODULES" "$SNAP_NAME" 1145 | fi 1146 | 1147 | # Check renaming before updating 1148 | if [ "$CURRENT_MISP" == "$NEW_MISP" ]; then 1149 | MISP_NEW_EQ_OLD=true 1150 | fi 1151 | if [ "$CURRENT_MYSQL" == "$NEW_MYSQL" ]; then 1152 | MYSQL_NEW_EQ_OLD=true 1153 | fi 1154 | if [ "$CURRENT_REDIS" == "$NEW_REDIS" ]; then 1155 | REDIS_NEW_EQ_OLD=true 1156 | fi 1157 | if [ "$CURRENT_MODULES" == "$NEW_MODULES" ]; then 1158 | MODULES_NEW_EQ_OLD=true 1159 | fi 1160 | 1161 | trap 'interrupt' INT 1162 | trap 'err ${LINENO}' ERR 1163 | 1164 | info "Update containers..." 1165 | creation_date=$(date "+%Y-%m-%d-%H-%M-%S") 1166 | if $MISP; then 1167 | if $MISP_NEW_EQ_OLD; then 1168 | info "Renaming container $CURRENT_MISP to updated-$ORIGINAL_MISP-$creation_date" 1169 | updated_container_name="updated-$ORIGINAL_MISP-$creation_date" 1170 | renameContainer "$CURRENT_MISP" "$updated_container_name" 1171 | log_to_file "Renamed container $CURRENT_MISP to $updated_container_name" "$NAMING_LOG_FILE" 1172 | CURRENT_MISP=$updated_container_name 1173 | RENAMED_MISP=true 1174 | fi 1175 | updateMISP 1176 | CURRENT_MISP=$NEW_MISP 1177 | fi 1178 | if $MYSQL; then 1179 | if $MYSQL_NEW_EQ_OLD; then 1180 | info "Renaming container $CURRENT_MYSQL to updated-$ORIGINAL_MYSQL-$creation_date" 1181 | updated_container_name="updated-$ORIGINAL_MYSQL-$creation_date" 1182 | renameContainer "$CURRENT_MYSQL" "$updated_container_name" 1183 | log_to_file "Renamed container $CURRENT_MYSQL to $updated_container_name" "$NAMING_LOG_FILE" 1184 | CURRENT_MYSQL=$updated_container_name 1185 | RENAMED_MYSQL=true 1186 | fi 1187 | updateMySQL 1188 | CURRENT_MYSQL=$NEW_MYSQL 1189 | fi 1190 | if $REDIS; then 1191 | if $REDIS_NEW_EQ_OLD; then 1192 | info "Renaming container $CURRENT_REDIS to updated-$ORIGINAL_REDIS-$creation_date" 1193 | updated_container_name="updated-$ORIGINAL_REDIS-$creation_date" 1194 | renameContainer "$CURRENT_REDIS" "$updated_container_name" 1195 | log_to_file "Renamed container $CURRENT_REDIS to $updated_container_name" "$NAMING_LOG_FILE" 1196 | CURRENT_REDIS=$updated_container_name 1197 | RENAMED_REDIS=true 1198 | fi 1199 | updateRedis 1200 | CURRENT_REDIS=$NEW_REDIS 1201 | fi 1202 | if $MODULES; then 1203 | if $MODULES_NEW_EQ_OLD; then 1204 | info "Renaming container $CURRENT_MODULES to updated-$ORIGINAL_MODULES-$creation_date" 1205 | updated_container_name="updated-$ORIGINAL_MODULES-$creation_date" 1206 | renameContainer "$CURRENT_MODULES" "$updated_container_name" 1207 | log_to_file "Renamed container $CURRENT_MODULES to $updated_container_name" "$NAMING_LOG_FILE" 1208 | CURRENT_MODULES=$updated_container_name 1209 | RENAMED_MODULES=true 1210 | fi 1211 | updateModules 1212 | CURRENT_MODULES=$NEW_MODULES 1213 | fi 1214 | 1215 | 1216 | # Restart worker supervisor 1217 | lxc exec $CURRENT_MISP -- systemctl restart supervisor 1218 | 1219 | # Print info 1220 | misp_ip=$(lxc list "$CURRENT_MISP" --format=json | jq -r '.[0].state.network.eth0.addresses[] | select(.family=="inet").address') 1221 | 1222 | echo "███ ███ ██ ███████ ██████ █████ ██ ██████ ██████ █████ ██████ " 1223 | echo "████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ " 1224 | echo "██ ████ ██ ██ ███████ ██████ █████ ███████ ██ ██████ ██ ███ ███████ ██████ " 1225 | echo "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ " 1226 | echo "██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██████ ██ ██ ██ " 1227 | echo "--------------------------------------------------------------------------------------------" 1228 | echo -e "${BLUE}MISP ${NC}is up and running on $misp_ip" 1229 | echo "--------------------------------------------------------------------------------------------" 1230 | echo "Hint: Be aware that the IP address of your container has changed! You may need to adjust some" 1231 | echo "configuration (e.g. /etc/hosts)" 1232 | echo "--------------------------------------------------------------------------------------------" 1233 | -------------------------------------------------------------------------------- /build/AIRGAP_INSTALL.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script is based on the official MISP 2.5 installation script for Ubuntu 4 | # 24.04. It installs a MISP core without MariaDB and MISP modules 5 | 6 | 7 | random_string() { 8 | cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 9 | } 10 | 11 | # Configure the following variables in advance for your environment 12 | ## required settings - please change all of these, failing to do so will result in a non-working installation or a highly insecure installation 13 | PASSWORD="$(random_string)" 14 | MISP_DOMAIN='misp.local' 15 | PATH_TO_SSL_CERT='' 16 | INSTALL_SSDEEP='n' # y/n, if you want to install ssdeep, set to 'y', however, this will require the installation of make 17 | 18 | ## optional settings 19 | MISP_PATH='/var/www/MISP' 20 | APACHE_USER='www-data' 21 | 22 | ### DB settings, if you want to use a different DB host, name, user, or password, please change these 23 | DBHOST='localhost' 24 | DBUSER_ADMIN='root' 25 | DBPASSWORD_ADMIN='' # Default on Ubuntu is a passwordless root account, if you have changed it, please set it here 26 | DBNAME='misp' 27 | DBPORT='3306' 28 | DBUSER_MISP='misp' 29 | DBPASSWORD_MISP="$(random_string)" 30 | 31 | ### Supervisor settings 32 | SUPERVISOR_USER='supervisor' 33 | SUPERVISOR_PASSWORD="$(random_string)" 34 | 35 | ### PHP settings 36 | upload_max_filesize="50M" 37 | post_max_size="50M" 38 | max_execution_time="300" 39 | memory_limit="2048M" 40 | 41 | ## GPG 42 | GPG_EMAIL_ADDRESS="admin@admin.test" 43 | GPG_PASSPHRASE="$(openssl rand -hex 32)" 44 | 45 | ### Only needed if no SSL CERT is provided 46 | OPENSSL_C='LU' 47 | OPENSSL_ST='Luxembourg' 48 | OPENSSL_L='Luxembourg' 49 | OPENSSL_O='MISP' 50 | OPENSSL_OU='MISP' 51 | OPENSSL_CN=${MISP_DOMAIN} 52 | OPENSSL_EMAILADDRESS='misp@'${MISP_DOMAIN} 53 | 54 | # Some helper functions shamelessly copied from @da667's automisp install script. 55 | 56 | logfile=/var/log/misp_install.log 57 | mkfifo ${logfile}.pipe 58 | tee < ${logfile}.pipe $logfile & 59 | exec &> ${logfile}.pipe 60 | rm ${logfile}.pipe 61 | 62 | function install_packages () 63 | { 64 | install_params=("$@") 65 | for i in "${install_params[@]}"; 66 | do 67 | sudo apt-get install -y "$i" &>> $logfile 68 | error_check "$i installation" 69 | done 70 | } 71 | 72 | 73 | function error_check 74 | { 75 | if [ $? -eq 0 ]; then 76 | print_ok "$1 successfully completed." 77 | else 78 | print_error "$1 failed. Please check $logfile for more details." 79 | exit 1 80 | fi 81 | } 82 | 83 | function error_check_soft 84 | { 85 | if [ $? -eq 0 ]; then 86 | print_ok "$1 successfully completed." 87 | else 88 | print_error "$1 failed. Please check $logfile for more details. This is not a blocking failure though, proceeding..." 89 | fi 90 | } 91 | 92 | function print_status () 93 | { 94 | echo -e "\x1B[01;34m[STATUS]\x1B[0m $1" 95 | } 96 | 97 | function print_ok () 98 | { 99 | echo -e "\x1B[01;32m[OK]\x1B[0m $1" 100 | } 101 | 102 | function print_error () 103 | { 104 | echo -e "\x1B[01;31m[ERROR]\x1B[0m $1" 105 | } 106 | 107 | function print_notification () 108 | { 109 | echo -e "\x1B[01;33m[NOTICE]\x1B[0m $1" 110 | } 111 | 112 | function os_version_check () 113 | { 114 | # Check if we're on Ubuntu 24.04 as expected: 115 | UBUNTU_VERSION=$(lsb_release -a | grep Release | grep -oP '[\d-]+.[\d-]+$') 116 | if [[ "$UBUNTU_VERSION" != "24.04" ]]; then 117 | print_error "This upgrade tool expects you to be running Ubuntu 24.04. If you are on a prior upgrade of Ubuntu, please make sure that you upgrade your distribution first, then execute this script again." 118 | exit 1 119 | fi 120 | } 121 | 122 | BLUE="\033[1;34m" 123 | NC="\033[0m" 124 | echo -e "${BLUE}███╗ ███╗${NC}██╗███████╗██████╗ " 125 | echo -e "${BLUE}████╗ ████║${NC}██║██╔════╝██╔══██╗" 126 | echo -e "${BLUE}██╔████╔██║${NC}██║███████╗██████╔╝" 127 | echo -e "${BLUE}██║╚██╔╝██║${NC}██║╚════██║██╔═══╝ " 128 | echo -e "${BLUE}██║ ╚═╝ ██║${NC}██║███████║██║ " 129 | echo -e "${BLUE}╚═╝ ╚═╝${NC}╚═╝╚══════╝╚═╝ " 130 | echo -e "v2.5 Setup on Ubuntu 24.04 LTS" 131 | 132 | os_version_check 133 | 134 | save_settings() { 135 | echo "[$(date)] MISP installation 136 | 137 | [MISP admin user] 138 | - Admin Username: admin@admin.test 139 | - Admin Password: ${PASSWORD} 140 | - Admin API key: ${MISP_USER_KEY} 141 | 142 | [MYSQL ADMIN] 143 | - Username: ${DBUSER_ADMIN} 144 | - Password: ${DBPASSWORD_ADMIN} 145 | 146 | [MYSQL MISP] 147 | - Username: ${DBUSER_MISP} 148 | - Password: ${DBPASSWORD_MISP} 149 | 150 | [MISP internal] 151 | - Path: ${MISP_PATH} 152 | - Apache user: ${APACHE_USER} 153 | - GPG Email: ${GPG_EMAIL_ADDRESS} 154 | - GPG Passphrase: ${GPG_PASSPHRASE} 155 | - SUPERVISOR_USER: ${SUPERVISOR_USER} 156 | - SUPERVISOR_PASSWORD: ${SUPERVISOR_PASSWORD} 157 | " | tee /var/log/misp_settings.txt &>> $logfile 158 | 159 | print_notification "Settings saved to /var/log/misp_settings.txt" 160 | } 161 | 162 | print_status "Updating base system..." 163 | sudo apt-get update &>> $logfile 164 | sudo apt-get upgrade -y &>> $logfile 165 | error_check "Base system update" 166 | 167 | print_status "Installing apt packages (git curl python3 python3-pip python3-virtualenv apache2 zip gcc sudo binutils openssl supervisor)..." 168 | declare -a packages=( git curl python3 python3-pip python3-virtualenv apache2 zip gcc sudo binutils openssl supervisor ); 169 | install_packages ${packages[@]} 170 | error_check "Basic dependencies installation" 171 | 172 | print_status "Installing MariaDB Client..." 173 | declare -a packages=( mariadb-client ); 174 | install_packages ${packages[@]} 175 | error_check "MariaDB Client installation" 176 | 177 | print_status "Installing PHP and the list of required extensions..." 178 | declare -a packages=( valkey-server php8.3 php8.3-cli php8.3-dev php8.3-xml php8.3-mysql php8.3-opcache php8.3-readline php8.3-mbstring php8.3-zip \ 179 | php8.3-intl php8.3-bcmath php8.3-gd php8.3-redis php8.3-gnupg php8.3-apcu libapache2-mod-php8.3 php8.3-curl ); 180 | install_packages ${packages[@]} 181 | PHP_ETC_BASE=/etc/php/8.3 182 | PHP_INI=${PHP_ETC_BASE}/apache2/php.ini 183 | error_check "PHP and required extensions installation." 184 | 185 | # Install composer and the composer dependencies of MISP 186 | 187 | print_status "Installing composer..." 188 | 189 | ## make pip and composer happy 190 | sudo mkdir /var/www/.cache/ 191 | sudo chown -R ${APACHE_USER}:${APACHE_USER} /var/www/.cache/ 192 | 193 | curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php &>> $logfile 194 | COMPOSER_HASH=`curl -sS https://composer.github.io/installer.sig` 195 | php -r "if (hash_file('SHA384', '/tmp/composer-setup.php') === '$COMPOSER_HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" &>> $logfile 196 | sudo php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer &>> $logfile 197 | error_check "Composer installation" 198 | 199 | print_status "Configuring php and MySQL configs..." 200 | for key in upload_max_filesize post_max_size max_execution_time max_input_time memory_limit 201 | do 202 | sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI 203 | done 204 | sudo sed -i "s/^\(session.sid_length\).*/\1 = 32/" $PHP_INI 205 | sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = 1/" $PHP_INI 206 | sudo sed -i "s/^\(session.save_handler\).*/\1 = redis/" $PHP_INI 207 | sudo sed -i "/session.save_handler/a session.save_path = 'tcp:\/\/localhost:6379'/" $PHP_INI 208 | 209 | sudo service apache2 restart 210 | error_check "Apache restart" 211 | 212 | # Probably only PHP need to be cofnigured? 213 | print_ok "PHP and MySQL configured..." 214 | 215 | print_status "Installing PECL extensions..." 216 | 217 | sudo pecl channel-update pecl.php.net &>> $logfile || echo "Continuing despite error in updating PECL channel" 218 | sudo pecl install brotli &>> $logfile 219 | error_check_soft "PECL brotli extension installation" || echo "Continuing despite error in installing PECL brotli extension" 220 | sudo pecl install simdjson &>> $logfile 221 | error_check_soft "PECL simdjson extension installation" || echo "Continuing despite error in installing PECL simdjson extension" 222 | sudo pecl install zstd &>> $logfile 223 | error_check_soft "PECL zstd extension installation" || echo "Continuing despite error in installing PECL zstd extension" 224 | 225 | if [ $INSTALL_SSDEEP == "y" ]; then 226 | sudo apt install make -y &>> $logfile 227 | error_check "The installation of make" || echo "Continuing despite error in installing make" 228 | 229 | git clone --recursive --depth=1 https://github.com/JakubOnderka/pecl-text-ssdeep.git /tmp/pecl-text-ssdeep 230 | error_check "Jakub Onderka's PHP8 SSDEEP extension cloning" || echo "Continuing despite error in cloning SSDEEP extension" 231 | 232 | cd /tmp/pecl-text-ssdeep && phpize && ./configure && make && make install 233 | error_check "Jakub Onderka's PHP8 SSDEEP extension compilation and installation" || echo "Continuing despite error in SSDEEP compilation and installation" 234 | fi 235 | 236 | 237 | print_status "Cloning MISP" 238 | sudo git clone https://github.com/MISP/MISP.git ${MISP_PATH} &>> $logfile 239 | error_check "MISP cloning" 240 | cd ${MISP_PATH} 241 | git fetch origin 2.5 &>> $logfile 242 | error_check "Fetching 2.5 branch" 243 | git checkout 2.5 &>> $logfile 244 | error_check "Checking out 2.5 branch" 245 | 246 | print_status "Cloning MISP submodules..." 247 | sudo git config --global --add safe.directory ${MISP_PATH} &>> $logfile 248 | sudo git -C ${MISP_PATH} submodule update --init --recursive &>> $logfile 249 | error_check "MISP submodules cloning" 250 | sudo git -C ${MISP_PATH} submodule foreach --recursive git config core.filemode false &>> $logfile 251 | sudo chown -R ${APACHE_USER}:${APACHE_USER} ${MISP_PATH} &>> $logfile 252 | sudo chown -R ${APACHE_USER}:${APACHE_USER} ${MISP_PATH}/.git &>> $logfile 253 | print_ok "MISP's submodules cloned." 254 | 255 | print_status "Installing MISP composer dependencies..." 256 | cd ${MISP_PATH}/app 257 | sudo -u ${APACHE_USER} composer install --no-dev --no-interaction --prefer-dist &>> $logfile 258 | error_check "MISP composer dependencies installation" 259 | 260 | print_status "Moving and configuring MISP php config files.." 261 | 262 | cd ${MISP_PATH}/app/Config 263 | cp -a bootstrap.default.php bootstrap.php 264 | cp -a database.default.php database.php 265 | cp -a core.default.php core.php 266 | cp -a config.default.php config.php 267 | sed -i "s#3306#${DBPORT}#" database.php 268 | sed -i "s#'host' => 'localhost'#'host' => '$DBHOST'#" database.php 269 | sed -i "s#db login#$DBUSER_MISP#" database.php 270 | sed -i "s#db password#$DBPASSWORD_MISP#" database.php 271 | sed -i "s#'database' => 'misp'#'database' => '$DBNAME'#" database.php 272 | sed -i "s#Rooraenietu8Eeyo> $logfile 282 | error_check "Self-signed SSL certificate generation" 283 | else 284 | print_status "Using provided SSL certificate." 285 | fi 286 | 287 | # Generate misp-ssl.conf 288 | print_status "Creating Apache configuration file for MISP..." 289 | 290 | echo " 291 | ServerAdmin admin@$MISP_DOMAIN 292 | ServerName $MISP_DOMAIN 293 | 294 | Redirect permanent / https://$MISP_DOMAIN 295 | 296 | LogLevel warn 297 | ErrorLog /var/log/apache2/misp.local_error.log 298 | CustomLog /var/log/apache2/misp.local_access.log combined 299 | ServerSignature Off 300 | 301 | 302 | 303 | ServerAdmin admin@$MISP_DOMAIN 304 | ServerName $MISP_DOMAIN 305 | DocumentRoot $MISP_PATH/app/webroot 306 | 307 | 308 | Options -Indexes 309 | AllowOverride all 310 | Require all granted 311 | Order allow,deny 312 | allow from all 313 | 314 | 315 | SSLEngine On 316 | SSLCertificateFile /etc/ssl/private/misp.local.crt 317 | SSLCertificateKeyFile /etc/ssl/private/misp.local.key 318 | 319 | LogLevel warn 320 | ErrorLog /var/log/apache2/misp.local_error.log 321 | CustomLog /var/log/apache2/misp.local_access.log combined 322 | ServerSignature Off 323 | Header set X-Content-Type-Options nosniff 324 | Header set X-Frame-Options DENY 325 | " | sudo tee /etc/apache2/sites-available/misp-ssl.conf &>> $logfile 326 | 327 | error_check "Apache configuration file creation" &>> $logfile 328 | 329 | print_status "Setting up Python environment for MISP" 330 | 331 | # Create a python3 virtualenv 332 | sudo -u ${APACHE_USER} virtualenv -p python3 ${MISP_PATH}/venv &>> $logfile 333 | error_check "Python virtualenv creation" 334 | 335 | cd ${MISP_PATH} 336 | . ./venv/bin/activate &>> $logfile 337 | error_check "Python virtualenv activation" 338 | 339 | # install python dependencies 340 | ${MISP_PATH}/venv/bin/pip install -r ${MISP_PATH}/requirements.txt &>> $logfile 341 | error_check "Python dependencies installation" 342 | 343 | chown -R ${APACHE_USER}:${APACHE_USER} ${MISP_PATH}/venv 344 | 345 | print_status "Setting up background workers" 346 | 347 | sudo echo " 348 | [inet_http_server] 349 | port=127.0.0.1:9001 350 | username=$SUPERVISOR_USER 351 | password=$SUPERVISOR_PASSWORD" | sudo tee -a /etc/supervisor/supervisord.conf &>> $logfile 352 | 353 | sudo echo "[group:misp-workers] 354 | programs=default,email,cache,prio,update 355 | 356 | [program:default] 357 | directory=$MISP_PATH 358 | command=$MISP_PATH/app/Console/cake start_worker default 359 | process_name=%(program_name)s_%(process_num)02d 360 | numprocs=5 361 | autostart=true 362 | autorestart=true 363 | redirect_stderr=false 364 | stderr_logfile=$MISP_PATH/app/tmp/logs/misp-workers-errors.log 365 | stdout_logfile=$MISP_PATH/app/tmp/logs/misp-workers.log 366 | directory=$MISP_PATH 367 | user=$APACHE_USER 368 | 369 | [program:prio] 370 | directory=$MISP_PATH 371 | command=$MISP_PATH/app/Console/cake start_worker prio 372 | process_name=%(program_name)s_%(process_num)02d 373 | numprocs=5 374 | autostart=true 375 | autorestart=true 376 | redirect_stderr=false 377 | stderr_logfile=$MISP_PATH/app/tmp/logs/misp-workers-errors.log 378 | stdout_logfile=$MISP_PATH/app/tmp/logs/misp-workers.log 379 | directory=$MISP_PATH 380 | user=$APACHE_USER 381 | 382 | [program:email] 383 | directory=$MISP_PATH 384 | command=$MISP_PATH/app/Console/cake start_worker email 385 | process_name=%(program_name)s_%(process_num)02d 386 | numprocs=5 387 | autostart=true 388 | autorestart=true 389 | redirect_stderr=false 390 | stderr_logfile=$MISP_PATH/app/tmp/logs/misp-workers-errors.log 391 | stdout_logfile=$MISP_PATH/app/tmp/logs/misp-workers.log 392 | directory=$MISP_PATH 393 | user=$APACHE_USER 394 | 395 | [program:update] 396 | directory=$MISP_PATH 397 | command=$MISP_PATH/app/Console/cake start_worker update 398 | process_name=%(program_name)s_%(process_num)02d 399 | numprocs=1 400 | autostart=true 401 | autorestart=true 402 | redirect_stderr=false 403 | stderr_logfile=$MISP_PATH/app/tmp/logs/misp-workers-errors.log 404 | stdout_logfile=$MISP_PATH/app/tmp/logs/misp-workers.log 405 | directory=$MISP_PATH 406 | user=$APACHE_USER 407 | 408 | [program:cache] 409 | directory=$MISP_PATH 410 | command=$MISP_PATH/app/Console/cake start_worker cache 411 | process_name=%(program_name)s_%(process_num)02d 412 | numprocs=5 413 | autostart=true 414 | autorestart=true 415 | redirect_stderr=false 416 | stderr_logfile=$MISP_PATH/app/tmp/logs/misp-workers-errors.log 417 | stdout_logfile=$MISP_PATH/app/tmp/logs/misp-workers.log 418 | user=$APACHE_USER" | sudo tee -a /etc/supervisor/conf.d/misp-workers.conf &>> $logfile 419 | 420 | sudo systemctl restart supervisor &>> $logfile 421 | error_check "Background workers setup" 422 | 423 | # Set settings 424 | # The default install is Python >=3.6 in a virtualenv, setting accordingly 425 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.python_bin" "${MISP_PATH}/venv/bin/python" &>> $logfile 426 | 427 | # Tune global time outs 428 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Session.autoRegenerate" 0 &>> $logfile 429 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Session.timeout" 600 &>> $logfile 430 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Session.cookieTimeout" 3600 &>> $logfile 431 | 432 | # Set the default temp dir 433 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.tmpdir" "${MISP_PATH}/app/tmp" &>> $logfile 434 | 435 | # Change base url, either with this CLI command or in the UI 436 | [[ ! -z ${MISP_DOMAIN} ]] && sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake admin setSetting MISP.baseurl "https://${MISP_DOMAIN}" &>> $logfile 437 | [[ ! -z ${MISP_DOMAIN} ]] && sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.external_baseurl" ${MISP_BASEURL} &>> $logfile 438 | 439 | # Enable GnuPG 440 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "GnuPG.email" "${GPG_EMAIL_ADDRESS}" &>> $logfile 441 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "GnuPG.homedir" "${MISP_PATH}/.gnupg" &>> $logfile 442 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "GnuPG.password" "${GPG_PASSPHRASE}" &>> $logfile 443 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "GnuPG.obscure_subject" true &>> $logfile 444 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "GnuPG.key_fetching_disabled" false &>> $logfile 445 | # FIXME: what if we have not gpg binary but a gpg2 one? 446 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "GnuPG.binary" "$(which gpg)" &>> $logfile 447 | 448 | # Enable installer org and tune some configurables 449 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.host_org_id" 1 &>> $logfile 450 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.email" "${GPG_EMAIL_ADDRESS}" &>> $logfile 451 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.disable_emailing" false &>> $logfile 452 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.contact" "${GPG_EMAIL_ADDRESS}" &>> $logfile 453 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.disablerestalert" true &>> $logfile 454 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.showCorrelationsOnIndex" true &>> $logfile 455 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.default_event_tag_collection" 0 &>> $logfile 456 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.log_new_audit" 1 &>> $logfile 457 | 458 | # Configure background workers 459 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.enabled" 1 &>> $logfile 460 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.redis_host" '127.0.0.1' &>> $logfile 461 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.redis_port" 6379 &>> $logfile 462 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.redis_database" 13 &>> $logfile 463 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.redis_password" "" &>> $logfile 464 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.redis_namespace" "background_jobs" &>> $logfile 465 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.supervisor_host" "localhost" &>> $logfile 466 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.supervisor_port" 9001 &>> $logfile 467 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.supervisor_user" ${SUPERVISOR_USER} &>> $logfile 468 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.supervisor_password" ${SUPERVISOR_PASSWORD} &>> $logfile 469 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "SimpleBackgroundJobs.redis_serializer" "JSON" &>> $logfile 470 | 471 | # Various plugin sightings settings 472 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.Sightings_policy" 0 &>> $logfile 473 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.Sightings_anonymise" false &>> $logfile 474 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.Sightings_anonymise_as" 1 &>> $logfile 475 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.Sightings_range" 365 &>> $logfile 476 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.Sightings_sighting_db_enable" false &>> $logfile 477 | 478 | # ZeroMQ settings 479 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_enable" false &>> $logfile 480 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_host" "127.0.0.1" &>> $logfile 481 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_port" 50000 &>> $logfile 482 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_host" "localhost" &>> $logfile 483 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_port" 6379 &>> $logfile 484 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_database" 1 &>> $logfile 485 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_namespace" "mispq" &>> $logfile 486 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_event_notifications_enable" false &>> $logfile 487 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_object_notifications_enable" false &>> $logfile 488 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_object_reference_notifications_enable" false &>> $logfile 489 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_attribute_notifications_enable" false &>> $logfile 490 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_sighting_notifications_enable" false &>> $logfile 491 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_user_notifications_enable" false &>> $logfile 492 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_organisation_notifications_enable" false &>> $logfile 493 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_include_attachments" false &>> $logfile 494 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Plugin.ZeroMQ_tag_notifications_enable" false &>> $logfile 495 | 496 | # Force defaults to make MISP Server Settings less RED 497 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.language" "eng" &>> $logfile 498 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.proposals_block_attributes" false &>> $logfile 499 | 500 | # Redis block 501 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.redis_host" "127.0.0.1" &>> $logfile 502 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.redis_port" 6379 &>> $logfile 503 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.redis_database" 13 &>> $logfile 504 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.redis_password" "" &>> $logfile 505 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.redis_serializer" "JSON" &>> $logfile 506 | 507 | # Force defaults to make MISP Server Settings less YELLOW 508 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.ssdeep_correlation_threshold" 40 &>> $logfile 509 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.extended_alert_subject" false &>> $logfile 510 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.default_event_threat_level" 4 &>> $logfile 511 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.newUserText" "Dear new MISP user,\\n\\nWe would hereby like to welcome you to the \$org MISP community.\\n\\n Use the credentials below to log into MISP at \$misp, where you will be prompted to manually change your password to something of your own choice.\\n\\nUsername: \$username\\nPassword: \$password\\n\\nIf you have any questions, don't hesitate to contact us at: \$contact.\\n\\nBest regards,\\nYour \$org MISP support team" &>> $logfile 512 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.passwordResetText" "Dear MISP user,\\n\\nA password reset has been triggered for your account. Use the below provided temporary password to log into MISP at \$misp, where you will be prompted to manually change your password to something of your own choice.\\n\\nUsername: \$username\\nYour temporary password: \$password\\n\\nIf you have any questions, don't hesitate to contact us at: \$contact.\\n\\nBest regards,\\nYour \$org MISP support team" &>> $logfile 513 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.enableEventBlocklisting" true &>> $logfile 514 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.enableOrgBlocklisting" true &>> $logfile 515 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.log_client_ip" true &>> $logfile 516 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.log_auth" false &>> $logfile 517 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.log_user_ips" true &>> $logfile 518 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.log_user_ips_authkeys" true &>> $logfile 519 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.disableUserSelfManagement" false &>> $logfile 520 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.disable_user_login_change" false &>> $logfile 521 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.disable_user_password_change" false &>> $logfile 522 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.disable_user_add" false &>> $logfile 523 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.block_event_alert" false &>> $logfile 524 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.block_event_alert_tag" "no-alerts=\"true\"" &>> $logfile 525 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.block_old_event_alert" false &>> $logfile 526 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.block_old_event_alert_age" "" &>> $logfile 527 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.block_old_event_alert_by_date" "" &>> $logfile 528 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.event_alert_republish_ban" true &>> $logfile 529 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.event_alert_republish_ban_threshold" 5 &>> $logfile 530 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.event_alert_republish_ban_refresh_on_retry" false &>> $logfile 531 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.incoming_tags_disabled_by_default" false &>> $logfile 532 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.attachments_dir" "${MISP_PATH}/app/files" &>> $logfile 533 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.download_attachments_on_load" true &>> $logfile 534 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.event_alert_metadata_only" false &>> $logfile 535 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "MISP.terms_download" false &>> $logfile 536 | 537 | # Force defaults to make MISP Server Settings less GREEN 538 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "debug" 0 &>> $logfile 539 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.auth_enforced" false &>> $logfile 540 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.log_each_individual_auth_fail" false &>> $logfile 541 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.rest_client_baseurl" "" &>> $logfile 542 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.advanced_authkeys" true &>> $logfile 543 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.password_policy_length" 12 &>> $logfile 544 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.password_policy_complexity" '/^((?=.*\\d)|(?=.*\\W+))(?![\\n])(?=.*[A-Z])(?=.*[a-z]).*$|.{16,}/' &>> $logfile 545 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.self_registration_message" "If you would like to send us a registration request, please fill out the form below. Make sure you fill out as much information as possible in order to ease the task of the administrators." &>> $logfile 546 | 547 | # Appease the security audit, #hardening 548 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.disable_browser_cache" true &>> $logfile 549 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.check_sec_fetch_site_header" true &>> $logfile 550 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.csp_enforce" true &>> $logfile 551 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.advanced_authkeys" true &>> $logfile 552 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.do_not_log_authkeys" true &>> $logfile 553 | 554 | # Appease the security audit, #loggin 555 | sudo -u ${APACHE_USER} ${MISP_PATH}/app/Console/cake Admin setSetting "Security.username_in_response_header" true &>> $logfile 556 | 557 | print_ok "Settings configured." 558 | 559 | # Enable modules, settings, and default of SSL in Apache 560 | sudo a2dismod status &>> $logfile 561 | sudo a2enmod ssl &>> $logfile 562 | sudo a2enmod rewrite &>> $logfile 563 | sudo a2enmod headers &>> $logfile 564 | sudo a2dissite 000-default &>> $logfile 565 | sudo a2ensite default-ssl &>> $logfile 566 | 567 | # activate new vhost 568 | sudo a2dissite default-ssl &>> $logfile 569 | sudo a2ensite misp-ssl &>> $logfile 570 | 571 | # Restart apache 572 | sudo systemctl restart apache2 &>> $logfile 573 | error_check "Apache restart" 574 | 575 | print_ok "Settings configured." 576 | 577 | print_status "Finalising MISP setup..." 578 | sudo chown -R ${APACHE_USER}:${APACHE_USER} ${MISP_PATH} &>> $logfile 579 | sudo chown -R ${APACHE_USER}:${APACHE_USER} ${MISP_PATH}/.git &>> $logfile 580 | 581 | #save_settings 582 | 583 | print_notification "You can now access your MISP instance at https://${MISP_DOMAIN}" 584 | print_notification "The default admin credentials are:" 585 | print_notification "Username: admin@admin.test" 586 | print_notification "Password: ${PASSWORD}" 587 | print_notification "MISP setup complete. Thank you, and have a very safe, and productive day." 588 | -------------------------------------------------------------------------------- /build/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Color codes 4 | GREEN='\033[0;32m' 5 | YELLOW='\033[1;33m' 6 | BLUE='\033[0;34m' 7 | RED='\033[0;31m' 8 | NC='\033[0m' # No Color 9 | 10 | # Software Depedencies 11 | DEPEDENCIES=(jq curl) 12 | 13 | setVars(){ 14 | REPO_URL="https://api.github.com/repos/MISP/misp-airgap" 15 | PATH_TO_BUILD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | MISP_INFO_TEMPLATE_FILE="$PATH_TO_BUILD/templates/misp_info.json" 17 | MODULES_INFO_TEMPLATE_FILE="$PATH_TO_BUILD/templates/modules_info.json" 18 | MISP_PATH="/var/www/" 19 | PROJECT_NAME=$(generateName "misp") 20 | STORAGE_POOL_NAME=$(generateName "misp") 21 | NETWORK_NAME=$(generateName "net") 22 | NETWORK_NAME=${NETWORK_NAME:0:14} 23 | MISP_CONTAINER=$(generateName "misp") 24 | MYSQL_CONTAINER=$(generateName "mysql") 25 | REDIS_CONTAINER=$(generateName "redis") 26 | VALKEY_CONTAINER=$(generateName "valkey") 27 | MODULES_CONTAINER=$(generateName "modules") 28 | UBUNTU="ubuntu:24.04" 29 | BUILD_REDIS_VERSION=false 30 | BUILD_MYSQL_VERSION=false 31 | BUILD_VALKEY_VERSION=false 32 | REDIS_SERVICE_FILE="$PATH_TO_BUILD/conf/redis-server.service" 33 | VALKEY_SERVICE_FILE="$PATH_TO_BUILD/conf/valkey.service" 34 | } 35 | 36 | setDefaultArgs(){ 37 | default_misp_image="MISP" 38 | default_misp=false 39 | default_mysql_image="MySQL" 40 | default_mysql=false 41 | default_redis_image="Redis" 42 | default_redis=false 43 | default_valkey_image="Valkey" 44 | default_valkey=false 45 | default_modules_image="Modules" 46 | default_modules=false 47 | default_outputdir="" 48 | default_sign=false 49 | } 50 | 51 | error() { 52 | local msg=$1 53 | echo -e "${RED}Error: $msg${NC}" > /dev/tty 54 | } 55 | 56 | warn() { 57 | local msg=$1 58 | echo -e "${YELLOW}Warning: $msg${NC}" > /dev/tty 59 | } 60 | 61 | okay() { 62 | local msg=$1 63 | echo -e "${GREEN}Info: $msg${NC}" > /dev/tty 64 | } 65 | 66 | 67 | getInstallerCommitID(){ 68 | commit_id=$(curl -s $REPO_URL/commits/main | jq -e -r '.sha // empty') 69 | if [ -z "$commit_id" ]; then 70 | error "Unable to retrieve commit ID." 71 | exit 1 72 | fi 73 | echo "$commit_id" 74 | } 75 | 76 | installMISP(){ 77 | local container_name="$1" 78 | 79 | sleep 2 80 | lxc exec "$container_name" -- sed -i "/#\$nrconf{restart} = 'i';/s/.*/\$nrconf{restart} = 'a';/" /etc/needrestart/needrestart.conf 81 | lxc exec "$container_name" -- apt update 82 | lxc exec "$container_name" -- apt upgrade -y 83 | lxc exec "$container_name" -- apt install debconf-utils -y 84 | 85 | # Add MISP user 86 | lxc exec "$container_name" -- useradd -m -s /bin/bash "misp" 87 | 88 | if lxc exec "$container_name" -- id "misp" &>/dev/null; then 89 | # Add the user to the sudo group 90 | lxc exec "$container_name" -- usermod -aG sudo "misp" 91 | echo "User misp has been added to the sudoers group." 92 | else 93 | echo "User misp does not exist." 94 | exit 1 95 | fi 96 | 97 | lxc exec "$container_name" -- bash -c "echo 'misp ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/misp" 98 | lxc exec "$container_name" -- wget --no-cache -O /tmp/AIRGAP_INSTALL.sh https://raw.githubusercontent.com/MISP/misp-airgap/main/build/AIRGAP_INSTALL.sh 99 | lxc exec "$container_name" -- sudo -u "misp" -H sh -c "sudo bash /tmp/AIRGAP_INSTALL.sh -c -u" 100 | lxc exec "$container_name" -- sed -i "/^\$nrconf{restart} = 'a';/s/.*/#\$nrconf{restart} = 'i';/" /etc/needrestart/needrestart.conf 101 | } 102 | 103 | waitForContainer() { 104 | local container_name="$1" 105 | 106 | sleep 1 107 | while true; do 108 | status=$(lxc list --format=json | jq -e --arg name "$container_name" '.[] | select(.name == $name) | .status') 109 | if [ "$status" = "\"Running\"" ]; then 110 | echo -e "${BLUE}$container_name ${GREEN}is running.${NC}" 111 | break 112 | fi 113 | echo "Waiting for $container_name container to start." 114 | sleep 5 115 | done 116 | } 117 | 118 | cleanupProject(){ 119 | local project="$1" 120 | 121 | okay "Starting cleanup ..." 122 | echo "Deleting container in project" 123 | for container in $(lxc query "/1.0/containers?recursion=1&project=${project}" | jq .[].name -r); do 124 | lxc delete --project "${project}" -f "${container}" 125 | done 126 | 127 | echo "Deleting images in project" 128 | for image in $(lxc query "/1.0/images?recursion=1&project=${project}" | jq .[].fingerprint -r); do 129 | lxc image delete --project "${project}" "${image}" 130 | done 131 | 132 | echo "Deleting project" 133 | lxc project delete "${project}" 134 | } 135 | 136 | cleanup(){ 137 | cleanupProject "$PROJECT_NAME" 138 | lxc storage delete "$STORAGE_POOL_NAME" 139 | lxc network delete "$NETWORK_NAME" 140 | } 141 | 142 | generateName(){ 143 | local name="$1" 144 | echo "${name}-$(date +%Y%m%d%H%M%S)" 145 | } 146 | 147 | addMISPInstallerInfo(){ 148 | local container=$1 149 | local version 150 | version=$(getVersionGitTag "$container" "$MISP_PATH/MISP" "www-data") 151 | local misp_commit_id 152 | misp_commit_id=$(getCommitID "$container" "$MISP_PATH/MISP") 153 | 154 | local date 155 | date=$(date '+%Y-%m-%d %H:%M:%S') 156 | 157 | # Installer info 158 | local installer_commit_id 159 | installer_commit_id=$(getInstallerCommitID) 160 | 161 | # Modify the JSON template as needed using jq 162 | jq --arg version "$version" --arg commit_id "$misp_commit_id" --arg date "$date" --arg installer_commit_id "$installer_commit_id" --arg sha1 "$sha1" --arg sha256 "$sha256" --arg sha384 "$sha384" --arg sha512 "$sha512"\ 163 | '.misp_version = $version | .commit_id = $commit_id | .creation_date = $date | .installer.commit_id = $installer_commit_id | .installer.sha1 = $sha1 | .installer.sha256 = $sha256 | .installer.sha384 = $sha384 | .installer.sha512 = $sha512' \ 164 | "$MISP_INFO_TEMPLATE_FILE" > /tmp/info.json 165 | 166 | lxc exec "$container" -- mkdir -p /etc/misp_info 167 | lxc file push /tmp/info.json "${container}"/etc/misp_info/ 168 | rm /tmp/info.json 169 | } 170 | 171 | checkSoftwareDependencies(){ 172 | 173 | for dep in "$@"; do 174 | if ! command -v "$dep" &> /dev/null; then 175 | echo -e "${RED}Error: $dep is not installed.${NC}" 176 | exit 1 177 | fi 178 | done 179 | } 180 | 181 | getMysqlVersion() { 182 | local container=$1 183 | local input 184 | local version 185 | input=$(lxc exec $container -- mariadb --version) 186 | if $BUILD_MYSQL_VERSION; then 187 | version=$(echo "$input" | grep -oP 'mariadb from \K[0-9]+\.[0-9]+\.[0-9]+(?=-MariaDB)' || echo "Version not found") 188 | else 189 | version=$(echo "$input" | grep -oP 'Distrib \K[^,]+(?=-MariaDB)' || echo "Version not found") 190 | fi 191 | echo "$version" 192 | } 193 | 194 | installMISPModules(){ 195 | local container=$1 196 | sleep 2 197 | lxc exec "$container" -- sed -i "/#\$nrconf{restart} = 'i';/s/.*/\$nrconf{restart} = 'a';/" /etc/needrestart/needrestart.conf 198 | lxc exec "$container" -- apt update 199 | lxc exec "$container" -- apt upgrade -y 200 | lxc exec "$container" -- apt install python3-pip python3-venv -y 201 | lxc exec "$container" -- apt install libpoppler-cpp-dev libzbar0 tesseract-ocr libgl1 yara -y 202 | 203 | lxc exec "$container" -- mkdir -p /var/www/MISP/ 204 | lxc exec "$container" -- chown -R www-data:www-data /var/www/MISP/ 205 | lxc exec "$container" -- sudo -u www-data -- python3 -m venv /var/www/MISP/modules 206 | lxc exec "$container" -- bash -c " 207 | source /var/www/MISP/modules/bin/activate && 208 | pip install --upgrade pip && 209 | pip install misp-modules && 210 | pip install \ 211 | git+https://github.com/cartertemm/ODTReader.git \ 212 | git+https://github.com/abenassi/Google-Search-API \ 213 | git+https://github.com/SteveClement/trustar-python.git \ 214 | git+https://github.com/sebdraven/pydnstrails.git \ 215 | git+https://github.com/sebdraven/pyonyphe.git 216 | " 217 | 218 | lxc exec "$container" -- bash -c "cat > /etc/systemd/system/misp-modules.service < /dev/null; then 287 | error "GPG is not installed. Please install it before running this script with signing." 288 | exit 1 289 | fi 290 | local file=$1 291 | 292 | # PATH_TO_BUILD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 293 | SIGN_CONFIG_FILE="$PATH_TO_BUILD/conf/sign.json" 294 | 295 | if [[ ! -f "$SIGN_CONFIG_FILE" ]]; then 296 | error "Config file not found: $SIGN_CONFIG_FILE" 297 | exit 1 298 | fi 299 | 300 | GPG_KEY_ID=$(jq -r '.EMAIL' "$SIGN_CONFIG_FILE") 301 | GPG_KEY_PASSPHRASE=$(jq -r '.PASSPHRASE' "$SIGN_CONFIG_FILE") 302 | 303 | # Check if the GPG key is available 304 | if ! gpg --list-keys | grep -q $GPG_KEY_ID; then 305 | warn "GPG key not found: $GPG_KEY_ID. Create new key." 306 | # Setup GPG key 307 | KEY_NAME=$(jq -r '.NAME' "$SIGN_CONFIG_FILE") 308 | KEY_EMAIL=$(jq -r '.EMAIL' "$SIGN_CONFIG_FILE") 309 | KEY_COMMENT=$(jq -r '.COMMENT' "$SIGN_CONFIG_FILE") 310 | KEY_EXPIRE=$(jq -r '.EXPIRE_DATE' "$SIGN_CONFIG_FILE") 311 | KEY_PASSPHRASE=$(jq -r '.PASSPHRASE' "$SIGN_CONFIG_FILE") 312 | BATCH_FILE=$(mktemp -d)/batch 313 | 314 | cat > "$BATCH_FILE" < /etc/apt/sources.list.d/mariadb.list" 541 | lxc exec "$container" -- apt update 542 | lxc exec "$container" -- apt install -y mariadb-server 543 | lxc exec "$container" -- sed -i "/^\$nrconf{restart} = 'a';/s/.*/#\$nrconf{restart} = 'i';/" /etc/needrestart/needrestart.conf 544 | } 545 | 546 | # Main 547 | checkSoftwareDependencies "${DEPEDENCIES[@]}" 548 | setVars 549 | setDefaultArgs 550 | 551 | VALID_ARGS=$(getopt -o ho:s --long help,outputdir:,misp,mysql,redis,valkey,modules,misp-name:,mysql-name:,redis-name:,valkey-name:,modules-name:,sign,valkey-version:,redis-version:,mysql-version: -- "$@") 552 | if [[ $? -ne 0 ]]; then 553 | exit 1; 554 | fi 555 | 556 | eval set -- "$VALID_ARGS" 557 | while [ $# -gt 0 ]; do 558 | case "$1" in 559 | -h | --help) 560 | usage 561 | exit 0 562 | ;; 563 | --misp) 564 | misp=true 565 | shift 566 | ;; 567 | --mysql) 568 | mysql=true 569 | shift 570 | ;; 571 | --redis) 572 | redis=true 573 | shift 574 | ;; 575 | --valkey) 576 | valkey=true 577 | shift 578 | ;; 579 | --modules) 580 | modules=true 581 | shift 582 | ;; 583 | --misp-name) 584 | misp_image=$2 585 | shift 2 586 | ;; 587 | --mysql-name) 588 | mysql_image=$2 589 | shift 2 590 | ;; 591 | --redis-name) 592 | redis_image=$2 593 | shift 2 594 | ;; 595 | --valkey-name) 596 | valkey_image=$2 597 | shift 2 598 | ;; 599 | --modules-name) 600 | modules_image=$2 601 | shift 2 602 | ;; 603 | --redis-version) 604 | REDIS_VERSION=$2 605 | BUILD_REDIS_VERSION=true 606 | shift 2 607 | ;; 608 | --valkey-version) 609 | VALKEY_VERSION=$2 610 | BUILD_VALKEY_VERSION=true 611 | shift 2 612 | ;; 613 | --mysql-version) 614 | MYSQL_VERSION=$2 615 | BUILD_MYSQL_VERSION=true 616 | shift 2 617 | ;; 618 | -o | --outputdir) 619 | outputdir=$2 620 | shift 2 621 | ;; 622 | -s | --sign) 623 | sign=true 624 | shift 625 | ;; 626 | *) 627 | break 628 | ;; 629 | esac 630 | done 631 | 632 | MISP=${misp:-$default_misp} 633 | MYSQL=${mysql:-$default_mysql} 634 | REDIS=${redis:-$default_redis} 635 | VALKEY=${valkey:-$default_valkey} 636 | MODULES=${modules:-$default_modules} 637 | MISP_IMAGE=${misp_image:-$default_misp_image} 638 | MYSQL_IMAGE=${mysql_image:-$default_mysql_image} 639 | REDIS_IMAGE=${redis_image:-$default_redis_image} 640 | VALKEY_IMAGE=${valkey_image:-$default_valkey_image} 641 | MODULES_IMAGE=${modules_image:-$default_modules_image} 642 | OUTPUTDIR=${outputdir:-$default_outputdir} 643 | SIGN=${sign:-$default_sign} 644 | 645 | if [ ! -e "$OUTPUTDIR" ]; then 646 | error "The specified directory does not exist." 647 | exit 1 648 | fi 649 | 650 | if ! $MISP && ! $MYSQL && ! $REDIS && ! $VALKEY && ! $MODULES; then 651 | error "No image specified!" 652 | exit 1 653 | fi 654 | 655 | echo "----------------------------------------" 656 | echo "Starting MISP-airgap build script ..." 657 | echo "----------------------------------------" 658 | 659 | trap cleanup EXIT 660 | 661 | # Project setup 662 | lxc project create "$PROJECT_NAME" 663 | lxc project switch "$PROJECT_NAME" 664 | lxc storage create "$STORAGE_POOL_NAME" "dir" 665 | lxc network create "$NETWORK_NAME" 666 | 667 | if $MISP; then 668 | lxc launch $UBUNTU "$MISP_CONTAINER" -p default --storage "$STORAGE_POOL_NAME" --network "$NETWORK_NAME" 669 | waitForContainer "$MISP_CONTAINER" 670 | installMISP "$MISP_CONTAINER" 671 | addMISPInstallerInfo "$MISP_CONTAINER" 672 | createLXDImage "$MISP_CONTAINER" "$MISP_IMAGE" "MISP" 673 | successMessage "MISP" 674 | fi 675 | 676 | if $MYSQL; then 677 | lxc launch $UBUNTU "$MYSQL_CONTAINER" -p default --storage "$STORAGE_POOL_NAME" --network "$NETWORK_NAME" 678 | waitForContainer "$MYSQL_CONTAINER" 679 | 680 | if $BUILD_MYSQL_VERSION; then 681 | installMySQLSource "$MYSQL_CONTAINER" "$MYSQL_VERSION" 682 | else 683 | installMySQLApt "$MYSQL_CONTAINER" 684 | fi 685 | 686 | createLXDImage "$MYSQL_CONTAINER" "$MYSQL_IMAGE" "MySQL" 687 | successMessage "MySQL" 688 | fi 689 | 690 | if $REDIS; then 691 | lxc launch $UBUNTU "$REDIS_CONTAINER" -p default --storage "$STORAGE_POOL_NAME" --network "$NETWORK_NAME" 692 | waitForContainer "$REDIS_CONTAINER" 693 | 694 | if $BUILD_REDIS_VERSION; then 695 | installRedisSource "$REDIS_CONTAINER" "$REDIS_VERSION" 696 | else 697 | installRedisApt "$REDIS_CONTAINER" 698 | fi 699 | 700 | createLXDImage "$REDIS_CONTAINER" "$REDIS_IMAGE" "Redis" 701 | successMessage "Redis" 702 | fi 703 | 704 | if $VALKEY; then 705 | lxc launch $UBUNTU "$VALKEY_CONTAINER" -p default --storage "$STORAGE_POOL_NAME" --network "$NETWORK_NAME" 706 | waitForContainer "$VALKEY_CONTAINER" 707 | 708 | if $BUILD_VALKEY_VERSION; then 709 | installValkeySource "$VALKEY_CONTAINER" "$VALKEY_VERSION" 710 | else 711 | installValkeyApt "$VALKEY_CONTAINER" 712 | fi 713 | 714 | createLXDImage "$VALKEY_CONTAINER" "$VALKEY_IMAGE" "Valkey" 715 | successMessage "Valkey" 716 | fi 717 | 718 | if $MODULES; then 719 | lxc launch $UBUNTU "$MODULES_CONTAINER" -p default --storage "$STORAGE_POOL_NAME" --network "$NETWORK_NAME" 720 | installMISPModules "$MODULES_CONTAINER" 721 | 722 | sleep 10 723 | if [ "$(lxc exec "$MODULES_CONTAINER" systemctl is-active misp-modules)" = "active" ]; then 724 | okay "Service misp-modules is running." 725 | else 726 | error "Service misp-modules is not running." 727 | lxc stop "$MODULES_CONTAINER" 728 | cleanup 729 | exit 1 730 | fi 731 | 732 | createLXDImage "$MODULES_CONTAINER" "$MODULES_IMAGE" "Modules" 733 | successMessage "Modules" 734 | fi 735 | 736 | echo "----------------------------------------" 737 | echo "Build script finished." 738 | echo "----------------------------------------" 739 | -------------------------------------------------------------------------------- /build/conf/redis-server.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Redis In-Memory Data Store 3 | After=network.target 4 | 5 | [Service] 6 | User=redis 7 | Group=redis 8 | ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf 9 | ExecStop=/usr/local/bin/redis-cli shutdown 10 | Restart=always 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /build/conf/sign.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "NAME": "admin", 3 | "EMAIL": "admin@admin.test", 4 | "COMMENT": "Key for signing images", 5 | "EXPIRE_DATE": 0, 6 | "PASSPHRASE": "admin" 7 | } -------------------------------------------------------------------------------- /build/conf/tracker.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "check_interval": 600, 3 | "outputdir": "/opt/misp_airgap/images", 4 | "sign": true, 5 | "github": [ 6 | { 7 | "name": "MISP", 8 | "id": "MISP/MISP", 9 | "mode": "commits", 10 | "args": [ 11 | "--misp", 12 | "--misp-name", 13 | "MISP" 14 | ] 15 | }, 16 | { 17 | "name": "MISP-modules", 18 | "id": "MISP/misp-modules", 19 | "mode": "commits", 20 | "args": [ 21 | "--modules", 22 | "--modules-name", 23 | "Modules" 24 | ] 25 | } 26 | ], 27 | "apt": [ 28 | { 29 | "name": "MySQL", 30 | "id": "mariadb-server", 31 | "args": [ 32 | "--mysql", 33 | "--mysql-name", 34 | "MySQL" 35 | ] 36 | }, 37 | { 38 | "name": "Redis", 39 | "id": "redis-server", 40 | "args": [ 41 | "--redis", 42 | "--redis-name", 43 | "Redis" 44 | ] 45 | }, 46 | { 47 | "name": "Valkey", 48 | "id": "valkey-server", 49 | "args": [ 50 | "--valkey", 51 | "--valkey-name", 52 | "Valkey" 53 | ] 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /build/conf/valkey.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Valkey - A high-performance key-value store 3 | After=network.target 4 | 5 | [Service] 6 | ExecStart=/usr/local/bin/valkey-server /etc/valkey/valkey.conf 7 | ExecStop=/usr/local/bin/valkey-cli shutdown 8 | Restart=always 9 | User=root 10 | Group=root 11 | LimitNOFILE=65535 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | 16 | -------------------------------------------------------------------------------- /build/systemd/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$(dirname "$0")" 4 | SERVICE_FILE="updatetracker.service" 5 | SERVICE_PATH="/etc/systemd/system/" 6 | MISP_AIRGAP_PATH="/opt/misp_airgap" 7 | BUILD_DIR="${DIR}/../../build" 8 | BATCH_FILE="/tmp/key_batch" 9 | SIGN_CONFIG_FILE="../conf/sign.json" 10 | 11 | log() { 12 | echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" 13 | } 14 | 15 | echo "Start setting up updatetracker service ..." 16 | 17 | if [[ $EUID -ne 0 ]]; then 18 | log "This script must be run as root or with sudo privileges" 19 | exit 1 20 | fi 21 | 22 | if [[ ! -d "$MISP_AIRGAP_PATH" ]]; then 23 | mkdir -p "$MISP_AIRGAP_PATH"/images || { log "Failed to create directory $MISP_AIRGAP_PATH"; exit 1; } 24 | fi 25 | 26 | if [[ -d "$BUILD_DIR" ]]; then 27 | cp -r "$BUILD_DIR" "$MISP_AIRGAP_PATH/" || { log "Failed to copy build directory"; exit 1; } 28 | else 29 | log "Build directory $BUILD_DIR does not exist" 30 | exit 1 31 | fi 32 | 33 | # Create user if it doesn't exist 34 | if ! id "updatetracker" &>/dev/null; then 35 | useradd -r -s /bin/false updatetracker || { log "Failed to create user updatetracker"; exit 1; } 36 | fi 37 | 38 | # Set ownership and permissions 39 | chown -R updatetracker: "$MISP_AIRGAP_PATH" || { log "Failed to change ownership"; exit 1; } 40 | chmod -R u+x "$MISP_AIRGAP_PATH/build/"*.py || { log "Failed to set execute permission on scripts"; exit 1; } 41 | chmod -R u+x "$MISP_AIRGAP_PATH/build/"*.sh || { log "Failed to set execute permission on scripts"; exit 1; } 42 | chmod -R u+w "$MISP_AIRGAP_PATH/images/" || { log "Failed to set execute permission on images dir"; exit 1; } 43 | 44 | # Add user to lxd group 45 | sudo usermod -aG lxd updatetracker || { log "Failed to add user updatetracker to lxd group"; exit 1; } 46 | mkdir -p /home/updatetracker || { log "Failed to create directory /home/updatetracker"; exit 1; } 47 | chown -R updatetracker: "/home/updatetracker" || { log "Failed to change ownership"; exit 1; } 48 | chmod -R u+w "/home/updatetracker" || { log "Failed to set execute permission on home dir"; exit 1; } 49 | 50 | # Setup GPG key 51 | KEY_NAME=$(jq -r '.NAME' "$SIGN_CONFIG_FILE") 52 | KEY_EMAIL=$(jq -r '.EMAIL' "$SIGN_CONFIG_FILE") 53 | KEY_COMMENT=$(jq -r '.COMMENT' "$SIGN_CONFIG_FILE") 54 | KEY_EXPIRE=$(jq -r '.EXPIRE_DATE' "$SIGN_CONFIG_FILE") 55 | KEY_PASSPHRASE=$(jq -r '.PASSPHRASE' "$SIGN_CONFIG_FILE") 56 | 57 | if ! sudo -u updatetracker bash -c "gpg --list-keys | grep -q $KEY_EMAIL"; then 58 | cat > "$BATCH_FILE" < None: 18 | self.id = id 19 | self.args = args 20 | self.name = name 21 | self.outputdir = outputdir 22 | self.last_seen_update = None 23 | 24 | def _check_for_new_update(self) -> bool: 25 | latest_update = self._get_latest_update() 26 | if latest_update and (latest_update != self.last_seen_update): 27 | print(f"New update found for {self.id}") 28 | self.last_seen_update = latest_update 29 | return True 30 | return False 31 | 32 | def _get_latest_update(self): 33 | raise NotImplementedError 34 | 35 | def _save_state(self): 36 | try: 37 | with open(f"{BUILD_PATH}/systemd/state.json", "r") as file: 38 | states = json.load(file) 39 | except FileNotFoundError: 40 | states = {} 41 | 42 | states[self.id] = self.last_seen_update 43 | 44 | with open(f"{BUILD_PATH}/systemd/state.json", "w") as file: 45 | json.dump(states, file) 46 | 47 | def load_state(self): 48 | try: 49 | with open(f"{BUILD_PATH}/systemd/state.json", "r") as file: 50 | states = json.load(file) 51 | except FileNotFoundError: 52 | states = {} 53 | 54 | self.last_seen_update = states.get(self.id, None) 55 | 56 | def build(self) -> None: 57 | if self._check_for_new_update(): 58 | try: 59 | cmd = [f"{BUILD_PATH}/build.sh"] + self.args + ["-o", self.outputdir] 60 | print(f"Running {cmd}") 61 | result = subprocess.run(cmd, check=False) 62 | if result.returncode != 0: 63 | print(f"Failed to run {cmd} for {self.id}") 64 | return 65 | most_recent_dir = max( 66 | (d for d in Path(self.outputdir).iterdir() if d.is_dir()), 67 | key=os.path.getctime, 68 | default=None, 69 | ) 70 | relative_path = most_recent_dir.relative_to(Path(self.outputdir)) 71 | if os.path.exists(f"{self.outputdir}/latest_{self.name}"): 72 | os.remove(f"{self.outputdir}/latest_{self.name}") 73 | os.symlink(relative_path, f"{self.outputdir}/latest_{self.name}") 74 | print( 75 | f"Created symlink {self.outputdir}/latest_{self.name} to {relative_path}" 76 | ) 77 | self._save_state() 78 | except Exception as e: 79 | print(f"Failed to run {cmd} for {self.id}: {e}") 80 | 81 | def cleanup(self, num_to_keep: int) -> None: 82 | files = os.listdir(self.outputdir) 83 | repo_images = [f for f in files if f.startswith(self.name)] 84 | if len(repo_images) > num_to_keep: 85 | repo_images.sort( 86 | key=lambda x: os.path.getmtime(os.path.join(self.outputdir, x)) 87 | ) 88 | for image in repo_images[:-num_to_keep]: 89 | shutil.rmtree(os.path.join(self.outputdir, image)) 90 | 91 | 92 | class GitHub(Repo): 93 | """Class for tracking GitHub repositories.""" 94 | 95 | def __init__( 96 | self, id: str, mode: str, args: List[str], name: str, outputdir: str 97 | ) -> None: 98 | super().__init__(id, args, name, outputdir) 99 | self.mode = mode 100 | 101 | def _get_latest_update(self) -> Optional[str]: 102 | print(f"Fetching {self.mode} for {self.id}") 103 | url = f"https://api.github.com/repos/{self.id}/{self.mode}" 104 | response = requests.get(url) 105 | if response.status_code == 200: 106 | return response.json()[0]["sha"] 107 | else: 108 | print(f"Failed to fetch {self.mode} for {self.id}") 109 | return None 110 | 111 | 112 | class APT(Repo): 113 | """Class for tracking APT packages.""" 114 | 115 | def __init__(self, id: str, args: List[str], name: str, outputdir: str) -> None: 116 | super().__init__(id, args, name, outputdir) 117 | 118 | def _get_latest_update(self) -> Optional[str]: 119 | try: 120 | cmd = ["apt-cache", "policy", self.id] 121 | print(f"Running {cmd}") 122 | output = subprocess.check_output(cmd).decode("utf-8") 123 | match = re.search(r"Candidate: (\S+)", output) 124 | if match: 125 | return match.group(1) 126 | else: 127 | return None 128 | except: 129 | return None 130 | 131 | 132 | def main(): 133 | with open(f"{BUILD_PATH}/conf/tracker.json") as f: 134 | config = json.load(f) 135 | 136 | repos = [] 137 | for repo in config["github"]: 138 | repos.append( 139 | GitHub( 140 | repo["id"], 141 | repo["mode"], 142 | repo["args"], 143 | repo["name"], 144 | config["outputdir"], 145 | ) 146 | ) 147 | 148 | aptpkg = [] 149 | for package in config["apt"]: 150 | aptpkg.append( 151 | APT(package["id"], package["args"], package["name"], config["outputdir"]) 152 | ) 153 | 154 | for repo in repos + aptpkg: 155 | if config["sign"]: 156 | repo.args.append("-s") 157 | repo.load_state() 158 | 159 | while True: 160 | for repo in repos: 161 | repo.build() 162 | repo.cleanup(num_to_keep=3) 163 | for package in aptpkg: 164 | package.build() 165 | package.cleanup(num_to_keep=3) 166 | sleep(config["check_interval"]) 167 | 168 | 169 | if __name__ == "__main__": 170 | main() 171 | -------------------------------------------------------------------------------- /docs/logo/logo-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/misp-airgap/465a5579a774e8cccb2409857ff84aa5d037d3fd/docs/logo/logo-large.png -------------------------------------------------------------------------------- /docs/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MISP/misp-airgap/465a5579a774e8cccb2409857ff84aa5d037d3fd/docs/logo/logo.png -------------------------------------------------------------------------------- /docs/logo/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 24 | 28 | 29 | 30 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 69 | 75 | 82 | 85 | 89 | 94 | 99 | 104 | 105 | 109 | 114 | 119 | 124 | 125 | 126 | 129 | 136 | 141 | 142 | 150 | 157 | 161 | 165 | 170 | 171 | 175 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | --------------------------------------------------------------------------------