├── LICENSE ├── README.md ├── ansible ├── .gitignore ├── roles │ └── mythic │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── .yamllint │ │ ├── README.md │ │ ├── files │ │ └── config.json │ │ ├── meta │ │ └── main.yml │ │ ├── tasks │ │ └── main.yml │ │ └── tests │ │ └── test.yml └── site.yml └── terraform ├── .gitignore ├── examples ├── aws.tf ├── digitalocean.tf └── gcp.tf ├── modules ├── aws │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── digitalocean │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── gcp │ ├── main.tf │ ├── outputs.tf │ └── variables.tf └── ssh_key │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── templates ├── config.tpl └── inventory.tpl └── terraform.tfvars.example /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 0xdeadbeefJERKY 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mythic-deploy 2 | Automated deployment and configuration of a [Mythic](https://github.com/its-a-feature/Mythic) server using Terraform and Ansible 3 | 4 | ## Prerequisites 5 | * [Install Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) 6 | * [Install Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) 7 | 8 | ## Runbook 9 | ### Deploy infrastructure (Terraform) 10 | 1. `git clone https://github.com/0xdeadbeefJERKY/mythic-deploy` 11 | 2. `cd mythic-deploy/terraform` 12 | 3. Create `main.tf`, using the `.tf` files in `terraform/examples` for guidance 13 | * e.g., `cp examples/digitalocean.tf main.tf` 14 | 4. `cp terraform.tfvars.example terraform.tfvars` 15 | 5. Modify `terraform.tfvars` as needed 16 | 6. Configure provider authentication 17 | * [AWS](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication) 18 | * [Digital Ocean](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs#argument-reference) 19 | * [Google Cloud](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started) 20 | 7. `terraform init` 21 | 8. `terraform plan` and review the output 22 | * Optionally, run `terraform plan -out terraform.tfplan` to save the Terraform plan locally. 23 | 9. `terraform apply` 24 | 25 | ### Provision servers (Ansible) 26 | 1. `cd mythic-deploy/ansible` 27 | 2. Modify `roles/mythic/files/config.json` to customize the Mythic deployment 28 | 3. `ansible-playbook -i inventory site.yml` 29 | 30 | ### Start Mythic 31 | 1. SSH into the Mythic server using one of the following methods: 32 | * `cd mythic-deploy/terraform && $(terraform output ssh_connect_cmd)` 33 | * `cd mythic-deploy/terraform/ssh_keys && ssh -F config mythic` 34 | * `cat mythic-deploy/terraform/ssh_keys/config >> ~/.ssh/config && ssh mythic` 35 | 2. From SSH session, run `cd $HOME/mythic && sudo ./start_mythic.sh` 36 | 3. Open browser, navigate to https://:7443 and log in using the credentials from `ansible/roles/mythic/files/config.json` -------------------------------------------------------------------------------- /ansible/.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | inventory -------------------------------------------------------------------------------- /ansible/roles/mythic/.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info/ 2 | *.py[cod] 3 | *.retry 4 | *.snap 5 | .bundle/ 6 | .cache/ 7 | .coverage 8 | .coverage.* 9 | .eggs/ 10 | .mypy_cache/ 11 | .tox/ 12 | .vagrant/ 13 | .pytest_cache/ 14 | build/ 15 | dist/ 16 | docs/docstree/html/ 17 | htmlcov/ 18 | pytestdebug.log 19 | ubuntu-xenial-16.04-cloudimg-console.log 20 | .python-version 21 | coverage.xml 22 | pip-wheel-metadata 23 | docs/docstree 24 | .docker 25 | molecule/** 26 | -------------------------------------------------------------------------------- /ansible/roles/mythic/.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: python 3 | python: "2.7" 4 | 5 | # Use the new container infrastructure 6 | sudo: false 7 | 8 | # Install ansible 9 | addons: 10 | apt: 11 | packages: 12 | - python-pip 13 | 14 | install: 15 | # Install ansible 16 | - pip install ansible 17 | 18 | # Check ansible version 19 | - ansible --version 20 | 21 | # Create ansible.cfg with correct roles_path 22 | - printf '[defaults]\nroles_path=../' >ansible.cfg 23 | 24 | script: 25 | # Basic role syntax check 26 | - ansible-playbook tests/test.yml -i tests/inventory --syntax-check 27 | 28 | notifications: 29 | webhooks: https://galaxy.ansible.com/api/v1/notifications/ -------------------------------------------------------------------------------- /ansible/roles/mythic/.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | # Based on ansible-lint config 3 | extends: default 4 | 5 | rules: 6 | braces: 7 | max-spaces-inside: 1 8 | level: error 9 | brackets: 10 | max-spaces-inside: 1 11 | level: error 12 | colons: 13 | max-spaces-after: -1 14 | level: error 15 | commas: 16 | max-spaces-after: -1 17 | level: error 18 | comments: disable 19 | comments-indentation: disable 20 | document-start: disable 21 | empty-lines: 22 | max: 3 23 | level: error 24 | hyphens: 25 | level: error 26 | indentation: disable 27 | key-duplicates: enable 28 | line-length: disable 29 | new-line-at-end-of-file: disable 30 | new-lines: 31 | type: unix 32 | trailing-spaces: disable 33 | truthy: disable 34 | -------------------------------------------------------------------------------- /ansible/roles/mythic/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | This role will deploy the [Mythic](https://github.com/its-a-feature/Mythic) post-exploit red teaming framework to the target server. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Modify the configuration file in `files/config.json` to customize the Mythic deployment, such as specifying the admin username and password. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | N/A 15 | 16 | Dependencies 17 | ------------ 18 | 19 | N/A 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | ```yaml 25 | --- 26 | - hosts: server 27 | roles: 28 | - mythic 29 | ``` 30 | 31 | License 32 | ------- 33 | 34 | MIT 35 | 36 | Author Information 37 | ------------------ 38 | 39 | @0xdeadbeefJERKY 40 | https://0xdeadbeefjerky.com -------------------------------------------------------------------------------- /ansible/roles/mythic/files/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mythic_admin_user": "admin", 3 | "mythic_admin_password": "Password123", 4 | "default_operation_name": "Operation Chimera", 5 | "listen_port": 7443, 6 | "ssl_cert_path": "./app/ssl/mythic-cert.pem", 7 | "ssl_key_path": "./app/ssl/mythic-ssl.key", 8 | "allowed_ip_blocks": ["0.0.0.0/0"], 9 | "use_ssl": true, 10 | "server_header": "nginx 1.2", 11 | "web_log_size": 1024000, 12 | "web_keep_logs": true, 13 | "siem_log_name": "", 14 | "excluded_c2_profiles": [ ], 15 | "excluded_payload_types": [ ], 16 | "start_documentation_container": true, 17 | "documentation_container_port": 8080 18 | } -------------------------------------------------------------------------------- /ansible/roles/mythic/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: 0xeadbeefJERKY 3 | description: Install Mythic post-exploit red teaming framework 4 | 5 | license: MIT 6 | 7 | min_ansible_version: 2.9 8 | 9 | platforms: 10 | - name: debian 11 | versions: 12 | - stretch 13 | - buster 14 | - name: ubuntu 15 | versions: 16 | - focal 17 | - bionic 18 | 19 | galaxy_tags: 20 | - "mythic" 21 | - "security" 22 | 23 | dependencies: [] 24 | -------------------------------------------------------------------------------- /ansible/roles/mythic/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set hostname 3 | hostname: 4 | name: "mythic" 5 | become: yes 6 | 7 | - name: Update and upgrade packages 8 | apt: 9 | upgrade: yes 10 | update_cache: yes 11 | become: yes 12 | 13 | - name: Clone Mythic Git repo 14 | git: 15 | repo: https://github.com/its-a-feature/Mythic 16 | dest: "{{ ansible_env.HOME }}/mythic" 17 | force: yes 18 | 19 | - name: Upload custom Mythic config.json 20 | copy: 21 | src: ./files/config.json 22 | dest: "{{ ansible_env.HOME }}/mythic/mythic-docker/config.json" 23 | owner: "{{ ansible_user }}" 24 | group: "{{ ansible_user }}" 25 | mode: 0644 26 | 27 | - name: Install Docker using Mythic-provided script 28 | command: "{{ ansible_env.HOME }}/mythic/install_docker_{{ ansible_distribution|lower }}.sh" 29 | become: yes 30 | -------------------------------------------------------------------------------- /ansible/roles/mythic/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - mythic -------------------------------------------------------------------------------- /ansible/site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: server 3 | roles: 4 | - mythic -------------------------------------------------------------------------------- /terraform/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | 11 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 12 | # password, private keys, and other secrets. These should not be part of version 13 | # control as they are data points which are potentially sensitive and subject 14 | # to change depending on the environment. 15 | # 16 | *.tfvars 17 | 18 | # Ignore override files as they are usually used to override resources locally and so 19 | # are not checked in 20 | override.tf 21 | override.tf.json 22 | *_override.tf 23 | *_override.tf.json 24 | 25 | # Include override files you do wish to add to version control using negated pattern 26 | # 27 | # !example_override.tf 28 | 29 | # Exclude tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | *.tfplan 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | 36 | # Ignore log files 37 | *.log 38 | 39 | # Ignore generated SSH keys and associated files 40 | ssh_keys/ 41 | 42 | # Ignore main.tf 43 | /main.tf -------------------------------------------------------------------------------- /terraform/examples/aws.tf: -------------------------------------------------------------------------------- 1 | variable "ports" {} 2 | 3 | module "ssh_key" { 4 | source = "./modules/ssh_key" 5 | } 6 | 7 | module "aws" { 8 | source = "./modules/aws" 9 | public_key_openssh = module.ssh_key.public_key_openssh 10 | ports = var.ports 11 | privkey_filename = module.ssh_key.privkey_filename 12 | } 13 | 14 | output "ec2_ip" { 15 | value = module.aws.ec2_ip 16 | } 17 | 18 | output "ssh_connect_cmd" { 19 | value = "ssh -F ${module.aws.ssh_config} ${module.aws.hostname}" 20 | } 21 | -------------------------------------------------------------------------------- /terraform/examples/digitalocean.tf: -------------------------------------------------------------------------------- 1 | variable "ports" {} 2 | 3 | module "ssh_key" { 4 | source = "./modules/ssh_key" 5 | } 6 | 7 | module "digitalocean" { 8 | source = "./modules/digitalocean" 9 | public_key_openssh = module.ssh_key.public_key_openssh 10 | ports = var.ports 11 | privkey_filename = module.ssh_key.privkey_filename 12 | } 13 | 14 | output "droplet_ip" { 15 | value = module.digitalocean.droplet_ip 16 | } 17 | 18 | output "ssh_connect_cmd" { 19 | value = "ssh -F ${module.digitalocean.ssh_config} ${module.digitalocean.hostname}" 20 | } 21 | -------------------------------------------------------------------------------- /terraform/examples/gcp.tf: -------------------------------------------------------------------------------- 1 | variable "ports" {} 2 | 3 | module "ssh_key" { 4 | source = "./modules/ssh_key" 5 | } 6 | 7 | module "gcp" { 8 | source = "./modules/gcp" 9 | public_key_openssh = module.ssh_key.public_key_openssh 10 | ports = var.ports 11 | privkey_filename = module.ssh_key.privkey_filename 12 | project = "mythic-deploy-test" 13 | region = "us-east1" 14 | zone = "us-east1-b" 15 | } 16 | 17 | output "vm_ip" { 18 | value = module.gcp.vm_ip 19 | } 20 | 21 | output "ssh_connect_cmd" { 22 | value = "ssh -F ${module.gcp.ssh_config} ${module.gcp.hostname}" 23 | } -------------------------------------------------------------------------------- /terraform/modules/aws/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.region 3 | } 4 | 5 | # Create a VPC 6 | resource "aws_vpc" "main" { 7 | cidr_block = var.vpc_cidr 8 | 9 | tags = { 10 | Name = "${var.name}-vpc" 11 | } 12 | } 13 | 14 | # Create a subnet in the VPC 15 | resource "aws_subnet" "main" { 16 | vpc_id = aws_vpc.main.id 17 | cidr_block = var.subnet_cidr 18 | 19 | tags = { 20 | Name = "${var.name}-subnet" 21 | } 22 | } 23 | 24 | # Add SSH key to AWS 25 | resource "aws_key_pair" "main" { 26 | key_name = "${var.name}-keypair" 27 | public_key = var.public_key_openssh 28 | } 29 | 30 | # Create Security Group that restricts inbound 31 | # traffic for Mythic server 32 | resource "aws_security_group" "main" { 33 | name = "ssh-and-mythic-ports-only" 34 | description = "Allow access to Mythic server" 35 | vpc_id = aws_vpc.main.id 36 | 37 | dynamic "ingress" { 38 | for_each = [for p in var.ports : { 39 | proto = p.proto 40 | port = p.port 41 | allow = p.allow 42 | desc = p.desc 43 | }] 44 | 45 | content { 46 | description = ingress.value.desc 47 | protocol = ingress.value.proto 48 | from_port = ingress.value.port 49 | to_port = ingress.value.port 50 | cidr_blocks = ingress.value.allow 51 | } 52 | } 53 | 54 | egress { 55 | from_port = 0 56 | to_port = 0 57 | protocol = "-1" 58 | cidr_blocks = ["0.0.0.0/0"] 59 | } 60 | 61 | tags = { 62 | Name = "${var.name}-sg" 63 | } 64 | } 65 | 66 | # Create EC2 instance to which Mythic will be deployed 67 | resource "aws_instance" "mythic" { 68 | ami = var.ami 69 | instance_type = var.instance_type 70 | key_name = aws_key_pair.main.key_name 71 | security_groups = [aws_security_group.main.id] 72 | subnet_id = aws_subnet.main.id 73 | associate_public_ip_address = true 74 | 75 | tags = { 76 | Name = "${var.name}-ec2" 77 | } 78 | } 79 | 80 | # Deploy internet gateway 81 | resource "aws_internet_gateway" "main" { 82 | vpc_id = aws_vpc.main.id 83 | 84 | tags = { 85 | Name = "${var.name}-ig" 86 | } 87 | } 88 | 89 | # Setup route table and associate with subnet 90 | resource "aws_route_table" "main" { 91 | vpc_id = aws_vpc.main.id 92 | 93 | route { 94 | cidr_block = "0.0.0.0/0" 95 | gateway_id = aws_internet_gateway.main.id 96 | } 97 | 98 | tags = { 99 | Name = "${var.name}-rt" 100 | } 101 | } 102 | 103 | resource "aws_route_table_association" "main" { 104 | subnet_id = aws_subnet.main.id 105 | route_table_id = aws_route_table.main.id 106 | } 107 | 108 | # Create SSH config file 109 | resource "local_file" "sshconfig" { 110 | content = templatefile("${path.cwd}/templates/config.tpl", { 111 | host = var.name 112 | hostname = aws_instance.mythic.public_ip 113 | identityfile = var.privkey_filename 114 | user = var.user 115 | }) 116 | filename = "${path.cwd}/ssh_keys/config" 117 | } 118 | 119 | # Populate Ansible inventory file 120 | resource "local_file" "inventory" { 121 | content = templatefile("${path.cwd}/templates/inventory.tpl", { 122 | host = var.name 123 | hostname = aws_instance.mythic.public_ip 124 | identityfile = var.privkey_filename 125 | user = var.user 126 | }) 127 | filename = "${path.cwd}/../ansible/inventory" 128 | } 129 | -------------------------------------------------------------------------------- /terraform/modules/aws/outputs.tf: -------------------------------------------------------------------------------- 1 | output "ec2_ip" { 2 | value = aws_instance.mythic.public_ip 3 | description = "Public IP for Mythic EC2 instance" 4 | } 5 | 6 | output "hostname" { 7 | value = var.name 8 | description = "Hostname assigned to server for SSH connections" 9 | } 10 | 11 | output "ssh_config" { 12 | value = local_file.sshconfig.filename 13 | description = "Full path to SSH config file" 14 | } 15 | -------------------------------------------------------------------------------- /terraform/modules/aws/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Mythic instance name (prefix used for component names)" 3 | default = "mythic" 4 | type = string 5 | } 6 | 7 | variable "ami" { 8 | description = "EC2 AMI to be used" 9 | default = "ami-0b893eef6e21b60a1" # Ubuntu 18.04 (Bionic) in us-east-1 region 10 | type = string 11 | } 12 | 13 | variable "region" { 14 | default = "us-east-1" 15 | description = "AWS region to which the instance will be deployed" 16 | type = string 17 | } 18 | 19 | variable "instance_type" { 20 | default = "t2.small" 21 | description = "EC2 instance type" 22 | type = string 23 | } 24 | 25 | variable "vpc_cidr" { 26 | default = "10.0.0.0/16" 27 | description = "CIDR block assigned to VPC" 28 | type = string 29 | } 30 | 31 | variable "subnet_cidr" { 32 | default = "10.0.1.0/24" 33 | description = "CIDR block assigned to subnet in VPC" 34 | type = string 35 | } 36 | 37 | variable "public_key_openssh" { 38 | description = "Contents of SSH public key used to connect to Mythic server" 39 | type = string 40 | } 41 | 42 | variable "user" { 43 | default = "ubuntu" 44 | description = "SSH username" 45 | type = string 46 | } 47 | 48 | variable "ports" { 49 | description = "List of objects describing ports to be allowlisted" 50 | type = list(object({ 51 | proto = string 52 | port = number 53 | allow = list(string) 54 | desc = string 55 | })) 56 | default = [ 57 | { 58 | proto = "tcp" 59 | port = 22 60 | allow = ["0.0.0.0/0", "::/0"] 61 | desc = "Allow SSH access to Mythic server from any IP" 62 | }, 63 | { 64 | proto = "tcp" 65 | port = 80 66 | allow = ["0.0.0.0/0", "::/0"] 67 | desc = "Allow HTTP access to Mythic server from any IP" 68 | }, 69 | { 70 | proto = "tcp" 71 | port = 443 72 | allow = ["0.0.0.0/0", "::/0"] 73 | desc = "Allow HTTPS access to Mythic server from any IP" 74 | }, 75 | { 76 | proto = "tcp" 77 | port = 7443 78 | allow = ["0.0.0.0/0", "::/0"] 79 | desc = "Allow HTTPS admin access to Mythic server from any IP" 80 | }, 81 | { 82 | proto = "tcp" 83 | port = 8080 84 | allow = ["0.0.0.0/0", "::/0"] 85 | desc = "Allow HTTP documentation access to Mythic server from any IP" 86 | }, 87 | ] 88 | 89 | } 90 | 91 | variable "privkey_filename" { 92 | description = "Full file path to SSH private key" 93 | type = string 94 | } 95 | -------------------------------------------------------------------------------- /terraform/modules/digitalocean/main.tf: -------------------------------------------------------------------------------- 1 | provider "digitalocean" {} 2 | 3 | # Add SSH key to Digital Ocean 4 | resource "digitalocean_ssh_key" "main" { 5 | name = "${var.name}-ssh-key" 6 | public_key = var.public_key_openssh 7 | } 8 | 9 | # Create Digital Ocean firewall that restricts inbound 10 | # traffic for Mythic server 11 | resource "digitalocean_firewall" "main" { 12 | name = "ssh-and-mythic-ports-only" 13 | 14 | droplet_ids = [digitalocean_droplet.mythic.id] 15 | 16 | dynamic "inbound_rule" { 17 | for_each = [for p in var.ports : { 18 | proto = p.proto 19 | port = p.port 20 | allow = p.allow 21 | }] 22 | 23 | content { 24 | protocol = inbound_rule.value.proto 25 | port_range = inbound_rule.value.port 26 | source_addresses = inbound_rule.value.allow 27 | } 28 | } 29 | 30 | outbound_rule { 31 | protocol = "tcp" 32 | port_range = "1-65535" 33 | destination_addresses = ["0.0.0.0/0", "::/0"] 34 | } 35 | 36 | outbound_rule { 37 | protocol = "udp" 38 | port_range = "1-65535" 39 | destination_addresses = ["0.0.0.0/0", "::/0"] 40 | } 41 | 42 | outbound_rule { 43 | protocol = "icmp" 44 | destination_addresses = ["0.0.0.0/0", "::/0"] 45 | } 46 | } 47 | 48 | # Create Droplet to which Mythic will be deployed 49 | resource "digitalocean_droplet" "mythic" { 50 | image = var.image 51 | name = var.name 52 | region = var.region 53 | size = var.size 54 | ssh_keys = [digitalocean_ssh_key.main.fingerprint] 55 | } 56 | 57 | # Create SSH config file 58 | resource "local_file" "sshconfig" { 59 | content = templatefile("${path.cwd}/templates/config.tpl", { 60 | host = var.name 61 | hostname = digitalocean_droplet.mythic.ipv4_address 62 | identityfile = var.privkey_filename 63 | user = var.user 64 | }) 65 | filename = "${path.cwd}/ssh_keys/config" 66 | } 67 | 68 | # Populate Ansible inventory file 69 | resource "local_file" "inventory" { 70 | content = templatefile("${path.cwd}/templates/inventory.tpl", { 71 | host = var.name 72 | hostname = digitalocean_droplet.mythic.ipv4_address 73 | identityfile = var.privkey_filename 74 | user = var.user 75 | }) 76 | filename = "${path.cwd}/../ansible/inventory" 77 | } 78 | -------------------------------------------------------------------------------- /terraform/modules/digitalocean/outputs.tf: -------------------------------------------------------------------------------- 1 | output "droplet_ip" { 2 | value = digitalocean_droplet.mythic.ipv4_address 3 | description = "Mythic server IPv4 address" 4 | } 5 | 6 | output "hostname" { 7 | value = var.name 8 | description = "Hostname assigned to server for SSH connections" 9 | } 10 | 11 | output "ssh_config" { 12 | value = local_file.sshconfig.filename 13 | description = "Full path to SSH config file" 14 | } 15 | -------------------------------------------------------------------------------- /terraform/modules/digitalocean/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Mythic instance name (prefix used for component names)" 3 | default = "mythic" 4 | type = string 5 | } 6 | 7 | variable "public_key_openssh" { 8 | description = "Contents of SSH public key used to connect to Mythic server" 9 | type = string 10 | } 11 | 12 | variable "user" { 13 | default = "root" 14 | description = "SSH username" 15 | type = string 16 | } 17 | 18 | variable "ports" { 19 | description = "List of objects describing ports to be allowlisted" 20 | type = list(object({ 21 | proto = string 22 | port = number 23 | allow = list(string) 24 | desc = string 25 | })) 26 | default = [ 27 | { 28 | proto = "tcp" 29 | port = 22 30 | allow = ["0.0.0.0/0", "::/0"] 31 | desc = "Allow SSH access to Mythic server from any IP" 32 | }, 33 | { 34 | proto = "tcp" 35 | port = 80 36 | allow = ["0.0.0.0/0", "::/0"] 37 | desc = "Allow HTTP access to Mythic server from any IP" 38 | }, 39 | { 40 | proto = "tcp" 41 | port = 443 42 | allow = ["0.0.0.0/0", "::/0"] 43 | desc = "Allow HTTPS access to Mythic server from any IP" 44 | }, 45 | { 46 | proto = "tcp" 47 | port = 7443 48 | allow = ["0.0.0.0/0", "::/0"] 49 | desc = "Allow HTTPS admin access to Mythic server from any IP" 50 | }, 51 | { 52 | proto = "tcp" 53 | port = 8080 54 | allow = ["0.0.0.0/0", "::/0"] 55 | desc = "Allow HTTP documentation access to Mythic server from any IP" 56 | }, 57 | ] 58 | } 59 | 60 | variable "image" { 61 | description = "Droplet image to be used" 62 | default = "ubuntu-18-04-x64" 63 | type = string 64 | } 65 | 66 | variable "region" { 67 | description = "Digital Ocean region to which the Droplet will be deployed" 68 | default = "nyc3" 69 | type = string 70 | } 71 | 72 | variable "size" { 73 | description = "Droplet size of the Mythic instance" 74 | default = "s-1vcpu-2gb" 75 | type = string 76 | } 77 | 78 | variable "privkey_filename" { 79 | description = "Full file path to SSH private key" 80 | type = string 81 | } 82 | -------------------------------------------------------------------------------- /terraform/modules/gcp/main.tf: -------------------------------------------------------------------------------- 1 | provider "google" { 2 | project = var.project 3 | region = var.region 4 | zone = var.zone 5 | } 6 | 7 | # Enable Cloud Resource Manager API for project 8 | resource "google_project_service" "main" { 9 | project = var.project 10 | service = "cloudresourcemanager.googleapis.com" 11 | } 12 | 13 | # Create network within project to which the Mythic VM will be deployed 14 | resource "google_compute_network" "main" { 15 | name = "${var.name}-network" 16 | auto_create_subnetworks = "false" 17 | project = var.project 18 | routing_mode = "GLOBAL" 19 | } 20 | 21 | resource "google_compute_subnetwork" "main" { 22 | name = "${var.name}-subnetwork" 23 | ip_cidr_range = var.subnet_cidr 24 | region = var.region 25 | network = google_compute_network.main.id 26 | } 27 | 28 | # Create firewall that restricts inbound traffic for Mythic server 29 | resource "google_compute_firewall" "main" { 30 | for_each = { for port in var.ports : port.port => port } 31 | name = "allow-${each.value.port}" 32 | network = google_compute_network.main.self_link 33 | project = google_compute_network.main.project 34 | 35 | allow { 36 | protocol = each.value.proto 37 | ports = [each.value.port] 38 | } 39 | 40 | source_ranges = each.value.allow 41 | } 42 | 43 | # Create Mythic VM instance 44 | resource "google_compute_instance" "main" { 45 | name = var.name 46 | machine_type = var.machine_type 47 | zone = var.zone 48 | 49 | boot_disk { 50 | initialize_params { 51 | image = var.image 52 | } 53 | } 54 | 55 | network_interface { 56 | network = google_compute_network.main.id 57 | subnetwork = google_compute_subnetwork.main.id 58 | 59 | access_config { 60 | # Creates an ephemeral IP for the VM instance 61 | } 62 | } 63 | 64 | metadata = { 65 | ssh-keys = "${var.user}:${var.public_key_openssh}" 66 | } 67 | 68 | depends_on = [google_project_service.main] 69 | } 70 | 71 | # Create SSH config file 72 | resource "local_file" "sshconfig" { 73 | content = templatefile("${path.cwd}/templates/config.tpl", { 74 | host = var.name 75 | hostname = google_compute_instance.main.network_interface[0].access_config[0].nat_ip 76 | identityfile = var.privkey_filename 77 | user = var.user 78 | }) 79 | filename = "${path.cwd}/ssh_keys/config" 80 | } 81 | 82 | # Populate Ansible inventory file 83 | resource "local_file" "inventory" { 84 | content = templatefile("${path.cwd}/templates/inventory.tpl", { 85 | host = var.name 86 | hostname = google_compute_instance.main.network_interface[0].access_config[0].nat_ip 87 | identityfile = var.privkey_filename 88 | user = var.user 89 | }) 90 | filename = "${path.cwd}/../ansible/inventory" 91 | } 92 | -------------------------------------------------------------------------------- /terraform/modules/gcp/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vm_ip" { 2 | value = google_compute_instance.main.network_interface[0].access_config[0].nat_ip 3 | description = "Public IP for Mythic VM instance" 4 | } 5 | 6 | output "hostname" { 7 | value = var.name 8 | description = "Hostname assigned to server for SSH connections" 9 | } 10 | 11 | output "ssh_config" { 12 | value = local_file.sshconfig.filename 13 | description = "Full path to SSH config file" 14 | } -------------------------------------------------------------------------------- /terraform/modules/gcp/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Mythic instance name (prefix used for component names)" 3 | default = "mythic" 4 | type = string 5 | } 6 | 7 | variable "project" { 8 | description = "Project to which the instance will be deployed" 9 | type = string 10 | } 11 | 12 | variable "image" { 13 | description = "VM image to be used" 14 | default = "ubuntu-1804-lts" 15 | type = string 16 | } 17 | 18 | variable "machine_type" { 19 | description = "VM type" 20 | default = "e2-small" 21 | type = string 22 | } 23 | 24 | variable "region" { 25 | description = "Region to which the instance will be deployed" 26 | type = string 27 | } 28 | 29 | variable "zone" { 30 | description = "Zone to which the instance will be deployed" 31 | type = string 32 | } 33 | 34 | variable "subnet_cidr" { 35 | default = "10.0.1.0/24" 36 | description = "CIDR block assigned to subnet in VPC" 37 | type = string 38 | } 39 | 40 | variable "public_key_openssh" { 41 | description = "Contents of SSH public key used to connect to Mythic server" 42 | type = string 43 | } 44 | 45 | variable "user" { 46 | default = "ubuntu" 47 | description = "SSH username" 48 | type = string 49 | } 50 | 51 | variable "ports" { 52 | description = "List of objects describing ports to be allowlisted" 53 | type = list(object({ 54 | proto = string 55 | port = number 56 | allow = list(string) 57 | desc = string 58 | })) 59 | default = [ 60 | { 61 | proto = "tcp" 62 | port = 22 63 | allow = ["0.0.0.0/0", "::/0"] 64 | desc = "Allow SSH access to Mythic server from any IP" 65 | }, 66 | { 67 | proto = "tcp" 68 | port = 80 69 | allow = ["0.0.0.0/0", "::/0"] 70 | desc = "Allow HTTP access to Mythic server from any IP" 71 | }, 72 | { 73 | proto = "tcp" 74 | port = 443 75 | allow = ["0.0.0.0/0", "::/0"] 76 | desc = "Allow HTTPS access to Mythic server from any IP" 77 | }, 78 | { 79 | proto = "tcp" 80 | port = 7443 81 | allow = ["0.0.0.0/0", "::/0"] 82 | desc = "Allow HTTPS admin access to Mythic server from any IP" 83 | }, 84 | { 85 | proto = "tcp" 86 | port = 8080 87 | allow = ["0.0.0.0/0", "::/0"] 88 | desc = "Allow HTTP documentation access to Mythic server from any IP" 89 | }, 90 | ] 91 | 92 | } 93 | 94 | variable "privkey_filename" { 95 | description = "Full file path to SSH private key" 96 | type = string 97 | } 98 | -------------------------------------------------------------------------------- /terraform/modules/ssh_key/main.tf: -------------------------------------------------------------------------------- 1 | # Generate SSH keypair 2 | resource "tls_private_key" "main" { 3 | algorithm = "RSA" 4 | rsa_bits = 4096 5 | } 6 | 7 | # Save private key to local file 8 | resource "local_file" "privkey" { 9 | content = tls_private_key.main.private_key_pem 10 | filename = "${path.cwd}/ssh_keys/${var.name}" 11 | file_permission = "0600" 12 | } 13 | 14 | # Save public key to local file 15 | resource "local_file" "pubkey" { 16 | content = tls_private_key.main.public_key_openssh 17 | filename = "${path.cwd}/ssh_keys/${var.name}.pub" 18 | } 19 | -------------------------------------------------------------------------------- /terraform/modules/ssh_key/outputs.tf: -------------------------------------------------------------------------------- 1 | output "public_key_openssh" { 2 | description = "SSH public key contents" 3 | value = tls_private_key.main.public_key_openssh 4 | } 5 | 6 | output "privkey_filename" { 7 | description = "SSH private key filename" 8 | value = local_file.privkey.filename 9 | } 10 | -------------------------------------------------------------------------------- /terraform/modules/ssh_key/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "SSH key filename" 3 | default = "mythic" 4 | type = string 5 | } 6 | -------------------------------------------------------------------------------- /terraform/templates/config.tpl: -------------------------------------------------------------------------------- 1 | Host ${host} 2 | Hostname ${hostname} 3 | User ${user} 4 | IdentityFile ${identityfile} 5 | -------------------------------------------------------------------------------- /terraform/templates/inventory.tpl: -------------------------------------------------------------------------------- 1 | [server] 2 | ${host} ansible_host=${hostname} ansible_user=${user} ansible_ssh_private_key_file=${identityfile} -------------------------------------------------------------------------------- /terraform/terraform.tfvars.example: -------------------------------------------------------------------------------- 1 | ports = [ 2 | { 3 | proto = "tcp" 4 | port = 22 5 | allow = ["1.2.3.4/32"] 6 | desc = "Allow SSH access to Mythic server from allowlisted IPs" 7 | }, 8 | { 9 | proto = "tcp" 10 | port = 80 11 | allow = ["1.2.3.4/32"] 12 | desc = "Allow HTTP access to Mythic server from allowlisted IPs" 13 | }, 14 | { 15 | proto = "tcp" 16 | port = 443 17 | allow = ["1.2.3.4/32"] 18 | desc = "Allow HTTPS access to Mythic server from allowlisted IPs" 19 | }, 20 | { 21 | proto = "tcp" 22 | port = 7443 23 | allow = ["1.2.3.4/32"] 24 | desc = "Allow HTTPS admin access to Mythic server from allowlisted IPs" 25 | }, 26 | { 27 | proto = "tcp" 28 | port = 8080 29 | allow = ["1.2.3.4/32"] 30 | desc = "Allow HTTP documentation access to Mythic server from allowlisted IPs" 31 | }, 32 | ] --------------------------------------------------------------------------------