├── .gitignore ├── LICENSE ├── README.md ├── aws ├── iam.tf ├── main.tf ├── network.tf ├── security_group.tf ├── storage.tf ├── terraform.tfvars ├── variables.tf └── vm.tf ├── azure ├── main.tf ├── network.tf ├── security_group.tf ├── storage.tf ├── terraform.tfvars ├── variables.tf └── vm.tf ├── gcp ├── firewall.tf ├── iam.tf ├── main.tf ├── storage.tf ├── terraform.tfvars ├── variables.tf └── vm.tf ├── opencti_scripts ├── connectors.sh └── installer.sh └── userdata └── installation-wrapper-script.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.terraform* 2 | *.tfstate* 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # opencti-terraform 2 | This repository is here to provide you with a quick and easy way to deploy an OpenCTI instance in the cloud (AWS, Azure, or GCP). 3 | 4 | If you run into any issues, please open an issue. 5 | 6 | ## Before you deploy 7 | You will need to first change into the `aws/` or `azure/` or `gcp/` directory _before_ you run `terraform init`. The following sections will bring you through the entire process and outline the various settings you will need to set before you can deploy. 8 | 9 | ### AWS 10 | First, change into the `aws/` directory: 11 | ``` 12 | cd aws/ 13 | ``` 14 | 15 | Before you get going, there are a some variables you will probably want to set. All of these can be found in `aws/terraform.tfvars`: 16 | - `allowed_ips_application`: Array containing each of the IPs that are allowed to access the web application. Default `0.0.0.0/0` all IPs. 17 | - `availability_zone`: The AWS availability zone. Default `us-east-1a`. 18 | - `instance_type`: The AWS instance type to use. Default `t3.2xlarge` (8x32). 19 | - `login_email`: The e-mail address used to login to the application. Default `login.email@example.com`. 20 | - `region`: The AWS region used. Default `us-east`. **NOTE:** if you change this, you will need to change the remote state region in `aws/main.tf`. Variable interpolation is not allowed in that block so it has to be hardcoded. 21 | - `root_volume_size`: The root volume size for the EC2 instance. Without this, the volume is 7.7GB and fills up in a day. Default `32` (GB). Note that this will incur costs. 22 | - `storage_bucket`: The name of the S3 bucket to store scripts and remote state in. Default `opencti-storage`. 23 | - `subnet_id`: The AWS subnet to use. No default specified. 24 | - `vpc_id`: The VPC to use. No default specified. 25 | 26 | If your AWS credentials are not stored in `~/.aws/credentials`, you will need to edit that line in `aws/main.tf`. 27 | 28 | #### Remote state 29 | The remote state is defined in `aws/main.tf`. Variable interpolation is not allowed in that block and the easiest choice (both for writing the code and for you using the code) was to pick sensible defaults and hardcode them. The variables are: 30 | - `key`: The name of the state file. Default `terraform_state`. 31 | - `region`: The region to use. Default `us-east-1`. 32 | - `storage_bucket`: The name of the S3 bucket to store the state file in. Default `opencti-storage`. 33 | 34 | **Important:** If you change the region in `aws/terraform.tfvars`, you will want to change the region here, too. If you want to change the S3 bucket name (defined in `aws/terraform.tfvars`), you will also want to change it here. 35 | 36 | ### Azure 37 | First, change into the `azure/` directory: 38 | ``` 39 | cd azure/ 40 | ``` 41 | 42 | Then, you will need to login to Azure CLI and set some variables. Let's do Azure login first. To that end, just run `az login` to login and be able to deploy the Terraform code. 43 | 44 | Before you deploy, you may wish to change some of the settings. These are all in `azure/terraform.tfvars`: 45 | - `account_name`: The Azure account name. No default; this must be set. 46 | - `admin_user`: The name of the admin user on the VM. Default `azureuser` 47 | - `location`: The Azure region to deploy in. Default `eastus`. 48 | - `login_email`: The e-mail address used to login to the OpenCTI web frontend. Default `login.email@example.com`. 49 | - `os_disk_size`: The VM's disk size (in GB). Default `32` (the [minimum recommended spec](https://github.com/OpenCTI-Platform/opencti/blob/5ede2579ee3c09c248d2111b483560f07d2f2c18/opencti-documentation/docs/getting-started/requirements.md)). 50 | - `storage_bucket`: Name of the storage bucket for storing scripts. Default `opencti-storage`. 51 | 52 | ### GCP 53 | Change into the `gcp/` directory: 54 | ``` 55 | cd gcp/ 56 | ``` 57 | 58 | You will need to create a new project in GCP and set up billing. Note the project ID because you will need it in a minute. Then, set up a service account with the following roles and download the service account key: 59 | - Security Admin 60 | - Owner 61 | - Storage Admin 62 | 63 | The following items can be set in `terraform.tfvars`: 64 | - `credentials`: The path to your service account key file. Please make sure it has the permissions listed above. No default. 65 | - `disk_size`: The disk size (in GB) for the instance. Default `32`. [OpenCTI minimum specs](https://github.com/OpenCTI-Platform/opencti/blob/5ede2579ee3c09c248d2111b483560f07d2f2c18/opencti-documentation/docs/getting-started/requirements.md) is 32GB drive. 66 | - `machine_type`: The GCE machine type to use. Default `e2-standard-8`. [OpenCTI minimum specs](https://github.com/OpenCTI-Platform/opencti/blob/5ede2579ee3c09c248d2111b483560f07d2f2c18/opencti-documentation/docs/getting-started/requirements.md) is 8x16. The default size is 8x32. 67 | - `project_id`: The Google Cloud project ID. No default. 68 | - `region`: The Google Cloud region to run the instance in. Default `us-east1`. 69 | - `zone`: The Google Cloud zone to run the instance in. Default `us-east1-b`. 70 | 71 | ## Deployment 72 | To see what Terraform is going to do and make sure you're cool with it, create a plan (`terraform plan`) and check it over. Once you're good to go, apply it (`terraform apply`). 73 | 74 | ### AWS 75 | Once the instance is online, connect to it via SSM (Systems Manager) in the AWS console. You can follow along with the install by checking the logfile: 76 | ``` 77 | tail -F /var/log/user-data.log 78 | ``` 79 | 80 | ### Azure 81 | To login, run the following commands. These commands will remove the old SSH key, put the new one in place, fix its permissions, and SSH into the VM: 82 | ``` 83 | rm -f ~/.ssh/azureuser 84 | cat terraform.tfstate | jq '.outputs.tls_private_key.value' | sed 's/"//g' | awk '{ gsub(/\\\\n/,"\\n") }1' > ~/.ssh/azureuser 85 | chmod 400 ~/.ssh/azureuser 86 | ssh -i ~/.ssh/azureuser azureuser@$(az vm show --resource-group opencti_rg --name opencti -d --query [publicIps] -o tsv) 87 | ``` 88 | 89 | ### GCP 90 | The apply will probably fail because the APIs (Compute Engine, IAM, etc.) are being activated. If it errors out because of the APIs, wait a few minutes and re-run `terraform apply`. 91 | 92 | ## Post-deployment 93 | Once the installation is complete, you'll want to grab the admin password that was generated. The username is the e-mail you provided in `terraform.tfvars`. Get the password by running the following on the VM: 94 | ``` 95 | cat /opt/opencti/config/production.json | jq '.app.admin.password' 96 | ``` 97 | 98 | Next, go to port 4000 of the public IP of the machine and login with the credentials you just grabbed. 99 | -------------------------------------------------------------------------------- /aws/iam.tf: -------------------------------------------------------------------------------- 1 | # IAM initial config 2 | resource "aws_iam_role" "opencti_role" { 3 | name = "opencti_role" 4 | assume_role_policy = jsonencode({ 5 | Version = "2012-10-17" 6 | Statement = [ 7 | { 8 | Action = "sts:AssumeRole" 9 | Effect = "Allow" 10 | Sid = "" 11 | Principal = { 12 | Service = "ec2.amazonaws.com" 13 | } 14 | }, 15 | ] 16 | }) 17 | } 18 | 19 | resource "aws_iam_instance_profile" "opencti_profile" { 20 | name = "opencti_profile" 21 | role = aws_iam_role.opencti_role.name 22 | } 23 | 24 | # AWS Systems Manager (SSM) 25 | data "aws_iam_policy" "ssm" { 26 | arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" 27 | } 28 | 29 | resource "aws_iam_role_policy_attachment" "opencti_ssm_attach" { 30 | role = aws_iam_role.opencti_role.name 31 | policy_arn = data.aws_iam_policy.ssm.arn 32 | } 33 | 34 | # S3 35 | data "aws_iam_policy" "s3readonly" { 36 | arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" 37 | } 38 | 39 | resource "aws_iam_role_policy_attachment" "opencti_readonly_attach" { 40 | role = aws_iam_role.opencti_role.name 41 | policy_arn = data.aws_iam_policy.s3readonly.arn 42 | } 43 | -------------------------------------------------------------------------------- /aws/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | shared_credentials_file = "~/.aws/credentials" 4 | profile = "default" 5 | } 6 | 7 | # Store Terraform state in S3 8 | terraform { 9 | backend "s3" { 10 | # The bucket name is a variable defined in `terraform.tfvars` (as `storage_bucket`), but variables are not allowed in this block. If you change this, you will need to change that. 11 | bucket = "opencti-storage" 12 | key = "terraform.tfstate" 13 | # Again, no variable interpolation in this block so make sure this matches the region defined in `terraform.tfvars`. Default `us-east-1`. 14 | region = "us-east-1" 15 | } 16 | } 17 | 18 | # These variables aren't meant to be changed by the end user. 19 | locals { 20 | ami_id = "ami-0074ee617a234808d" # Ubuntu 20.04 LTS 21 | opencti_dir = "/opt/opencti" 22 | opencti_install_script_name = "opencti-installer.sh" 23 | opencti_connectors_script_name = "opencti-connectors.sh" 24 | } 25 | -------------------------------------------------------------------------------- /aws/network.tf: -------------------------------------------------------------------------------- 1 | # This code creates a VPC and Subnet. The code applies just fine. But Systems Manager (SSM) is unusable. Says something isn't right. Been tracking it down for far too long and it's outside the scope of this change anyway so commenting and moving along. This VPC/Subnet issue is tracked in #9. 2 | # resource "aws_vpc" "opencti_vpc" { 3 | # cidr_block = "10.1.0.0/16" 4 | 5 | # tags = { 6 | # Name = "OpenCTI VPC" 7 | # } 8 | # } 9 | 10 | # resource "aws_subnet" "opencti_subnet" { 11 | # vpc_id = aws_vpc.opencti_vpc.id 12 | # cidr_block = "10.1.10.0/24" 13 | # availability_zone = var.availability_zone 14 | 15 | # tags = { 16 | # Name = "OpenCTI subnet" 17 | # } 18 | # } 19 | 20 | # resource "aws_network_interface" "opencti_nic" { 21 | # subnet_id = aws_subnet.opencti_subnet.id 22 | # # private_ips = ["10.1.10.100"] 23 | # security_groups = [ aws_security_group.opencti_sg.id ] 24 | 25 | # tags = { 26 | # Name = "primary_network_interface" 27 | # } 28 | # } 29 | -------------------------------------------------------------------------------- /aws/security_group.tf: -------------------------------------------------------------------------------- 1 | # Security group 2 | resource "aws_security_group" "opencti_sg" { 3 | name = "opencti_sg" 4 | vpc_id = var.vpc_id 5 | 6 | ingress { 7 | description = "Allow access to application on port 4000" 8 | from_port = 4000 9 | to_port = 4000 10 | protocol = "tcp" 11 | cidr_blocks = var.allowed_ips_application 12 | } 13 | 14 | egress { 15 | description = "Application can send outbound traffic to these IPs" 16 | from_port = 0 17 | to_port = 0 18 | protocol = "-1" 19 | cidr_blocks = ["0.0.0.0/0"] 20 | } 21 | 22 | tags = { 23 | Name = "opencti security group" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /aws/storage.tf: -------------------------------------------------------------------------------- 1 | # S3 bucket to store install and connectors scripts. 2 | resource "aws_s3_bucket" "opencti_bucket" { 3 | bucket = var.storage_bucket 4 | acl = "private" 5 | 6 | # Turn on bucket versioning. We'll be storing the Terraform state in S3 and versioning will help protect against human error. 7 | versioning { 8 | enabled = true 9 | } 10 | } 11 | 12 | # S3 IAM (I don't think any of these permissions are being used) 13 | data "aws_iam_policy_document" "opencti_s3" { 14 | statement { 15 | actions = [ 16 | "s3:*", 17 | ] 18 | 19 | resources = [ 20 | "arn:aws:s3:::${var.storage_bucket}", 21 | "arn:aws:s3:::${var.storage_bucket}/*", 22 | ] 23 | } 24 | } 25 | 26 | resource "aws_iam_policy" "opencti_s3" { 27 | name = "opencti_s3" 28 | policy = data.aws_iam_policy_document.opencti_s3.json 29 | } 30 | 31 | resource "aws_iam_role_policy_attachment" "opencti_s3_attach" { 32 | role = aws_iam_role.opencti_role.name 33 | policy_arn = aws_iam_policy.opencti_s3.arn 34 | } 35 | 36 | # OpenCTI installer script 37 | resource "aws_s3_bucket_object" "opencti-install-script" { 38 | bucket = aws_s3_bucket.opencti_bucket.id 39 | key = "opencti-installer.sh" 40 | source = "../opencti_scripts/installer.sh" 41 | } 42 | 43 | # OpenCTI connectors script 44 | resource "aws_s3_bucket_object" "opencti-connectors-script" { 45 | bucket = aws_s3_bucket.opencti_bucket.id 46 | key = "opencti-connectors.sh" 47 | source = "../opencti_scripts/connectors.sh" 48 | } 49 | -------------------------------------------------------------------------------- /aws/terraform.tfvars: -------------------------------------------------------------------------------- 1 | # allowed_ips_application = ["0.0.0.0/0"] 2 | # availability_zone = "us-east-1a" 3 | # instance_type = "t3.2xlarge" 4 | login_email = "login.email@example.com" 5 | # region = "us-east-1" 6 | # root_volume_size = 32 7 | # storage_bucket = "opencti-storage" 8 | subnet_id = "" 9 | vpc_id = "" 10 | -------------------------------------------------------------------------------- /aws/variables.tf: -------------------------------------------------------------------------------- 1 | variable "allowed_ips_application" { 2 | description = "List of IP addresses allowed to access application on port 4000 of public IP. Default is all IPs." 3 | type = list(string) 4 | default = ["0.0.0.0/0"] 5 | } 6 | 7 | variable "availability_zone" { 8 | description = "The availability zone to use." 9 | type = string 10 | default = "us-east-1a" 11 | } 12 | 13 | variable "instance_type" { 14 | description = "Instance type to use. Default is 8x32." 15 | type = string 16 | default = "t3.2xlarge" 17 | } 18 | 19 | variable "login_email" { 20 | description = "The e-mail address to use for logging into the OpenCTI instance." 21 | type = string 22 | default = "login.email@example.com" 23 | } 24 | 25 | variable "region" { 26 | description = "The region to deploy in." 27 | type = string 28 | default = "us-east-1" 29 | } 30 | 31 | variable "root_volume_size" { 32 | description = "The size of the root volume." 33 | type = number 34 | default = 32 35 | } 36 | 37 | variable "storage_bucket" { 38 | description = "The name of the S3 storage bucket to store scripts and remote state in." 39 | type = string 40 | default = "opencti-storage" 41 | } 42 | 43 | variable "subnet_id" { 44 | description = "The subnet ID to use." 45 | type = string 46 | } 47 | 48 | variable "vpc_id" { 49 | description = "The VPC ID to use." 50 | type = string 51 | } 52 | -------------------------------------------------------------------------------- /aws/vm.tf: -------------------------------------------------------------------------------- 1 | # EC2 Instance 2 | resource "aws_instance" "opencti_instance" { 3 | ami = local.ami_id 4 | instance_type = var.instance_type 5 | 6 | associate_public_ip_address = true 7 | iam_instance_profile = aws_iam_instance_profile.opencti_profile.name 8 | root_block_device { 9 | volume_size = var.root_volume_size 10 | } 11 | subnet_id = var.subnet_id 12 | 13 | # The wrapper script is used by each of the providers and each variable has to be filled out in order to run. Unfortunately, this means that if you change something in one provider, you have to change it in each of the others. It's not ideal, but FYI. 14 | user_data = templatefile("../userdata/installation-wrapper-script.sh", { 15 | account_name = "only for azure", 16 | cloud = "aws", 17 | connection_string = "only for azure", 18 | connectors_script_name = local.opencti_connectors_script_name, 19 | install_script_name = local.opencti_install_script_name, 20 | login_email = var.login_email, 21 | storage_bucket = var.storage_bucket 22 | }) 23 | 24 | vpc_security_group_ids = [aws_security_group.opencti_sg.id] 25 | 26 | tags = { 27 | Name = "opencti" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /azure/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "~>2.46.0" 6 | } 7 | } 8 | } 9 | 10 | provider "azurerm" { 11 | features {} 12 | } 13 | 14 | resource "azurerm_resource_group" "opencti_rg" { 15 | name = "opencti_rg" 16 | location = var.location 17 | } 18 | 19 | locals { 20 | # This is an 8x16 node with 80GB SSD. The A series is meant for proof-of-concept work. 21 | # OpenCTI minimum specs: https://github.com/OpenCTI-Platform/opencti/blob/5ede2579ee3c09c248d2111b483560f07d2f2c18/opencti-documentation/docs/getting-started/requirements.md 22 | instance_type = "Standard_A8_v2" 23 | } 24 | -------------------------------------------------------------------------------- /azure/network.tf: -------------------------------------------------------------------------------- 1 | # Create virtual network that everything is going to live in. 2 | resource "azurerm_virtual_network" "opencti_virtual_network" { 3 | name = "opencti_virtual_network" 4 | address_space = ["10.0.0.0/16"] 5 | location = azurerm_resource_group.opencti_rg.location 6 | resource_group_name = azurerm_resource_group.opencti_rg.name 7 | 8 | tags = { 9 | environment = "OpenCTI virtual network" 10 | } 11 | } 12 | 13 | # Create subnet inside OpenCTI virtual network. 14 | resource "azurerm_subnet" "opencti_subnet" { 15 | name = "opencti_subnet" 16 | resource_group_name = azurerm_resource_group.opencti_rg.name 17 | virtual_network_name = azurerm_virtual_network.opencti_virtual_network.name 18 | address_prefixes = ["10.0.2.0/24"] 19 | } 20 | 21 | # Create public IP so we can access OpenCTI resources externally. The network security group (set up in `security_group.tf`) controls access to the network. 22 | resource "azurerm_public_ip" "opencti_public_ip" { 23 | name = "opencti_public_ip" 24 | location = azurerm_resource_group.opencti_rg.location 25 | resource_group_name = azurerm_resource_group.opencti_rg.name 26 | allocation_method = "Dynamic" 27 | 28 | tags = { 29 | environment = "OpenCTI public IP" 30 | } 31 | } 32 | 33 | # The virtual NIC connects the VM to the virtual network, public IP address, and network security group. 34 | resource "azurerm_network_interface" "opencti_nic" { 35 | name = "opencti_nic" 36 | location = azurerm_resource_group.opencti_rg.location 37 | resource_group_name = azurerm_resource_group.opencti_rg.name 38 | 39 | ip_configuration { 40 | name = "opencti_nic_config" 41 | subnet_id = azurerm_subnet.opencti_subnet.id 42 | private_ip_address_allocation = "Dynamic" 43 | public_ip_address_id = azurerm_public_ip.opencti_public_ip.id 44 | } 45 | 46 | tags = { 47 | environment = "OpenCTI NIC" 48 | } 49 | } 50 | 51 | # Connect the security group to the network interface 52 | resource "azurerm_network_interface_security_group_association" "connect_security_group" { 53 | network_interface_id = azurerm_network_interface.opencti_nic.id 54 | network_security_group_id = azurerm_network_security_group.opencti_network_security_group.id 55 | } 56 | -------------------------------------------------------------------------------- /azure/security_group.tf: -------------------------------------------------------------------------------- 1 | # Control the flow of network traffic in and out of the VM. 2 | resource "azurerm_network_security_group" "opencti_network_security_group" { 3 | name = "opencti_network_security_group" 4 | location = var.location 5 | resource_group_name = azurerm_resource_group.opencti_rg.name 6 | 7 | # Allow SSH on port 22 8 | security_rule { 9 | name = "SSH" 10 | priority = 200 11 | direction = "Inbound" 12 | access = "Allow" 13 | protocol = "Tcp" 14 | source_port_range = "*" 15 | destination_port_range = "22" 16 | source_address_prefix = "*" 17 | destination_address_prefix = "*" 18 | } 19 | 20 | # Allow access to application on port 4000 21 | security_rule { 22 | name = "opencti_app_inbound" 23 | priority = 100 24 | direction = "Inbound" 25 | access = "Allow" 26 | protocol = "*" 27 | source_port_range = "*" 28 | destination_port_range = "4000" 29 | source_address_prefix = "*" 30 | destination_address_prefix = "*" 31 | } 32 | 33 | tags = { 34 | environment = "OpenCTI environment" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /azure/storage.tf: -------------------------------------------------------------------------------- 1 | # Create storage account. The default storage type is StorageV2: https://docs.microsoft.com/en-us/azure/storage/common/storage-account-overview 2 | resource "azurerm_storage_account" "opencti_storage" { 3 | name = var.storage_bucket 4 | resource_group_name = azurerm_resource_group.opencti_rg.name 5 | location = var.location 6 | account_replication_type = "LRS" 7 | account_tier = "Standard" 8 | 9 | tags = { 10 | environment = "OpenCTI environment" 11 | } 12 | } 13 | 14 | # Create a storage container within the storage account. This is where we will store files. 15 | resource "azurerm_storage_container" "opencti-storage-container" { 16 | name = "opencti-storage-container" 17 | storage_account_name = azurerm_storage_account.opencti_storage.name 18 | container_access_type = "private" 19 | } 20 | 21 | # Store the install and connectors scripts in the storage container. 22 | resource "azurerm_storage_blob" "install_script" { 23 | name = "opencti-installer.sh" 24 | storage_account_name = azurerm_storage_account.opencti_storage.name 25 | storage_container_name = azurerm_storage_container.opencti-storage-container.name 26 | type = "Block" 27 | content_type = "text/x-shellscript" 28 | source = "../opencti_scripts/installer.sh" 29 | } 30 | 31 | resource "azurerm_storage_blob" "connectors_script" { 32 | name = "opencti-connectors.sh" 33 | storage_account_name = azurerm_storage_account.opencti_storage.name 34 | storage_container_name = azurerm_storage_container.opencti-storage-container.name 35 | type = "Block" 36 | content_type = "text/x-shellscript" 37 | source = "../opencti_scripts/connectors.sh" 38 | } 39 | -------------------------------------------------------------------------------- /azure/terraform.tfvars: -------------------------------------------------------------------------------- 1 | account_name = "" 2 | # admin_user = "azureuser" 3 | # location = "eastus" 4 | login_email = "login.email@example.com" 5 | # os_disk_size = 32 6 | # storage_bucket = "opencti" 7 | -------------------------------------------------------------------------------- /azure/variables.tf: -------------------------------------------------------------------------------- 1 | variable "account_name" { 2 | description = "The Azure account name" 3 | type = string 4 | } 5 | 6 | variable "admin_user" { 7 | description = "The username for the administrator account." 8 | type = string 9 | default = "azureuser" 10 | } 11 | 12 | variable "location" { 13 | description = "The Azure region to deploy into." 14 | type = string 15 | default = "eastus" 16 | } 17 | 18 | variable "login_email" { 19 | description = "The email address used to login to OpenCTI." 20 | type = string 21 | default = "login.email@example.com" 22 | } 23 | 24 | variable "os_disk_size" { 25 | description = "The size of the OS disk (in GB). Minimum recommended is 32." 26 | type = number 27 | default = 32 28 | } 29 | 30 | variable "storage_bucket" { 31 | description = "Name of the storage bucket." 32 | type = string 33 | default = "opencti" 34 | } 35 | -------------------------------------------------------------------------------- /azure/vm.tf: -------------------------------------------------------------------------------- 1 | # Create private key and output it to the console. 2 | resource "tls_private_key" "ssh_private_key" { 3 | algorithm = "RSA" 4 | rsa_bits = 4096 5 | } 6 | 7 | output "tls_private_key" { value = tls_private_key.ssh_private_key.private_key_pem } 8 | 9 | # Bootstrap deployment with wrapper script. 10 | data "template_file" "wrapper_script" { 11 | template = file("../userdata/installation-wrapper-script.sh") 12 | # The wrapper script is used by each of the providers and each variable has to be filled out in order to run. Unfortunately, this means that if you change something in one provider, you have to change it in each of the others. It's not ideal, but FYI. 13 | vars = { 14 | "account_name" = var.account_name 15 | "cloud" = "azure" 16 | "connection_string" = azurerm_storage_account.opencti_storage.primary_connection_string 17 | "connectors_script_name" = "opencti-connectors.sh" 18 | "install_script_name" = "opencti-installer.sh" 19 | "login_email" = var.login_email 20 | "storage_bucket" = azurerm_storage_container.opencti-storage-container.name 21 | } 22 | } 23 | 24 | # Create Ubuntu virtual machine and attach it to the virtual NIC. 25 | resource "azurerm_linux_virtual_machine" "opencti_vm" { 26 | name = "opencti" 27 | location = azurerm_resource_group.opencti_rg.location 28 | resource_group_name = azurerm_resource_group.opencti_rg.name 29 | network_interface_ids = [azurerm_network_interface.opencti_nic.id] 30 | size = local.instance_type 31 | admin_username = var.admin_user 32 | custom_data = base64encode(data.template_file.wrapper_script.rendered) 33 | 34 | os_disk { 35 | name = "os_disk" 36 | caching = "ReadWrite" 37 | disk_size_gb = var.os_disk_size 38 | storage_account_type = "Standard_LRS" 39 | } 40 | 41 | # Ubuntu 20.04 LTS 42 | source_image_reference { 43 | publisher = "canonical" 44 | offer = "0001-com-ubuntu-server-focal" 45 | sku = "20_04-lts" 46 | version = "latest" 47 | } 48 | 49 | admin_ssh_key { 50 | username = var.admin_user 51 | public_key = tls_private_key.ssh_private_key.public_key_openssh 52 | } 53 | 54 | boot_diagnostics { 55 | storage_account_uri = azurerm_storage_account.opencti_storage.primary_blob_endpoint 56 | } 57 | 58 | tags = { 59 | environment = "OpenCTI virtual machine" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /gcp/firewall.tf: -------------------------------------------------------------------------------- 1 | resource "google_compute_firewall" "opencti_app_firewall_rule" { 2 | name = "opencti-app-firewall-rule" 3 | network = "default" 4 | 5 | allow { 6 | protocol = "tcp" 7 | ports = ["4000"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /gcp/iam.tf: -------------------------------------------------------------------------------- 1 | # Create the service account that accesses the storage. 2 | resource "google_service_account" "storage" { 3 | account_id = "storage-service-account" 4 | display_name = "OpenCTI storage service account" 5 | } 6 | 7 | resource "google_service_account_key" "storage_key" { 8 | service_account_id = google_service_account.storage.name 9 | } 10 | 11 | # Set IAM policy on storage bucket so objects can be downloaded and associate service account with it. 12 | data "google_iam_policy" "storage_iam" { 13 | binding { 14 | role = "roles/storage.objectViewer" 15 | members = [ "serviceAccount:${google_service_account.storage.email}" ] 16 | } 17 | } 18 | 19 | resource "google_storage_bucket_iam_policy" "bucket_iam" { 20 | bucket = google_storage_bucket.opencti_storage.name 21 | policy_data = data.google_iam_policy.storage_iam.policy_data 22 | } 23 | -------------------------------------------------------------------------------- /gcp/main.tf: -------------------------------------------------------------------------------- 1 | # Configure Google Cloud provider 2 | provider "google" { 3 | credentials = file(var.credentials) 4 | project = var.project_id 5 | region = var.region 6 | } 7 | 8 | # Enable Cloud Resource Manager (so we can enable Compute Engine below) 9 | resource "google_project_service" "resource_manager" { 10 | service = "cloudresourcemanager.googleapis.com" 11 | disable_on_destroy = false 12 | } 13 | 14 | # Enable Compute Engine API 15 | resource "google_project_service" "compute_api" { 16 | service = "compute.googleapis.com" 17 | disable_on_destroy = false 18 | } 19 | 20 | # Enable IAM API 21 | resource "google_project_service" "iam_api" { 22 | service = "iam.googleapis.com" 23 | disable_on_destroy = false 24 | } 25 | 26 | locals { 27 | connectors_script_name = "opencti-connectors.sh" 28 | install_script_name = "opencti-installer.sh" 29 | } 30 | -------------------------------------------------------------------------------- /gcp/storage.tf: -------------------------------------------------------------------------------- 1 | # Create storage bucket and add install and connectors script to it. 2 | resource "google_storage_bucket" "opencti_storage" { 3 | name = var.storage_bucket 4 | location = var.region 5 | force_destroy = true 6 | } 7 | 8 | resource "google_storage_bucket_object" "opencti_install_script" { 9 | name = local.install_script_name 10 | source = "../opencti_scripts/installer.sh" 11 | bucket = google_storage_bucket.opencti_storage.name 12 | } 13 | 14 | resource "google_storage_bucket_object" "opencti_connectors_script" { 15 | name = local.connectors_script_name 16 | source = "../opencti_scripts/connectors.sh" 17 | bucket = google_storage_bucket.opencti_storage.name 18 | } 19 | -------------------------------------------------------------------------------- /gcp/terraform.tfvars: -------------------------------------------------------------------------------- 1 | credentials = "" 2 | # disk_size = 32 3 | login_email = "login.email@example.com" 4 | # machine_type = "e2-standard-8" 5 | project_id = "" 6 | # region = "us-east1" 7 | # storage_bucket = "opencti-storage" 8 | # zone = "us-east1-b" 9 | -------------------------------------------------------------------------------- /gcp/variables.tf: -------------------------------------------------------------------------------- 1 | variable "credentials" { 2 | description = "Path to the service account key file." 3 | type = string 4 | } 5 | 6 | variable "disk_size" { 7 | description = "The disk size (in GB) for the instance." 8 | type = number 9 | default = 32 10 | } 11 | 12 | variable "login_email" { 13 | description = "The email used to login to the OpenCTI web page." 14 | type = string 15 | default = "login.email@example.com" 16 | } 17 | 18 | variable "machine_type" { 19 | description = "The machine type to use." 20 | type = string 21 | default = "e2-standard-8" 22 | } 23 | 24 | variable "project_id" { 25 | description = "The project ID string for the GCP project." 26 | type = string 27 | } 28 | 29 | variable "region" { 30 | description = "The Google Cloud region to run the instance in." 31 | type = string 32 | default = "us-east1" 33 | } 34 | 35 | variable "storage_bucket" { 36 | description = "Name of the storage bucket." 37 | type = string 38 | default = "opencti-storage" 39 | } 40 | 41 | variable "zone" { 42 | description = "The Google Cloud zone to run the instance in." 43 | type = string 44 | default = "us-east1-b" 45 | } 46 | -------------------------------------------------------------------------------- /gcp/vm.tf: -------------------------------------------------------------------------------- 1 | # Startup script template 2 | data "template_file" "startup_script" { 3 | template = file("../userdata/installation-wrapper-script.sh") 4 | # The wrapper script is used by each of the providers and each variable has to be filled out in order to run. Unfortunately, this means that if you change something in one provider, you have to change it in each of the others. It's not ideal, but FYI. 5 | vars = { 6 | "account_name" = "only for azure" 7 | "cloud" = "gcp" 8 | "connection_string" = "only for azure" 9 | "connectors_script_name" = "opencti-connectors.sh" 10 | "install_script_name" = "opencti-installer.sh" 11 | "login_email" = var.login_email 12 | "storage_bucket" = var.storage_bucket 13 | } 14 | } 15 | 16 | resource "google_compute_instance" "opencti_instance" { 17 | name = "opencti" 18 | machine_type = var.machine_type 19 | zone = var.zone 20 | 21 | # Ubuntu 20.04 LTS 22 | boot_disk { 23 | initialize_params { 24 | image = "ubuntu-2004-lts" 25 | size = var.disk_size 26 | } 27 | } 28 | 29 | # Startup script 30 | metadata_startup_script = data.template_file.startup_script.rendered 31 | 32 | network_interface { 33 | network = "default" 34 | 35 | access_config { 36 | # Using defaults, but included to provide IP address. 37 | } 38 | } 39 | 40 | service_account { 41 | email = google_service_account.storage.email 42 | # Scopes are outlined here: https://cloud.google.com/sdk/gcloud/reference/alpha/compute/instances/set-scopes#--scopes 43 | scopes = ["cloud-platform", "storage-ro"] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /opencti_scripts/connectors.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | # Script: ubuntu_opencti_connectors.sh 4 | # Purpose: To automate the installation of OpenCTI connectors based on the manual deployment here: https://www.notion.so/Connectors-4586c588462d4a1fb5e661f2d9837db8 5 | # Disclaimer: This script is written for testing and runs as root. Check the code and use at your own risk! The author is not liable for any damages or unexpected explosions! 6 | # License: Apache 2.0 7 | 8 | # ################ 9 | # Define functions 10 | # ################ 11 | 12 | # Function: print_banner 13 | # Print a massive OpenCTI banner. 14 | # Parameters: None 15 | # Returns: Nothing 16 | function print_banner { 17 | echo -e "\n\n\x1B[1;49;34mMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 18 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 19 | echo -e "MMMMMMMMMMMMMWK00000000000000000000000000000000000000000XWMWXKKKKKKNWMMMMMMMMMMMMM" 20 | echo -e "MMMMMMMMMMMMMXdcccccccccccccccccccccccccccccccccccccccclxNMW0dooooo0WMMMMMMMMMMMMM" 21 | echo -e "MMMMMMMMMMMMMXdccdkOOOOOOOOOOOOOOOOOOOOOOOOkkOOOOOOOOOOOKWWWNKOkdooOWMMMMMMMMMMMMM" 22 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMMMMWN0k0WMMMMMMMMMMMMXXWMWKxooOWMMMMMMMMMMMMM" 23 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMMMNOdodxkO0KXNWMMMMMWKOXMMXxooOWMMMMMMMMMMMMM" 24 | echo -e "MMMMMMMMMMMMMXdcckNMMMMMMMMMMMMMMMMMMWKdldOKK00OkkkkO0KXXN0x0WWXxooOWMMMMMMMMMMMMM" 25 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMXdldkkkkOO0KKKK0OOkkkxoONWXxooOWMMMMMMMMMMMMM" 26 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMWOlokKKKK0OOkkkkO00KKKkokNWKdooOWMMMMMMMMMMMMM" 27 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMNxcoxkkkkO00KKKK0OkkkkdoOWNOoooOWMMMMMMMMMMMMM" 28 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMNkcoOKKKK0OkkkkkO0KK0xoxXN0doooOWMMMMMMMMMMMMM" 29 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMWOllokkkOO0KKKK00OxddoxKNKxooooOWMMMMMMMMMMMMM" 30 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMKdcd0KKK0OkkkkOOkdodOXXOddddooOWMMMMMMMMMMMMM" 31 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMWOlloxkOO0K0kdooox0XNKxdkK0xooOWMMMMMMMMMMMMM" 32 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMWWWMXxllxkxooooodkOKNN0kkOXWWXxooOWMMMMMMMMMMMMM" 33 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMWXXXNNWWWXklclooodkOKNWNXK00XNWMMMXxooOWMMMMMMMMMMMMM" 34 | echo -e "MMMMMMMMMMMMMXdcckNMMMMMMWX0OO0XNWNKOxdoolclkKNWWWWNXXNWMMMMMMMXxooOWMMMMMMMMMMMMM" 35 | echo -e "MMMMMMMMMMMMMXdclkNMMMWN0xdx0NWX0kdoolldkxlldXMMMWWWMMMMMMMMMMMXxooOWMMMMMMMMMMMMM" 36 | echo -e "MMMMMMMMMMMMMXdcckNMMN0dld0WWXOdollx0KOkxdlclkNMMMMMMMMMMMMMMMMKxooOWMMMMMMMMMMMMM" 37 | echo -e "MMMMMMMMMMMMMNxccxNMXkllkNMNOdldO0kxxk0KXKkdllOWMMMMMMMMMMMMMMWKdod0WMMMMMMMMMMMMM" 38 | echo -e "MMMMMMMMMMMMMWOlco0NkllkNMNkodxdxO0KK0kxxk0OocdKMMMMMMMMMMMMMMNOooxXMMMMMMMMMMMMMM" 39 | echo -e "MMMMMMMMMMMMMMXxclxOocxXMW0olxKX0OxxxOKXKOxdlclOWMMMMMMMMMMMMWKdooOWMMMMMMMMMMMMMM" 40 | echo -e "MMMMMMMMMMMMMMW0ocllll0WMNkoxxxxk0KK0kxxk0KKOdcdXMMMMMMMMMMMWXkookXMMMMMMMMMMMMMMM" 41 | echo -e "MMMMMMMMMMMMMMMWOlcccoKMMXxokKXKOxxxOKXKOkxxkdcoKMMMMMMMMMMWNkooxKWMMMMMMMMMMMMMMM" 42 | echo -e "MMMMMMMMMMMMMMMMNOlccoKMMNkdxxxk0KK0kxxk0KK0kocdXMMMMMMMMMWXkooxKWMMMMMMMMMMMMMMMM" 43 | echo -e "MMMMMMMMMMMMMMMMMNOlclkNMWNXWNKOkxxOKKK0kxxkOdlkNMMMMMMMMWXxooxKWMMMMMMMMMMMMMMMMM" 44 | echo -e "MMMMMMMMMMMMMMMMMMW0oclxKWMMMMMMWNKOxxkOKXKxllxXMMMMMMMMN0dookXWMMMMMMMMMMMMMMMMMM" 45 | echo -e "MMMMMMMMMMMMMMMMMMMWKxlcokXWMMMMMMMMWX0kxxkdokXMMMMMMMWKkoodONMMMMMMMMMMMMMMMMMMMM" 46 | echo -e "MMMMMMMMMMMMMMMMMMMMMN0dlldOXWMMMMMMMMMW0ookKWMMMMMMWXOdookKWMMMMMMMMMMMMMMMMMMMMM" 47 | echo -e "MMMMMMMMMMMMMMMMMMMMMMWXOolloOXWMMMMMWX0k0XWMMMMMWNKkdooxKWMMMMMMMMMMMMMMMMMMMMMMM" 48 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMWXOdlcox0NMMWNXNWMMMMMWNXOxoodkKNWMMMMMMMMMMMMMMMMMMMMMMMM" 49 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMWN0xod0NMMWNWWWMWNX0OxdooxOKWMMMMMMMMMMMMMMMMMMMMMMMMMMM" 50 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMWXNWWX0kxxkOOkxdoodxOKNWMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 51 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0kxdoooddkO0XNWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 52 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWWNXKKXNWWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 53 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM OPENCTI CONNECTORS MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 54 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\x1B[0m\n\n" 55 | } 56 | 57 | # Function: check_root 58 | # Check if we are logged in as root (UID 0) and exit if not. 59 | # Parameters: None 60 | # Returns: Nothing 61 | function check_root { 62 | echo "Checking if root..." 63 | if [ "$EUID" -ne 0 ] 64 | then 65 | echo "Elevated privileges required. Please run with sudo or as root." 66 | exit 67 | fi 68 | } 69 | 70 | # Function: warn_user 71 | # Warn the user that the script is running with root privileges and to be cautious. 72 | # Parameters: None 73 | # Returns: Nothing 74 | function warn_user { 75 | echo -e "\x1B[0;49;91mThis script runs with elevated access; check the code and use at your own risk!\x1B[0m" 76 | echo 77 | } 78 | 79 | # Function: quit_on_error 80 | # On a failure, this function prints the reason for the failure and exits the script. 81 | # Parameters: String containing exit reason 82 | # Returns: Nothing 83 | function quit_on_error { 84 | if [[ $? -gt 0 ]] 85 | then 86 | echo -e "\n\n $@ ...FAIL"; 87 | exit 10 88 | else 89 | echo "$@ ...OK" 90 | fi 91 | } 92 | 93 | # ################ 94 | # Define constants 95 | # ################ 96 | 97 | # Show a prompt? When called from the wrapper script, we want this script to run through without user intervention. However, the user is expected to edit this script and we will want to run prompts for them in that case. 98 | while getopts p: flag 99 | do 100 | case "${flag}" in 101 | a) show_user_prompt=${OPTARG} 102 | ;; 103 | 104 | *) show_user_prompt=0 105 | ;; 106 | esac 107 | done 108 | 109 | # Connectors 110 | opencti_dir="/opt/opencti" 111 | opencti_connector_dir="${opencti_dir}/connectors" 112 | opencti_url="http://localhost:4000" 113 | opencti_token=$(grep token ${opencti_dir}/config/production.json | cut -d\" -f4) 114 | 115 | # Check Ubuntu version for Python: 116 | # - 18 uses 3.6 (will install 3.7) 117 | # - 20 uses 3.8 118 | ubuntu_version=$(grep "Ubuntu " /etc/lsb-release | cut -d" " -f2 | cut -d\. -f1) 119 | if [[ ${ubuntu_version} == 18 ]] 120 | then 121 | python_ver="3.7" 122 | elif [[ ${ubuntu_version} == 20 ]] 123 | then 124 | # Using bionic since focal not avaialble yet for RabbitMQ 125 | python_ver="3" 126 | else 127 | quit_on_error echo "You are using an unsupported version of Ubuntu. Exiting." 128 | fi 129 | 130 | # ########### 131 | # Main script 132 | # ########### 133 | print_banner 134 | check_root 135 | warn_user 136 | 137 | # Connectors hash: 138 | # - 0: disabled 139 | # - 1: enabled 140 | # This will only set up your instance for the connectors enabled. You must supply an API token (e.g., alienvault token) and enable the service. 141 | # It should be safe to run this after changing configs or enabling services. 142 | declare -A CONNECTORS; 143 | CONNECTORS['alienvault']=0 144 | CONNECTORS['amitt']=0 145 | CONNECTORS['crowdstrike']=0 146 | CONNECTORS['cryptolaemus']=0 147 | CONNECTORS['cve']=1 148 | CONNECTORS['cyber-threat-coalition']=0 149 | CONNECTORS['cybercrime-tracker']=0 150 | CONNECTORS['export-file-csv']=1 151 | CONNECTORS['export-file-stix']=1 152 | CONNECTORS['hygiene']=0 153 | CONNECTORS['import-file-pdf-observables']=1 154 | CONNECTORS['import-file-stix']=1 155 | CONNECTORS['ipinfo']=0 156 | CONNECTORS['lastinfosec']=0 157 | CONNECTORS['malpedia']=0 158 | CONNECTORS['misp']=1 159 | CONNECTORS['mitre']=1 160 | CONNECTORS['opencti']=1 161 | CONNECTORS['valhalla']=0 162 | CONNECTORS['virustotal']=1 163 | 164 | echo "The following connectors will be installed:" 165 | for i in "${!CONNECTORS[@]}" 166 | do 167 | if [[ ${CONNECTORS[$i]} == 1 ]] 168 | then 169 | echo $i 170 | fi 171 | done 172 | 173 | if [[ ! $show_user_prompt ]] 174 | then 175 | echo 176 | read -p "Are you sure you want to continue with the list above? [y/N] " -n 1 -r 177 | echo 178 | if [[ ! $REPLY =~ ^[Yy]$ ]] 179 | then 180 | echo 'User backed out. Exiting.' 181 | exit 1 182 | fi 183 | fi 184 | 185 | for i in "${!CONNECTORS[@]}" 186 | do 187 | if [[ ${CONNECTORS[$i]} == 1 ]] 188 | then 189 | python${python_ver} -m pip -q install -r ${opencti_connector_dir}/$i/src/requirements.txt 190 | 191 | if [[ -f "${opencti_connector_dir}/$i/src/config.yml" ]] 192 | then 193 | sed -i"" -e "s=http://localhost:8080=${opencti_url}=g" "${opencti_connector_dir}/$i/src/config.yml" 194 | sed -i"" -e "s/token: 'ChangeMe'/token: '${opencti_token}'/g" "${opencti_connector_dir}/$i/src/config.yml" 195 | sed -i"" -e "s/id: 'ChangeMe'/id: '$(grep id: ${opencti_connector_dir}/$i/src/config.yml | cut -d\' -f2)'/g" "${opencti_connector_dir}/$i/src/config.yml" 196 | else 197 | cp "${opencti_connector_dir}/$i/src/config.yml.sample" "${opencti_connector_dir}/$i/src/config.yml" 198 | sed -i"" -e "s=http://localhost:8080=${opencti_url}=g" "${opencti_connector_dir}/$i/src/config.yml" 199 | sed -i"" -e "s/token: 'ChangeMe'/token: '${opencti_token}'/g" "${opencti_connector_dir}/$i/src/config.yml" 200 | sed -i"" -e "s/id: 'ChangeMe'/id: '$(uuidgen -r | tr -d '\n' | tr '[:upper:]' '[:lower:]')'/g" "${opencti_connector_dir}/$i/src/config.yml" 201 | fi 202 | 203 | if [[ ! -f "/etc/systemd/system/opencti-connector-$i.service" ]] 204 | then 205 | cat > /etc/systemd/system/opencti-connector-$i.service <<- EOT 206 | [Unit] 207 | Description=OpenCTI Connector - $i 208 | After=network.target 209 | [Service] 210 | Type=simple 211 | WorkingDirectory=${opencti_connector_dir}/$i/src 212 | ExecStart=/usr/bin/python${python_ver} "${opencti_connector_dir}/$i/src/$i.py" 213 | ExecReload=/bin/kill -s HUP \$MAINPID 214 | ExecStop=/bin/kill -s TERM \$MAINPID 215 | PrivateTmp=true 216 | Restart=always 217 | [Install] 218 | WantedBy=multi-user.target 219 | EOT 220 | 221 | systemctl daemon-reload 222 | systemctl start opencti-connector-$i.service 223 | fi 224 | 225 | if [[ $(systemctl status --no-pager opencti-connector-$i.service | grep 'Active: active') ]] 226 | then 227 | echo "opencti-connector-$i.service is already running, restarting due to config changes" 228 | systemctl restart opencti-connector-$i.service 229 | fi 230 | 231 | quit_on_error "Installing service for connector: $i" 232 | fi 233 | done 234 | 235 | echo "Performing daemon-reload" 236 | systemctl daemon-reload 237 | quit_on_error "Reloading systemctl daemon" 238 | -------------------------------------------------------------------------------- /opencti_scripts/installer.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | # Script: ubuntu_opencti_installer.sh 4 | # Purpose: To automate the installation of OpenCTI. Based on this manual deployment: https://www.notion.so/Manual-deployment-b911beba44234f179841582ab3894bb1 5 | # Disclaimer: This script is written for testing and runs as root. Check the code and use at your own risk! The author is not liable for any damages or unexpected explosions! 6 | # License: Apache 2.0 7 | 8 | # #################### 9 | # Function definitions 10 | # #################### 11 | 12 | # Function: print_banner 13 | # Print a massive OpenCTI banner. 14 | # Parameters: None 15 | function print_banner { 16 | echo -e "\n\n\x1B[1;49;34mMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 17 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 18 | echo -e "MMMMMMMMMMMMMWK00000000000000000000000000000000000000000XWMWXKKKKKKNWMMMMMMMMMMMMM" 19 | echo -e "MMMMMMMMMMMMMXdcccccccccccccccccccccccccccccccccccccccclxNMW0dooooo0WMMMMMMMMMMMMM" 20 | echo -e "MMMMMMMMMMMMMXdccdkOOOOOOOOOOOOOOOOOOOOOOOOkkOOOOOOOOOOOKWWWNKOkdooOWMMMMMMMMMMMMM" 21 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMMMMWN0k0WMMMMMMMMMMMMXXWMWKxooOWMMMMMMMMMMMMM" 22 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMMMNOdodxkO0KXNWMMMMMWKOXMMXxooOWMMMMMMMMMMMMM" 23 | echo -e "MMMMMMMMMMMMMXdcckNMMMMMMMMMMMMMMMMMMWKdldOKK00OkkkkO0KXXN0x0WWXxooOWMMMMMMMMMMMMM" 24 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMXdldkkkkOO0KKKK0OOkkkxoONWXxooOWMMMMMMMMMMMMM" 25 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMWOlokKKKK0OOkkkkO00KKKkokNWKdooOWMMMMMMMMMMMMM" 26 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMNxcoxkkkkO00KKKK0OkkkkdoOWNOoooOWMMMMMMMMMMMMM" 27 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMNkcoOKKKK0OkkkkkO0KK0xoxXN0doooOWMMMMMMMMMMMMM" 28 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMWOllokkkOO0KKKK00OxddoxKNKxooooOWMMMMMMMMMMMMM" 29 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMKdcd0KKK0OkkkkOOkdodOXXOddddooOWMMMMMMMMMMMMM" 30 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMMMMWOlloxkOO0K0kdooox0XNKxdkK0xooOWMMMMMMMMMMMMM" 31 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMMMMMMWWWMXxllxkxooooodkOKNN0kkOXWWXxooOWMMMMMMMMMMMMM" 32 | echo -e "MMMMMMMMMMMMMXdclkNMMMMMMMMMMWXXXNNWWWXklclooodkOKNWNXK00XNWMMMXxooOWMMMMMMMMMMMMM" 33 | echo -e "MMMMMMMMMMMMMXdcckNMMMMMMWX0OO0XNWNKOxdoolclkKNWWWWNXXNWMMMMMMMXxooOWMMMMMMMMMMMMM" 34 | echo -e "MMMMMMMMMMMMMXdclkNMMMWN0xdx0NWX0kdoolldkxlldXMMMWWWMMMMMMMMMMMXxooOWMMMMMMMMMMMMM" 35 | echo -e "MMMMMMMMMMMMMXdcckNMMN0dld0WWXOdollx0KOkxdlclkNMMMMMMMMMMMMMMMMKxooOWMMMMMMMMMMMMM" 36 | echo -e "MMMMMMMMMMMMMNxccxNMXkllkNMNOdldO0kxxk0KXKkdllOWMMMMMMMMMMMMMMWKdod0WMMMMMMMMMMMMM" 37 | echo -e "MMMMMMMMMMMMMWOlco0NkllkNMNkodxdxO0KK0kxxk0OocdKMMMMMMMMMMMMMMNOooxXMMMMMMMMMMMMMM" 38 | echo -e "MMMMMMMMMMMMMMXxclxOocxXMW0olxKX0OxxxOKXKOxdlclOWMMMMMMMMMMMMWKdooOWMMMMMMMMMMMMMM" 39 | echo -e "MMMMMMMMMMMMMMW0ocllll0WMNkoxxxxk0KK0kxxk0KKOdcdXMMMMMMMMMMMWXkookXMMMMMMMMMMMMMMM" 40 | echo -e "MMMMMMMMMMMMMMMWOlcccoKMMXxokKXKOxxxOKXKOkxxkdcoKMMMMMMMMMMWNkooxKWMMMMMMMMMMMMMMM" 41 | echo -e "MMMMMMMMMMMMMMMMNOlccoKMMNkdxxxk0KK0kxxk0KK0kocdXMMMMMMMMMWXkooxKWMMMMMMMMMMMMMMMM" 42 | echo -e "MMMMMMMMMMMMMMMMMNOlclkNMWNXWNKOkxxOKKK0kxxkOdlkNMMMMMMMMWXxooxKWMMMMMMMMMMMMMMMMM" 43 | echo -e "MMMMMMMMMMMMMMMMMMW0oclxKWMMMMMMWNKOxxkOKXKxllxXMMMMMMMMN0dookXWMMMMMMMMMMMMMMMMMM" 44 | echo -e "MMMMMMMMMMMMMMMMMMMWKxlcokXWMMMMMMMMWX0kxxkdokXMMMMMMMWKkoodONMMMMMMMMMMMMMMMMMMMM" 45 | echo -e "MMMMMMMMMMMMMMMMMMMMMN0dlldOXWMMMMMMMMMW0ookKWMMMMMMWXOdookKWMMMMMMMMMMMMMMMMMMMMM" 46 | echo -e "MMMMMMMMMMMMMMMMMMMMMMWXOolloOXWMMMMMWX0k0XWMMMMMWNKkdooxKWMMMMMMMMMMMMMMMMMMMMMMM" 47 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMWXOdlcox0NMMWNXNWMMMMMWNXOxoodkKNWMMMMMMMMMMMMMMMMMMMMMMMM" 48 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMWN0xod0NMMWNWWWMWNX0OxdooxOKWMMMMMMMMMMMMMMMMMMMMMMMMMMM" 49 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMWXNWWX0kxxkOOkxdoodxOKNWMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 50 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX0kxdoooddkO0XNWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 51 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWWNXKKXNWWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 52 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM OPENCTI INSTALLER MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" 53 | echo -e "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\x1B[0m\n\n" 54 | } 55 | 56 | # Function: log_section_heading 57 | # Prints an obnoxious line, the time (with second precision), and the section heading. This visually separates the log and makes it more readable. 58 | # Parameters: 59 | # - $1: section heading 60 | function log_section_heading { 61 | echo 62 | echo "###^^^###^^^###^^^###^^^###" 63 | date --iso-8601=seconds 64 | echo $1 65 | echo "###^^^###^^^###^^^###^^^###" 66 | echo 67 | } 68 | 69 | # Function: check_root 70 | # Check if we are logged in as root (UID 0) and exit if not. 71 | # Parameters: None 72 | function check_root { 73 | echo "Checking if root..." 74 | if [ "$EUID" -ne 0 ] 75 | then 76 | echo "Elevated privileges required. Please run with sudo or as root." 77 | exit 78 | fi 79 | } 80 | 81 | # Function: warn_user 82 | # Warn the user that the script is running with root privileges and to be cautious. 83 | # Parameters: None 84 | function warn_user { 85 | echo -e "\x1B[0;49;91mThis script runs with elevated access; check the code and use at your own risk!\x1B[0m" 86 | echo 87 | } 88 | 89 | # Function: quit_on_error 90 | # On a failure, this function prints the reason for the failure and exits the script. 91 | # Parameters: String containing exit reason 92 | function quit_on_error { 93 | if [[ $? -gt 0 ]] 94 | then 95 | echo -e "\n\n $@ ...FAIL"; 96 | exit 10 97 | else 98 | echo "$@ ...OK" 99 | fi 100 | } 101 | 102 | # Function: update_apt_pkg 103 | # Non-interactive package management which updates the supplied package. 104 | # Parameters: The package to update. 105 | function update_apt_pkg { 106 | DEBIAN_FRONTEND=noninteractive apt-get -qq update 107 | quit_on_error "Checking packages" 108 | } 109 | 110 | # Function: check_apt_pkg 111 | # Checks if a package is installed and updates it. If the package is not installed, it is installed. 112 | # Parameters: 113 | # - $1: package to install 114 | # - $2: version to install 115 | function check_apt_pkg { 116 | if [[ $(dpkg -l | grep $1) ]] 117 | then 118 | echo >&2 "$1 found, attempting upgrade: executing apt-get -y install --only-upgrade '$1''$2'"; 119 | DEBIAN_FRONTEND=noninteractive apt-get install --only-upgrade "$1""$2" 120 | quit_on_error "Upgrading $1$2" 121 | else 122 | echo >&2 "$1 missing, attempting install: executing apt-get -y install '$1''$2'"; 123 | DEBIAN_FRONTEND=noninteractive apt-get -y install "$1""$2" 124 | quit_on_error "Installing $1$2" 125 | fi 126 | } 127 | 128 | # Function: check_service 129 | # Checks if a service is active or nah. Matches Grakn service output. 130 | # Parameters: 131 | # - $1: service to check 132 | function check_service { 133 | if [[ $(systemctl show -p ActiveState --value "$1") == "active" ]] 134 | then 135 | echo "$1: RUNNING" 136 | else 137 | echo "$1: NOT RUNNING" 138 | fi 139 | } 140 | 141 | # Function: enable_service 142 | # Checks if a service is disabled and enables it. If the service is already running, restart it. Otherwise, start the service. 143 | # Parameters: 144 | # - $1: service name 145 | function enable_service { 146 | if [[ $(systemctl is-enabled "$1") == "disabled" ]] 147 | then 148 | echo "$1 service not enabled." 149 | systemctl enable --now "$1" 150 | quit_on_error "Enabling $1" 151 | elif [[ $(systemctl show -p SubState --value "$1") == "running" ]] 152 | then 153 | echo "$1 service already running." 154 | systemctl restart "$1" 155 | quit_on_error "Restarting $1" 156 | else 157 | echo "$1 service not running." 158 | systemctl start "$1" 159 | quit_on_error "Starting $1" 160 | fi 161 | } 162 | 163 | # Function: disable_service 164 | # For the provided service, if it is enabled, disable it; otherwise, if it's running, stop it; otherwise do nothing. 165 | # Parameters: 166 | # - $1: service name 167 | function disable_service { 168 | if [[ $(systemctl is-enabled "$1") == "enabled" ]] 169 | then 170 | echo "$1 service enabled. Disabling." 171 | systemctl disable --now "$1" 172 | quit_on_error "Disabling $1" 173 | elif [[ $(systemctl show -p SubState --value "$1") == "running" ]] 174 | then 175 | echo "$1 service still running. Stopping." 176 | systemctl stop "$1" 177 | quit_on_error "Stopping $1" 178 | else 179 | echo "$1 service not running." 180 | quit_on_error "Skipping $1" 181 | fi 182 | } 183 | 184 | # ################ 185 | # Define constants 186 | # ################ 187 | 188 | # Check Ubuntu version for Python: 189 | # - 18 uses 3.6 (will install 3.7) 190 | # - 20 uses 3.8 191 | ubuntu_version=$(grep "Ubuntu " /etc/lsb-release | cut -d" " -f2 | cut -d\. -f1) 192 | if [[ ${ubuntu_version} == 18 ]] 193 | then 194 | distro="bionic" 195 | run_python="python3.7" 196 | elif [[ ${ubuntu_version} == 20 ]] 197 | then 198 | # Using bionic since focal not avaialble yet for RabbitMQ 199 | distro="bionic" 200 | run_python="python3" 201 | else 202 | quit_on_error echo "You are using an unsupported version of Ubuntu. Exiting." 203 | fi 204 | 205 | # Grakn 206 | grakn_bin_version="2.0.0-alpha-6" 207 | grakn_console_version="2.0.0-alpha-4" 208 | grakn_core_all_version="2.0.0-alpha-4" 209 | grakn_core_server_version="2.0.0-alpha-4" 210 | 211 | # Minio 212 | minio_dir="/opt/minio/data" 213 | 214 | # Redis 215 | redis_ver="6.0.5" 216 | 217 | # RabbitMQ 218 | rabbitmq_ver="3.8.5-1" 219 | rabbitmq_release_url="https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc" 220 | 221 | # OpenCTI 222 | # This has to be in email address format, otherwise the opencti-server service will freak out and not start correctly. - KTW 223 | # TODO: this e-mail needs to be scrubbed and read in from Terraform 224 | while getopts e: flag 225 | do 226 | case "${flag}" in 227 | e) opencti_email=${OPTARG} 228 | ;; 229 | 230 | *) opencti_email="user@example.com" 231 | ;; 232 | esac 233 | done 234 | 235 | opencti_ver="4.2.1" 236 | opencti_dir="/opt/opencti" 237 | opencti_worker_count=2 238 | 239 | # ########### 240 | # Main script 241 | # ########### 242 | print_banner 243 | check_root 244 | warn_user 245 | 246 | # Enter script's root directory 247 | script_pwd="$(dirname "$0")" 248 | 249 | if [[ -d "${script_pwd}" ]] 250 | then 251 | cd "${script_pwd}" 252 | log_section_heading "Entering script's root directory: ${script_pwd}" 253 | fi 254 | 255 | # Clean the slate: disable any service we will be messing with in this script. If you're looking through the install log and see errors here, it's fine; a lot of these aren't installed. This is just to be safe. 256 | log_section_heading "Disable existing services" 257 | for i in $(seq 1 $opencti_worker_count) 258 | do 259 | disable_service "opencti-worker@$i" 260 | done 261 | disable_service 'opencti-server' 262 | disable_service 'elasticsearch' 263 | disable_service 'redis-server' 264 | disable_service 'rabbitmq-server' 265 | disable_service 'minio' 266 | disable_service 'grakn' 267 | 268 | # The VMs we're running are not that big and we're going to quickly fill the system log with our work (and especially the connectors). This will max out the logs at 100M. 269 | echo "SystemMaxUse=100M" >> /etc/systemd/journald.conf 270 | 271 | # Ensure required packages are installed at latest (or specified) version. Repositories were updated in the wrapper script. 272 | log_section_heading "Installing and updating required packages" 273 | update_apt_pkg 274 | check_apt_pkg 'apt-transport-https' 275 | check_apt_pkg 'curl' 276 | check_apt_pkg 'git' 277 | check_apt_pkg 'jq' 278 | check_apt_pkg 'openssl' 279 | check_apt_pkg 'software-properties-common' 280 | check_apt_pkg 'tar' 281 | check_apt_pkg 'wget' 282 | 283 | ## NodeJS 284 | log_section_heading "NodeJS" 285 | check_apt_pkg 'nodejs' 286 | check_apt_pkg 'npm' 287 | npm install -g n 288 | n lts 289 | sed -i 's|PATH.*|'"${PATH}:/usr/local/bin/node"'|g' /etc/environment 290 | export PATH="$PATH:/usr/local/bin/node" 291 | npm rebuild 292 | 293 | ## Yarn 294 | log_section_heading "Yarn" 295 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - 296 | echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list 297 | update_apt_pkg 298 | check_apt_pkg 'yarn' 299 | 300 | ## Python 301 | log_section_heading "Python" 302 | check_apt_pkg "${run_python}" 303 | check_apt_pkg "python3-pip" 304 | ${run_python} -m pip install --upgrade pip 305 | ${run_python} -m pip -q install --ignore-installed PyYAML 306 | 307 | ## Grakn 308 | log_section_heading "Grakn" 309 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 8F3DA4B5E9AEF44C 310 | sudo add-apt-repository 'deb [ arch=all ] https://repo.grakn.ai/repository/apt/ trusty main' 311 | update_apt_pkg 312 | # apt-get install -y grakn-console=2.0.0-alpha-3 # Required dependency 313 | # apt-get install -y grakn-core-all 314 | check_apt_pkg 'grakn-bin' "=${grakn_bin_version}" 315 | check_apt_pkg 'grakn-core-server' "=${grakn_core_server_version}" 316 | check_apt_pkg 'grakn-console' "=${grakn_console_version}" 317 | check_apt_pkg 'grakn-core-all' "=${grakn_core_all_version}" 318 | 319 | ### Create systemd unit file for Grakn 320 | cat < /etc/systemd/system/grakn.service 321 | [Unit] 322 | Description=Grakn.AI Server daemon 323 | After=network.target 324 | [Service] 325 | Type=forking 326 | ExecStart=/usr/local/bin/grakn server start 327 | ExecStop=/usr/local/bin/grakn server stop 328 | ExecReload=/usr/local/bin/grakn server stop && /usr/local/bin/grakn server start 329 | RemainAfterExit=yes 330 | [Install] 331 | WantedBy=multi-user.target 332 | EOT 333 | systemctl daemon-reload 334 | enable_service 'grakn' 335 | 336 | ## Elasticsearch 337 | log_section_heading "Elasticsearch" 338 | echo "Setting up logrotate for Elasticsearch" 339 | # rotate 20 logs at 50M means a maximum of 1GB Elasticsearch logs. 340 | cat < /etc/logrotate.d/elasticsearch 341 | /var/log/elasticsearch/*.log { 342 | daily 343 | rotate 20 344 | size 50M 345 | copytruncate 346 | compress 347 | delaycompress 348 | missingok 349 | notifempty 350 | create 644 elasticsearch elasticsearch 351 | } 352 | EOT 353 | wget -qO - 'https://artifacts.elastic.co/GPG-KEY-elasticsearch' | apt-key add - 354 | add-apt-repository "deb https://artifacts.elastic.co/packages/7.x/apt stable main" 355 | update_apt_pkg 356 | check_apt_pkg 'elasticsearch' 357 | enable_service 'elasticsearch' 358 | 359 | ## Minio 360 | log_section_heading "Minio" 361 | wget --quiet -O minio https://dl.min.io/server/minio/release/linux-amd64/minio 362 | chmod +x minio 363 | mv minio "/usr/local/bin/" 364 | if [[ ! -d "${minio_dir}" ]] 365 | then 366 | mkdir -p "${minio_dir}" 367 | fi 368 | 369 | ### From: https://github.com/minio/minio-service/blob/master/linux-systemd/minio.service 370 | if [[ ! -f "/etc/default/minio" ]] 371 | then 372 | # .minio.access_key 373 | RMINIOAK="$(openssl rand -hex 12)" 374 | # .minio.secret_key 375 | RMINIOSK="$(openssl rand -base64 25 | tr -d '/')" 376 | cat > /etc/default/minio <<- EOT 377 | # Volume to be used for MinIO server. 378 | MINIO_VOLUMES="/opt/minio/data/" 379 | # Use if you want to run MinIO on a custom port. 380 | # MINIO_OPTS="--address :9199" 381 | # Access Key of the server. 382 | MINIO_ACCESS_KEY=${RMINIOAK} 383 | # Secret key of the server. 384 | MINIO_SECRET_KEY=${RMINIOSK} 385 | EOT 386 | else 387 | RMINIOAK="$(grep -o 'MINIO_ACCESS_KEY=.*' /etc/default/minio | cut -f2- -d=)" 388 | RMINIOSK="$(grep -o 'MINIO_SECRET_KEY=.*' /etc/default/minio | cut -f2- -d=)" 389 | fi 390 | 391 | curl "https://raw.githubusercontent.com/minio/minio-service/master/linux-systemd/minio.service" -o "/etc/systemd/system/minio.service" 392 | sed -i'' -e 's/User=minio-user/User=root/g' "/etc/systemd/system/minio.service" 393 | sed -i'' -e 's/Group=minio-user/Group=root/g' "/etc/systemd/system/minio.service" 394 | systemctl daemon-reload 395 | enable_service 'minio' 396 | 397 | ## Redis 398 | log_section_heading "Redis" 399 | update_apt_pkg 400 | check_apt_pkg 'gcc' 401 | check_apt_pkg 'build-essential' 402 | check_apt_pkg 'libsystemd-dev' # required for systemd notify to work 403 | check_apt_pkg 'pkg-config' 404 | 405 | if [[ ! -f "/usr/local/bin/redis-server" ]] 406 | then 407 | wget --quiet "http://download.redis.io/releases/redis-${redis_ver}.tar.gz" 408 | tar xzf redis-${redis_ver}.tar.gz 409 | cd redis-${redis_ver} 410 | make -s 411 | quit_on_error "Building Redis ${redis_ver}..." 412 | make -s install 413 | quit_on_error "Installing Redis ${redis_ver}..." 414 | cd .. 415 | fi 416 | 417 | if [[ ! $(id redis) ]] 418 | then 419 | adduser --system --group --no-create-home redis 420 | usermod -L redis 421 | fi 422 | 423 | if [[ ! -d "/var/lib/redis/" ]] 424 | then 425 | mkdir -p "/var/lib/redis" 426 | chown redis:redis "/var/lib/redis" 427 | chmod ug+rwX "/var/lib/redis" 428 | fi 429 | 430 | if [[ ! -d "/etc/redis/" ]] 431 | then 432 | mkdir -p "/etc/redis/" 433 | chown -R redis:redis "/etc/redis/" 434 | fi 435 | 436 | if [[ ! -f "/etc/redis/redis.conf" ]] 437 | then 438 | cp "redis-${redis_ver}/redis.conf" "/etc/redis/redis.conf" 439 | sed -i 's/^supervised no/supervised systemd/' "/etc/redis/redis.conf" 440 | chown redis:redis "/etc/redis/redis.conf" 441 | fi 442 | 443 | if [[ ! -f "/etc/default/redis-server" ]] 444 | then 445 | touch "/etc/default/redis-server" 446 | echo 'ULIMIT=65536' >> "/etc/default/redis-server" 447 | fi 448 | 449 | if [[ ! -f "/etc/systemd/system/redis-server.service" ]] 450 | then 451 | cat > /etc/systemd/system/redis-server.service <<- EOT 452 | [Unit] 453 | Description=Redis persistent key-value storage 454 | After=network.target 455 | [Service] 456 | Type=notify 457 | ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf 458 | ExecStop=/usr/local/bin/redis-cli -p 6379 shutdown 459 | ExecReload=/bin/kill -USR2 \$MAINPID 460 | TimeoutStartSec=10 461 | TimeoutStopSec=10 462 | Restart=on-failure 463 | [Install] 464 | WantedBy=multi-user.target 465 | EOT 466 | systemctl daemon-reload 467 | fi 468 | 469 | enable_service 'redis-server' 470 | 471 | ## RabbitMQ 472 | log_section_heading "RabbitMQ" 473 | curl -fsSL "${rabbitmq_release_url}" | apt-key add - 474 | tee /etc/apt/sources.list.d/bintray.rabbitmq.list < ${opencti_dir}/config/production.json 550 | 551 | echo "OpenCTI: Create unit file" 552 | cat > /etc/systemd/system/opencti-server.service <<- EOT 553 | [Unit] 554 | Description=OpenCTI Server daemon 555 | After=network.target 556 | [Service] 557 | Type=simple 558 | WorkingDirectory=${opencti_dir}/ 559 | ExecStart=/usr/bin/yarn serv 560 | ExecReload=/bin/kill -s HUP \$MAINPID 561 | ExecStop=/bin/kill -s TERM \$MAINPID 562 | Restart=on-failure 563 | [Install] 564 | WantedBy=multi-user.target 565 | EOT 566 | 567 | echo "OpenCTI: Copy worker configuration" 568 | cp "$opencti_dir/worker/config.yml.sample" "$opencti_dir/worker/config.yml" 569 | 570 | echo "OpenCTI: Edit worker configuration" 571 | sed -i'' -e 's/token: '"'"'ChangeMe'"'"'/token: '"${RADMINTOKEN}"'/g' "$opencti_dir/worker/config.yml" 572 | 573 | # Switch to port 4000 574 | sed -i "s/'http:\/\/localhost:8080'/'http:\/\/localhost:4000'/" "$opencti_dir/worker/config.yml" 575 | 576 | echo "OpenCTI: create unit file" 577 | cat > /etc/systemd/system/opencti-worker@.service <<- EOT 578 | [Unit] 579 | Description=OpenCTI Worker daemon %i 580 | After=network.target opencti-server.service 581 | [Service] 582 | Type=simple 583 | WorkingDirectory=${opencti_dir}/worker/ 584 | ExecStart=/usr/bin/${run_python} "${opencti_dir}/worker/worker.py" 585 | ExecReload=/bin/kill -s HUP \$MAINPID 586 | ExecStop=/bin/kill -s TERM \$MAINPID 587 | PrivateTmp=true 588 | Restart=always 589 | [Install] 590 | WantedBy=multi-user.target 591 | EOT 592 | 593 | echo "OpenCTI: daemon-reload" 594 | systemctl daemon-reload 595 | 596 | ## Remove requirement for node v12-13 since we are installing v14; making it >= 12. 597 | sed -i 's/"node": ">= 12.* < 13.0.0"/"node": ">= 12"/' $opencti_dir/package.json 598 | 599 | echo "OpenCTI: Starting services - sleep 30 to wait for services to boot" 600 | enable_service 'opencti-server' 601 | sleep 30 # waiting for opencti to check all services 602 | check_service 'opencti-server' 603 | 604 | echo "OpenCTI: Enabling workers" 605 | for i in $(seq 1 $opencti_worker_count) 606 | do 607 | sleep 5 608 | enable_service "opencti-worker@$i" 609 | sleep 5 610 | check_service "opencti-worker@$i" 611 | done 612 | 613 | # Clean up packages 614 | log_section_heading "Clean up packages" 615 | apt-get clean 616 | apt-get autoremove -y 617 | -------------------------------------------------------------------------------- /userdata/installation-wrapper-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Why a wrapper script? Terraform doesn't support massively long scripts (the limit is 16k characters). The install script is much longer than that (even without the banner). This wrapper script sets up the base OS, installs the necessary tools to run the installation and connector scripts, and then pulls those in from an S3 bucket (as defined in s3.tf). 3 | # The wrapper script is used by each of the providers (in `*/vm.tf`) and each variable has to be filled out in order to run. Unfortunately, this means that if you change something in one provider (or in this script), you have to change it in each of the others. It's not ideal, but FYI. 4 | 5 | # Print all output to the specified logfile, the system log (-t: as opencti-install), and STDERR (-s). 6 | exec > >(tee /var/log/opencti-install.log|logger -t opencti-install -s 2>/dev/console) 2>&1 7 | 8 | echo "Update base OS" 9 | apt-get update 10 | apt-get upgrade -y 11 | 12 | # Copy install and connectors script down from cloud storage. 13 | if [[ ${cloud} == "aws" ]] 14 | then 15 | echo "Install AWS CLI" 16 | apt-get install -y awscli 17 | echo "Copy the opencti installer script to /opt" 18 | aws s3 cp s3://${storage_bucket}/${install_script_name} /opt/${install_script_name} 19 | echo "Copy opencti connectors script to /opt" 20 | aws s3 cp s3://${storage_bucket}/${connectors_script_name} /opt/${connectors_script_name} 21 | elif [[ ${cloud} == "azure" ]] 22 | then 23 | echo "Install Azure CLI (this can take several minutes)" 24 | curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash 25 | echo "Copy the opencti installer script to /opt" 26 | az storage blob download --account-name "${account_name}" --container-name "${storage_bucket}" --name "${install_script_name}" --file /opt/"${install_script_name}" --connection-string "${connection_string}" 27 | echo "Copy opencti connectors script to /opt" 28 | az storage blob download --account-name "${account_name}" --container-name "${storage_bucket}" --name "${connectors_script_name}" --file /opt/"${connectors_script_name}" --connection-string "${connection_string}" 29 | elif [[ ${cloud} == "gcp" ]] 30 | then 31 | echo "Copy the opencti installer script to /opt" 32 | gsutil cp gs://${storage_bucket}/${install_script_name} /opt/${install_script_name} 33 | echo "Copy opencti connectors script to /opt" 34 | gsutil cp gs://${storage_bucket}/${connectors_script_name} /opt/${connectors_script_name} 35 | fi 36 | 37 | echo "Make scripts executable" 38 | chmod +x /opt/${install_script_name} 39 | chmod +x /opt/${connectors_script_name} 40 | 41 | echo "Starting OpenCTI installation script" 42 | # Run the install script with the provided e-mail address. 43 | # AWS automatically runs the script as root, Azure doesn't. 44 | sudo /opt/${install_script_name} -e "${login_email}" 45 | echo "OpenCTI installation script complete." 46 | 47 | echo "Starting OpenCTI connectors script." 48 | # Run the script without prompting the user (the default, `-p 0`, will prompt if the user wants to apply; this is less than ideal for an automated script). 49 | sudo /opt/${connectors_script_name} -p 1 50 | echo "OpenCTI wrapper script complete." 51 | --------------------------------------------------------------------------------