Initial public portfolio release
Sanitized version of red team infrastructure automation platform. Operational content (implant pipelines, lures, credential capture) replaced with documented stubs. Architecture and infrastructure automation code intact.
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
---
|
||||
# Linode Attack Box Deployment Playbook
|
||||
# Based on attk-box-setup but optimized for headless server deployment
|
||||
|
||||
- name: Deploy Attack Box on Linode
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ ansible_playbook_python }}"
|
||||
deployment_type: "attack_box"
|
||||
attack_box_name: "{{ attack_box_name | default('a-' + deployment_id) }}"
|
||||
attack_box_type: "{{ attack_box_type | default('kali') }}"
|
||||
linode_instance_type: "{{ linode_instance_type | default('g6-standard-4') }}"
|
||||
linode_region: "{{ linode_region | default('us-east') }}"
|
||||
ssh_key_name: "{{ ssh_key_path | default('c2deploy_' + attack_box_name) | basename | regex_replace('\\.pub$', '') }}"
|
||||
|
||||
# Attack box image mapping (headless versions)
|
||||
attack_box_images:
|
||||
kali: "linode/kali"
|
||||
custom: "linode/kali" # Use Kali as base for custom builds
|
||||
quick_recon: "linode/kali" # Kali Linux for easy tool expansion
|
||||
|
||||
tasks:
|
||||
- name: Debug attack box deployment
|
||||
debug:
|
||||
msg: "Deploying {{ attack_box_type }} attack box on Linode in {{ linode_region }}"
|
||||
|
||||
- name: Set attack box image
|
||||
set_fact:
|
||||
attack_box_image: "{{ attack_box_images[attack_box_type] | default('linode/kali') }}"
|
||||
|
||||
- name: Check if SSH key exists (should be pre-generated)
|
||||
stat:
|
||||
path: "~/.ssh/{{ ssh_key_name }}"
|
||||
register: ssh_key_check
|
||||
|
||||
- name: Generate SSH key pair for attack box (if not exists)
|
||||
openssh_keypair:
|
||||
path: "~/.ssh/{{ ssh_key_name }}"
|
||||
type: rsa
|
||||
size: 2048
|
||||
force: no
|
||||
register: ssh_key_result
|
||||
when: not ssh_key_check.stat.exists
|
||||
|
||||
- name: Read public key content
|
||||
slurp:
|
||||
src: "~/.ssh/{{ ssh_key_name }}.pub"
|
||||
register: public_key_content
|
||||
|
||||
- name: Add SSH key to Linode
|
||||
uri:
|
||||
url: "https://api.linode.com/v4/profile/sshkeys"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ linode_token }}"
|
||||
Content-Type: "application/json"
|
||||
body_format: json
|
||||
body:
|
||||
label: "{{ ssh_key_name }}"
|
||||
ssh_key: "{{ public_key_content.content | b64decode | trim }}"
|
||||
status_code: [200, 201]
|
||||
register: linode_ssh_key
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Generate strong root password
|
||||
set_fact:
|
||||
root_password: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,!@#$%^&*-_=+ length=32') }}"
|
||||
when: ansible_password is not defined
|
||||
|
||||
- name: Set ansible_password if not defined
|
||||
set_fact:
|
||||
ansible_password: "{{ root_password }}"
|
||||
when: ansible_password is not defined and root_password is defined
|
||||
|
||||
- name: Create Linode instance for attack box
|
||||
uri:
|
||||
url: "https://api.linode.com/v4/linode/instances"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ linode_token }}"
|
||||
Content-Type: "application/json"
|
||||
body_format: json
|
||||
body:
|
||||
label: "{{ attack_box_name }}"
|
||||
type: "{{ linode_instance_type }}"
|
||||
region: "{{ linode_region }}"
|
||||
image: "{{ attack_box_image }}"
|
||||
root_pass: "{{ ansible_password | default(root_password) }}"
|
||||
authorized_keys:
|
||||
- "{{ public_key_content.content | b64decode | trim }}"
|
||||
booted: true
|
||||
backups_enabled: false
|
||||
private_ip: false
|
||||
tags:
|
||||
- "attack-box"
|
||||
- "c2itall"
|
||||
- "{{ deployment_id }}"
|
||||
status_code: [200, 201]
|
||||
register: linode_instance
|
||||
|
||||
- name: Get current timestamp
|
||||
setup:
|
||||
gather_subset: min
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Ensure logs directory exists
|
||||
file:
|
||||
path: "{{ playbook_dir }}/logs"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Save attack box credentials to local file
|
||||
copy:
|
||||
content: |
|
||||
Attack Box: {{ attack_box_name }}
|
||||
Deployment ID: {{ deployment_id }}
|
||||
Instance IP: {{ linode_instance.json.ipv4[0] if linode_instance.json.ipv4 else 'Pending' }}
|
||||
SSH Key: ~/.ssh/{{ ssh_key_name }}
|
||||
Root Password: {{ ansible_password | default(root_password) }}
|
||||
SSH Command: ssh -i ~/.ssh/{{ ssh_key_name }} root@{{ linode_instance.json.ipv4[0] if linode_instance.json.ipv4 else 'IP_ADDRESS' }}
|
||||
|
||||
Generated at: {{ ansible_date_time.iso8601 }}
|
||||
dest: "{{ playbook_dir }}/logs/deployment_info_{{ deployment_id }}.txt"
|
||||
mode: '0600'
|
||||
when: linode_instance is succeeded
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Save instance information
|
||||
set_fact:
|
||||
attack_box_ip: "{{ linode_instance.json.ipv4[0] }}"
|
||||
attack_box_id: "{{ linode_instance.json.id }}"
|
||||
|
||||
- name: Display attack box information
|
||||
debug:
|
||||
msg:
|
||||
- "Attack Box Created Successfully!"
|
||||
- "Name: {{ attack_box_name }}"
|
||||
- "IP Address: {{ attack_box_ip }}"
|
||||
- "Instance ID: {{ attack_box_id }}"
|
||||
- "Type: {{ attack_box_type }}"
|
||||
- "SSH Key: ~/.ssh/{{ ssh_key_name }}"
|
||||
|
||||
- name: Wait for instance to be fully booted
|
||||
wait_for:
|
||||
host: "{{ attack_box_ip }}"
|
||||
port: 22
|
||||
delay: 30
|
||||
timeout: 300
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Test SSH connectivity
|
||||
command: ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -i ~/.ssh/{{ ssh_key_name }} root@{{ attack_box_ip }} echo "SSH connection successful"
|
||||
register: ssh_test
|
||||
retries: 5
|
||||
delay: 30
|
||||
until: ssh_test.rc == 0
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Create dynamic inventory for attack box configuration
|
||||
add_host:
|
||||
name: "{{ attack_box_ip }}"
|
||||
groups: attack_boxes
|
||||
ansible_host: "{{ attack_box_ip }}"
|
||||
ansible_user: root
|
||||
ansible_ssh_private_key_file: "~/.ssh/{{ ssh_key_name }}"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
||||
attack_box_type: "{{ attack_box_type }}"
|
||||
deployment_id: "{{ deployment_id }}"
|
||||
|
||||
- name: Save deployment information
|
||||
copy:
|
||||
content: |
|
||||
# Attack Box Deployment Information
|
||||
DEPLOYMENT_ID={{ deployment_id }}
|
||||
ATTACK_BOX_NAME={{ attack_box_name }}
|
||||
ATTACK_BOX_IP={{ attack_box_ip }}
|
||||
ATTACK_BOX_ID={{ attack_box_id }}
|
||||
ATTACK_BOX_TYPE={{ attack_box_type }}
|
||||
SSH_KEY_PATH=~/.ssh/{{ ssh_key_name }}
|
||||
PROVIDER=linode
|
||||
REGION={{ linode_region }}
|
||||
INSTANCE_TYPE={{ linode_instance_type }}
|
||||
DEPLOYED_DATE={{ ansible_date_time.iso8601 }}
|
||||
dest: "{{ playbook_dir }}/logs/attack_box_{{ deployment_id }}.env"
|
||||
|
||||
- name: Configure Attack Box
|
||||
hosts: attack_boxes
|
||||
gather_facts: yes
|
||||
become: yes
|
||||
vars:
|
||||
setup_workspace: "{{ setup_workspace | default(true) }}"
|
||||
setup_vpn: "{{ setup_vpn | default(false) }}"
|
||||
setup_tor: "{{ setup_tor | default(false) }}"
|
||||
|
||||
tasks:
|
||||
- name: Wait for system to be ready
|
||||
wait_for_connection:
|
||||
delay: 30
|
||||
timeout: 300
|
||||
|
||||
- name: Update package cache only (avoid grub-pc issues)
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
when: ansible_os_family == "Debian"
|
||||
retries: 3
|
||||
delay: 10
|
||||
|
||||
- name: Include attack box configuration tasks
|
||||
include_tasks: "{{ playbook_dir }}/../../modules/attack-box/tasks/configure_attack_box.yml"
|
||||
when: deployment_type != "quick_recon_box"
|
||||
|
||||
- name: Include quick recon configuration tasks
|
||||
include_tasks: "{{ playbook_dir }}/../../modules/attack-box/tasks/configure_quick_recon.yml"
|
||||
when: deployment_type == "quick_recon_box"
|
||||
|
||||
- name: Display final setup information (Full Attack Box)
|
||||
debug:
|
||||
msg:
|
||||
- "🎯 Attack Box Configuration Complete!"
|
||||
- "📦 Instance: {{ attack_box_name }}"
|
||||
- "🔑 SSH Command: ssh -i ~/.ssh/a-{{ deployment_id }} root@{{ ansible_host }}"
|
||||
- "🔒 Root Password: {{ ansible_password | default('Check deployment_info_' + deployment_id + '.txt') }}"
|
||||
- "📁 Credentials saved to: logs/deployment_info_{{ deployment_id }}.txt"
|
||||
- ""
|
||||
- "🛠️ Available Commands:"
|
||||
- " recon <target> - Run reconnaissance automation"
|
||||
- " portscan <target> - Run port scan automation"
|
||||
- " webenum <target> - Run web enumeration automation"
|
||||
when: deployment_type != "quick_recon_box"
|
||||
|
||||
- name: Display final setup information (Quick Recon Box)
|
||||
debug:
|
||||
msg:
|
||||
- "🎯 Quick Recon Box Configuration Complete!"
|
||||
- "📦 Instance: {{ attack_box_name }}"
|
||||
- "🔑 SSH Command: ssh -i ~/.ssh/qr-{{ deployment_id }} root@{{ ansible_host }}"
|
||||
- "🔒 Root Password: {{ ansible_password | default('Check deployment_info_' + deployment_id + '.txt') }}"
|
||||
- "📁 Credentials saved to: logs/deployment_info_{{ deployment_id }}.txt"
|
||||
- ""
|
||||
- "🎯 Quick Recon Commands:"
|
||||
- " qr - Go to working directory"
|
||||
- " toolkit - Show available tools"
|
||||
- " portscan <target> - Port scan"
|
||||
- " subfind <domain> - Subdomain enumeration"
|
||||
- " webscan <url> - Web application scan"
|
||||
- " tor-recon <target> - Anonymous reconnaissance"
|
||||
when: deployment_type == "quick_recon_box"
|
||||
Executable
+205
@@ -0,0 +1,205 @@
|
||||
---
|
||||
# Linode C2 Deployment Playbook
|
||||
|
||||
- name: Deploy Linode C2 server
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
vars_files:
|
||||
- vars.yaml
|
||||
vars:
|
||||
# Default values for required variables
|
||||
linode_region: "{{ linode_region | default(region_choices | random) }}"
|
||||
plan: "{{ plan | default('g6-standard-2') }}"
|
||||
# Use static value to avoid recursive templating
|
||||
c2_image_static: "linode/kali"
|
||||
|
||||
# Generate random instance name if not provided
|
||||
c2_name: "{{ c2_name | default('node-' + 9999999999 | random | to_uuid | hash('md5') | truncate(8, True, '')) }}"
|
||||
|
||||
tasks:
|
||||
- name: Validate required Linode token
|
||||
assert:
|
||||
that:
|
||||
- linode_token is defined and linode_token != ""
|
||||
fail_msg: "Linode API token is required. Set linode_token in vars.yaml or via --linode-token."
|
||||
|
||||
# Replace these tasks at the beginning of the playbook
|
||||
- name: Set region for C2 server
|
||||
set_fact:
|
||||
c2_region_value: "{{ selected_region | default(linode_region, true) | default('us-east', true) }}"
|
||||
|
||||
- name: Display region selection information for debugging
|
||||
debug:
|
||||
msg:
|
||||
- "Selected Region: {{ selected_region | default('Not set') }}"
|
||||
- "Linode Region: {{ linode_region | default('Not set') }}"
|
||||
- "Using Region: {{ c2_region_value }}"
|
||||
when: debug | default(false) | bool
|
||||
|
||||
- name: Set random region only if absolutely no region specified
|
||||
set_fact:
|
||||
c2_region_value: "{{ region_choices | random }}"
|
||||
when: region_choices is defined and region_choices|length > 0 and not selected_region is defined and not linode_region is defined and not c2_region_value is defined
|
||||
|
||||
- name: Create C2 Linode instance
|
||||
block:
|
||||
- name: Try creating instance in specified region
|
||||
community.general.linode_v4:
|
||||
access_token: "{{ linode_token }}"
|
||||
label: "{{ c2_name }}"
|
||||
type: "{{ plan }}"
|
||||
region: "{{ c2_region_value }}"
|
||||
image: "linode/kali"
|
||||
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
|
||||
authorized_keys:
|
||||
- "{{ lookup('file', ssh_key_path) }}"
|
||||
state: present
|
||||
register: c2_instance
|
||||
rescue:
|
||||
- name: Log region restriction error
|
||||
debug:
|
||||
msg: "Region {{ c2_region_value }} is restricted. Trying fallback regions..."
|
||||
|
||||
- name: Set fallback regions (most reliable ones first)
|
||||
set_fact:
|
||||
fallback_regions:
|
||||
- "us-east"
|
||||
- "us-central"
|
||||
- "eu-west"
|
||||
- "us-west"
|
||||
|
||||
- name: Try fallback regions
|
||||
community.general.linode_v4:
|
||||
access_token: "{{ linode_token }}"
|
||||
label: "{{ c2_name }}"
|
||||
type: "{{ plan }}"
|
||||
region: "{{ item }}"
|
||||
image: "linode/kali"
|
||||
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
|
||||
authorized_keys:
|
||||
- "{{ lookup('file', ssh_key_path) }}"
|
||||
state: present
|
||||
register: c2_instance
|
||||
loop: "{{ fallback_regions }}"
|
||||
when: c2_instance is not defined or c2_instance.failed
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Update deployment region with successful fallback
|
||||
set_fact:
|
||||
c2_region_value: "{{ item }}"
|
||||
loop: "{{ fallback_regions }}"
|
||||
when: c2_instance.results is defined and c2_instance.results[ansible_loop.index0] is defined and not c2_instance.results[ansible_loop.index0].failed
|
||||
|
||||
- name: Fail if all regions are restricted
|
||||
fail:
|
||||
msg: "All attempted regions are restricted. Please try again later or contact Linode support."
|
||||
when: c2_instance.failed | default(true)
|
||||
|
||||
- name: Set c2_ip for later use
|
||||
block:
|
||||
- name: Extract instance info from direct creation
|
||||
set_fact:
|
||||
c2_ip: "{{ c2_instance.instance.ipv4[0] }}"
|
||||
c2_instance_id: "{{ c2_instance.instance.id }}"
|
||||
when: c2_instance.instance is defined
|
||||
|
||||
- name: Extract instance info from fallback creation
|
||||
set_fact:
|
||||
c2_ip: "{{ item.instance.ipv4[0] }}"
|
||||
c2_instance_id: "{{ item.instance.id }}"
|
||||
loop: "{{ c2_instance.results | default([]) }}"
|
||||
when: c2_instance.results is defined and item.instance is defined and not item.failed
|
||||
|
||||
- name: Display final C2 deployment region and IP
|
||||
debug:
|
||||
msg: "C2 server deployed successfully in region {{ c2_region_value }} with IP {{ c2_ip }}"
|
||||
|
||||
# Enhanced SSH wait task for Linode/c2.yml
|
||||
- name: Wait for C2 SSH to be available
|
||||
block:
|
||||
- name: Initial wait for port to be open
|
||||
wait_for:
|
||||
host: "{{ c2_instance.instance.ipv4[0] }}"
|
||||
port: 22
|
||||
delay: 60
|
||||
timeout: 180
|
||||
state: started
|
||||
|
||||
- name: Additional pause for SSH initialization
|
||||
pause:
|
||||
seconds: 60
|
||||
|
||||
- name: Test SSH connection
|
||||
command: >
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10
|
||||
-i {{ ssh_key_path | replace('.pub', '') }} root@{{ c2_instance.instance.ipv4[0] }} echo "SSH Ready"
|
||||
register: ssh_test
|
||||
retries: 5
|
||||
delay: 20
|
||||
until: ssh_test.rc == 0
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Fail if SSH test unsuccessful
|
||||
fail:
|
||||
msg: "Could not connect to C2 server via SSH after multiple attempts"
|
||||
when: ssh_test.rc != 0
|
||||
|
||||
- name: Add C2 to inventory
|
||||
add_host:
|
||||
name: "c2"
|
||||
groups: "c2servers"
|
||||
ansible_host: "{{ c2_ip }}"
|
||||
ansible_user: "root"
|
||||
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||
ansible_python_interpreter: "/usr/bin/python3" # Use remote system's Python
|
||||
|
||||
- name: Configure C2 server
|
||||
hosts: c2servers
|
||||
become: true
|
||||
gather_facts: true
|
||||
vars_files:
|
||||
- vars.yaml
|
||||
vars:
|
||||
redirector_ip: "{{ hostvars['localhost']['redirector_ip'] | default('127.0.0.1') }}"
|
||||
c2_subdomain: "{{ c2_subdomain | default('mail') }}"
|
||||
tasks:
|
||||
- name: Wait for apt to be available
|
||||
apt:
|
||||
update_cache: yes
|
||||
register: apt_result
|
||||
until: apt_result is success
|
||||
retries: 5
|
||||
delay: 10
|
||||
|
||||
- name: Disable root password authentication for SSH immediately
|
||||
lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^#?PasswordAuthentication'
|
||||
line: 'PasswordAuthentication no'
|
||||
state: present
|
||||
|
||||
- name: Restart SSH service to apply changes
|
||||
service:
|
||||
name: ssh
|
||||
state: restarted
|
||||
|
||||
- name: Include common tool installation tasks
|
||||
include_tasks: "../../common/tasks/install_tools.yml"
|
||||
|
||||
- name: Include common C2 configuration tasks
|
||||
include_tasks: "../../modules/c2/tasks/configure_c2.yml"
|
||||
|
||||
- name: Include common mail server configuration tasks
|
||||
include_tasks: "../../common/tasks/configure_mail.yml"
|
||||
|
||||
- name: Print deployment summary
|
||||
debug:
|
||||
msg:
|
||||
- "C2 Server Deployment Complete!"
|
||||
- "-----------------------------"
|
||||
- "C2 Server IP: {{ ansible_host }}"
|
||||
- "C2 Server Domain: {{ domain }}"
|
||||
- "GoPhish Admin Port: {{ gophish_admin_port }}"
|
||||
when: not disable_summary | default(false)
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
---
|
||||
# Linode/cleanup.yml
|
||||
- name: Clean up Linode infrastructure
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
vars_files:
|
||||
- vars.yaml
|
||||
vars:
|
||||
cleanup_redirector: "{{ (redirector_name is defined and redirector_name != '') | ternary(true, false) }}"
|
||||
cleanup_c2: "{{ (c2_name is defined and c2_name != '') | ternary(true, false) }}"
|
||||
cleanup_tracker: "{{ (tracker_name is defined and tracker_name != '') | ternary(true, false) }}"
|
||||
cleanup_attack_box: "{{ (attack_box_name is defined and attack_box_name != '') | ternary(true, false) }}"
|
||||
confirm_cleanup: false # Skip confirmation in automated teardown
|
||||
|
||||
tasks:
|
||||
- name: Validate required Linode token
|
||||
assert:
|
||||
that:
|
||||
- linode_token is defined and linode_token != ""
|
||||
fail_msg: "Linode API token is required. Set linode_token in vars.yaml or via --linode-token."
|
||||
|
||||
- name: Cleanup resources notice
|
||||
debug:
|
||||
msg: |
|
||||
===============================================
|
||||
CLEANUP OPERATION
|
||||
===============================================
|
||||
The following resources will be DELETED PERMANENTLY:
|
||||
{% if cleanup_redirector and redirector_name is defined %}
|
||||
- Redirector instance: {{ redirector_name }}
|
||||
{% endif %}
|
||||
{% if cleanup_c2 and c2_name is defined %}
|
||||
- C2 instance: {{ c2_name }}
|
||||
{% endif %}
|
||||
{% if cleanup_tracker and tracker_name is defined %}
|
||||
- Tracker instance: {{ tracker_name }}
|
||||
{% endif %}
|
||||
{% if cleanup_attack_box and attack_box_name is defined %}
|
||||
- Attack Box instance: {{ attack_box_name }}
|
||||
{% endif %}
|
||||
when: confirm_cleanup | bool
|
||||
|
||||
- name: Confirm cleanup operation
|
||||
pause:
|
||||
prompt: "\n>>> Type 'yes' to confirm deletion or press Ctrl+C to abort <<<"
|
||||
register: confirmation
|
||||
when: confirm_cleanup | bool
|
||||
|
||||
- name: Skip cleanup if not confirmed
|
||||
meta: end_play
|
||||
when: confirm_cleanup | bool and (confirmation.user_input | default('')) != 'yes'
|
||||
|
||||
- name: Delete redirector instance
|
||||
community.general.linode_v4:
|
||||
access_token: "{{ linode_token }}"
|
||||
label: "{{ redirector_name }}"
|
||||
state: absent
|
||||
when: cleanup_redirector and redirector_name is defined and redirector_name != ""
|
||||
register: redirector_deletion
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Delete C2 instance
|
||||
community.general.linode_v4:
|
||||
access_token: "{{ linode_token }}"
|
||||
label: "{{ c2_name }}"
|
||||
state: absent
|
||||
when: cleanup_c2 and c2_name is defined and c2_name != ""
|
||||
register: c2_deletion
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Delete tracker instance
|
||||
community.general.linode_v4:
|
||||
access_token: "{{ linode_token }}"
|
||||
label: "{{ tracker_name }}"
|
||||
state: absent
|
||||
when: cleanup_tracker and tracker_name is defined and tracker_name != ""
|
||||
register: tracker_deletion
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Delete attack box instance
|
||||
community.general.linode_v4:
|
||||
access_token: "{{ linode_token }}"
|
||||
label: "{{ attack_box_name }}"
|
||||
state: absent
|
||||
when: cleanup_attack_box and attack_box_name is defined and attack_box_name != ""
|
||||
register: attack_box_deletion
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Clean up deployment state file
|
||||
file:
|
||||
path: "{{ playbook_dir }}/../deployment_state_{{ deployment_id }}.json"
|
||||
state: absent
|
||||
ignore_errors: yes
|
||||
register: state_file_deletion
|
||||
|
||||
- name: Report state file cleanup
|
||||
debug:
|
||||
msg: "Deployment state file {{ 'deleted' if state_file_deletion.changed else 'not found' }}"
|
||||
when: state_file_deletion is defined
|
||||
|
||||
- name: Report cleanup status
|
||||
debug:
|
||||
msg: |
|
||||
Cleanup results:
|
||||
{% if redirector_deletion is defined and redirector_name is defined %}
|
||||
- Redirector {{ redirector_name }}: {{ redirector_deletion.changed | ternary('Deleted', 'Not found/Could not delete') }}
|
||||
{% endif %}
|
||||
{% if c2_deletion is defined and c2_name is defined %}
|
||||
- C2 {{ c2_name }}: {{ c2_deletion.changed | ternary('Deleted', 'Not found/Could not delete') }}
|
||||
{% endif %}
|
||||
{% if tracker_deletion is defined and tracker_name is defined %}
|
||||
- Tracker {{ tracker_name }}: {{ tracker_deletion.changed | ternary('Deleted', 'Not found/Could not delete') }}
|
||||
{% endif %}
|
||||
{% if attack_box_deletion is defined and attack_box_name is defined %}
|
||||
- Attack Box {{ attack_box_name }}: {{ attack_box_deletion.changed | ternary('Deleted', 'Not found/Could not delete') }}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
# Linode-specific phishing infrastructure tasks
|
||||
|
||||
- name: Create Linode instances for phishing
|
||||
uri:
|
||||
url: "https://api.linode.com/v4/linode/instances"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ linode_api_token }}"
|
||||
Content-Type: "application/json"
|
||||
body_format: json
|
||||
body:
|
||||
type: "{{ linode_instance_type | default('g6-nanode-1') }}"
|
||||
region: "{{ linode_region | default('us-east') }}"
|
||||
image: "{{ linode_image | default('linode/ubuntu22.04') }}"
|
||||
label: "{{ deployment_id }}-{{ item }}"
|
||||
root_pass: "{{ instance_password }}"
|
||||
authorized_keys:
|
||||
- "{{ ssh_public_key }}"
|
||||
tags:
|
||||
- "c2itall"
|
||||
- "phishing"
|
||||
- "{{ deployment_id }}"
|
||||
status_code: 200
|
||||
register: linode_instances
|
||||
loop: "{{ deployment_components }}"
|
||||
when: item in deployment_components
|
||||
|
||||
- name: Wait for instances to be running
|
||||
uri:
|
||||
url: "https://api.linode.com/v4/linode/instances/{{ item.json.id }}"
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "Bearer {{ linode_api_token }}"
|
||||
register: instance_status
|
||||
until: instance_status.json.status == "running"
|
||||
retries: 30
|
||||
delay: 10
|
||||
loop: "{{ linode_instances.results }}"
|
||||
when: linode_instances.results is defined
|
||||
|
||||
- name: Get instance details
|
||||
uri:
|
||||
url: "https://api.linode.com/v4/linode/instances/{{ item.json.id }}"
|
||||
method: GET
|
||||
headers:
|
||||
Authorization: "Bearer {{ linode_api_token }}"
|
||||
register: instance_details
|
||||
loop: "{{ linode_instances.results }}"
|
||||
when: linode_instances.results is defined
|
||||
|
||||
- name: Set instance facts
|
||||
set_fact:
|
||||
phishing_instances: >-
|
||||
{% set instances = [] %}
|
||||
{% for result in instance_details.results %}
|
||||
{% set instance = {
|
||||
'id': result.json.id,
|
||||
'label': result.json.label,
|
||||
'ipv4': result.json.ipv4[0],
|
||||
'region': result.json.region,
|
||||
'type': result.json.type,
|
||||
'status': result.json.status
|
||||
} %}
|
||||
{% set _ = instances.append(instance) %}
|
||||
{% endfor %}
|
||||
{{ instances }}
|
||||
when: instance_details.results is defined
|
||||
|
||||
- name: Wait for SSH connectivity
|
||||
wait_for:
|
||||
host: "{{ item.ipv4 }}"
|
||||
port: 22
|
||||
delay: 30
|
||||
timeout: 300
|
||||
loop: "{{ phishing_instances }}"
|
||||
when: phishing_instances is defined
|
||||
|
||||
- name: Update instance inventory
|
||||
add_host:
|
||||
name: "{{ item.ipv4 }}"
|
||||
groups: "phishing_{{ item.label.split('-')[-1] }}"
|
||||
ansible_host: "{{ item.ipv4 }}"
|
||||
ansible_user: root
|
||||
ansible_ssh_private_key_file: "{{ ssh_private_key_path }}"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||
instance_id: "{{ item.id }}"
|
||||
instance_label: "{{ item.label }}"
|
||||
provider: "linode"
|
||||
loop: "{{ phishing_instances }}"
|
||||
when: phishing_instances is defined
|
||||
|
||||
- name: Configure Gophish servers
|
||||
include_tasks: ../common/configure_gophish.yml
|
||||
when: "'gophish' in deployment_components"
|
||||
|
||||
- name: Configure MTA fronts
|
||||
include_tasks: ../common/configure_mta.yml
|
||||
when: "'mta_front' in deployment_components"
|
||||
|
||||
- name: Configure redirectors
|
||||
include_tasks: ../common/configure_redirector.yml
|
||||
when: "'redirector' in deployment_components"
|
||||
|
||||
- name: Configure web servers
|
||||
include_tasks: ../common/configure_webserver.yml
|
||||
when: "'webserver' in deployment_components"
|
||||
|
||||
- name: Display deployment summary
|
||||
debug:
|
||||
msg: |
|
||||
🎯 Linode Phishing Infrastructure Deployed
|
||||
{{ phishing_instances | length }} instances created
|
||||
Provider: Linode
|
||||
Deployment ID: {{ deployment_id }}
|
||||
Instances:
|
||||
{% for instance in phishing_instances %}
|
||||
- {{ instance.label }}: {{ instance.ipv4 }} ({{ instance.type }})
|
||||
{% endfor %}
|
||||
when: phishing_instances is defined
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
# Phishing infrastructure deployment playbook
|
||||
# This is a comprehensive playbook that handles all phishing components
|
||||
|
||||
- name: Deploy Phishing Infrastructure
|
||||
hosts: localhost
|
||||
gather_facts: true
|
||||
connection: local
|
||||
vars_files:
|
||||
- vars.yaml
|
||||
|
||||
tasks:
|
||||
- name: Display deployment information
|
||||
debug:
|
||||
msg: |
|
||||
Deploying phishing infrastructure
|
||||
Deployment ID: {{ deployment_id }}
|
||||
Provider: {{ provider }}
|
||||
Domain: {{ domain | default(phishing_domain) }}
|
||||
Components: {{ deployment_type }}
|
||||
|
||||
- name: Set deployment facts
|
||||
set_fact:
|
||||
deployment_timestamp: "{{ ansible_date_time.epoch }}"
|
||||
phishing_deployment_results: {}
|
||||
deployment_components: >-
|
||||
{% set components = [] %}
|
||||
{% if deployment_type in ['gophish_only', 'basic_phishing', 'advanced_phishing', 'full_phishing', 'fedramp_phishing'] %}
|
||||
{% set _ = components.append('gophish') %}
|
||||
{% endif %}
|
||||
{% if deployment_type in ['mta_front_only', 'basic_phishing', 'advanced_phishing', 'full_phishing', 'ephemeral_mta'] %}
|
||||
{% set _ = components.append('mta_front') %}
|
||||
{% endif %}
|
||||
{% if deployment_type in ['phishing_redirector_only', 'advanced_phishing', 'full_phishing'] %}
|
||||
{% set _ = components.append('redirector') %}
|
||||
{% endif %}
|
||||
{% if deployment_type in ['phishing_webserver_only', 'full_phishing', 'fedramp_phishing'] %}
|
||||
{% set _ = components.append('webserver') %}
|
||||
{% endif %}
|
||||
{{ components }}
|
||||
|
||||
- name: Include provider-specific tasks
|
||||
include_tasks: "{{ provider }}_phishing.yml"
|
||||
when: deployment_components | length > 0
|
||||
|
||||
- name: Ensure logs directory exists
|
||||
file:
|
||||
path: "../../logs"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Save deployment information
|
||||
template:
|
||||
src: phishing_deployment_info.j2
|
||||
dest: "{{ playbook_dir }}/logs/phishing_deployment_{{ deployment_id }}.json"
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Display success message
|
||||
debug:
|
||||
msg: |
|
||||
✅ Phishing infrastructure deployment completed
|
||||
Deployment ID: {{ deployment_id }}
|
||||
Components deployed: {{ deployment_components | join(', ') }}
|
||||
Check logs/phishing_deployment_{{ deployment_id }}.json for details
|
||||
Executable
+190
@@ -0,0 +1,190 @@
|
||||
---
|
||||
# Linode Redirector-only Deployment Playbook
|
||||
|
||||
- name: Deploy Linode redirector
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
vars_files:
|
||||
- vars.yaml
|
||||
vars:
|
||||
# Use constant values directly to avoid recursive template resolution
|
||||
redirector_image_static: "linode/debian12"
|
||||
|
||||
tasks:
|
||||
- name: Validate required Linode token
|
||||
assert:
|
||||
that:
|
||||
- linode_token is defined and linode_token != ""
|
||||
fail_msg: "Linode API token is required. Set linode_token in vars.yaml or via --linode-token."
|
||||
|
||||
# Update the region selection logic in Linode/redirector.yml
|
||||
- name: Set region for redirector
|
||||
set_fact:
|
||||
deployment_region: "{{ selected_region | default(linode_region, true) | default('us-east', true) }}"
|
||||
|
||||
- name: Display region selection for redirector
|
||||
debug:
|
||||
msg: "Using region for redirector: {{ deployment_region }}"
|
||||
when: debug | default(false) | bool
|
||||
|
||||
# Only use random region as a last resort
|
||||
- name: Set random region only if no region specified
|
||||
set_fact:
|
||||
deployment_region: "{{ region_choices | random }}"
|
||||
when: region_choices is defined and region_choices|length > 0 and not selected_region is defined and not linode_region is defined and not deployment_region is defined
|
||||
|
||||
- name: Create redirector Linode instance
|
||||
block:
|
||||
- name: Try creating instance in specified region
|
||||
community.general.linode_v4:
|
||||
access_token: "{{ linode_token }}"
|
||||
label: "{{ redirector_name }}"
|
||||
type: "{{ redirector_plan | default('g6-nanode-1') }}"
|
||||
region: "{{ deployment_region }}"
|
||||
image: "linode/debian12"
|
||||
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
|
||||
authorized_keys:
|
||||
- "{{ lookup('file', ssh_key_path) }}"
|
||||
state: present
|
||||
register: redirector_instance
|
||||
rescue:
|
||||
- name: Log region restriction error
|
||||
debug:
|
||||
msg: "Region {{ deployment_region }} is restricted. Trying fallback regions..."
|
||||
|
||||
- name: Set fallback regions (most reliable ones first)
|
||||
set_fact:
|
||||
fallback_regions:
|
||||
- "us-east"
|
||||
- "us-central"
|
||||
- "eu-west"
|
||||
- "us-west"
|
||||
|
||||
- name: Try fallback regions
|
||||
community.general.linode_v4:
|
||||
access_token: "{{ linode_token }}"
|
||||
label: "{{ redirector_name }}"
|
||||
type: "{{ redirector_plan | default('g6-nanode-1') }}"
|
||||
region: "{{ item }}"
|
||||
image: "linode/debian12"
|
||||
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
|
||||
authorized_keys:
|
||||
- "{{ lookup('file', ssh_key_path) }}"
|
||||
state: present
|
||||
register: redirector_instance
|
||||
loop: "{{ fallback_regions }}"
|
||||
when: redirector_instance is not defined or redirector_instance.failed
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Update deployment region with successful fallback
|
||||
set_fact:
|
||||
deployment_region: "{{ item }}"
|
||||
loop: "{{ fallback_regions }}"
|
||||
when: redirector_instance.results is defined and redirector_instance.results[ansible_loop.index0] is defined and not redirector_instance.results[ansible_loop.index0].failed
|
||||
|
||||
- name: Fail if all regions are restricted
|
||||
fail:
|
||||
msg: "All attempted regions are restricted. Please try again later or contact Linode support."
|
||||
when: redirector_instance.failed | default(true)
|
||||
|
||||
- name: Set redirector_ip for later use
|
||||
block:
|
||||
- name: Extract instance info from direct creation
|
||||
set_fact:
|
||||
redirector_ip: "{{ redirector_instance.instance.ipv4[0] }}"
|
||||
redirector_instance_id: "{{ redirector_instance.instance.id }}"
|
||||
when: redirector_instance.instance is defined
|
||||
|
||||
- name: Extract instance info from fallback creation
|
||||
set_fact:
|
||||
redirector_ip: "{{ item.instance.ipv4[0] }}"
|
||||
redirector_instance_id: "{{ item.instance.id }}"
|
||||
loop: "{{ redirector_instance.results | default([]) }}"
|
||||
when: redirector_instance.results is defined and item.instance is defined and not item.failed
|
||||
|
||||
- name: Display final deployment region and IP
|
||||
debug:
|
||||
msg: "Redirector deployed successfully in region {{ deployment_region }} with IP {{ redirector_ip }}"
|
||||
|
||||
# Enhanced SSH wait task with better retry mechanism
|
||||
- name: Wait for redirector SSH to be available
|
||||
block:
|
||||
- name: Initial wait for port to be open
|
||||
wait_for:
|
||||
host: "{{ redirector_instance.instance.ipv4[0] }}"
|
||||
port: 22
|
||||
delay: 60
|
||||
timeout: 180
|
||||
state: started
|
||||
|
||||
- name: Additional pause for SSH initialization
|
||||
pause:
|
||||
seconds: 60
|
||||
|
||||
- name: Test SSH connection
|
||||
command: >
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10
|
||||
-i {{ ssh_key_path | replace('.pub', '') }} root@{{ redirector_instance.instance.ipv4[0] }} echo "SSH Ready"
|
||||
register: ssh_test
|
||||
retries: 5
|
||||
delay: 20
|
||||
until: ssh_test.rc == 0
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Fail if SSH test unsuccessful
|
||||
fail:
|
||||
msg: "Could not connect to redirector via SSH after multiple attempts"
|
||||
when: ssh_test.rc != 0
|
||||
|
||||
- name: Add redirector to inventory
|
||||
add_host:
|
||||
name: "redirector"
|
||||
groups: "redirectors"
|
||||
ansible_host: "{{ redirector_ip }}"
|
||||
ansible_user: "root"
|
||||
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||
|
||||
- name: Configure redirector server
|
||||
hosts: redirectors
|
||||
become: true
|
||||
gather_facts: true
|
||||
vars_files:
|
||||
- vars.yaml
|
||||
vars:
|
||||
c2_ip: "{{ hostvars['localhost']['c2_ip'] | default('127.0.0.1') }}"
|
||||
redirector_subdomain: "{{ redirector_subdomain | default('cdn') }}"
|
||||
ansible_python_interpreter: auto # Explicitly add this line
|
||||
tasks:
|
||||
- name: Wait for apt to be available
|
||||
apt:
|
||||
update_cache: yes
|
||||
register: apt_result
|
||||
until: apt_result is success
|
||||
retries: 5
|
||||
delay: 10
|
||||
|
||||
- name: Disable root password authentication for SSH immediately
|
||||
lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
regexp: '^#?PasswordAuthentication'
|
||||
line: 'PasswordAuthentication no'
|
||||
state: present
|
||||
|
||||
- name: Restart SSH service to apply changes
|
||||
service:
|
||||
name: ssh
|
||||
state: restarted
|
||||
|
||||
- name: Include common redirector configuration tasks
|
||||
include_tasks: "../../modules/redirectors/tasks/configure_redirector.yml"
|
||||
|
||||
- name: Print deployment summary
|
||||
debug:
|
||||
msg:
|
||||
- "Redirector Deployment Complete!"
|
||||
- "-------------------------------"
|
||||
- "Redirector IP: {{ ansible_host }}"
|
||||
- "Redirector Domain: {{ redirector_subdomain }}.{{ domain }} (Update DNS A record)"
|
||||
when: not disable_summary | default(false)
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"deployment_id": "{{ deployment_id }}",
|
||||
"deployment_type": "{{ deployment_type }}",
|
||||
"provider": "{{ provider }}",
|
||||
"domain": "{{ phishing_domain | default(domain) }}",
|
||||
"timestamp": "{{ ansible_date_time.iso8601 }}",
|
||||
"components": {{ deployment_components | to_json }},
|
||||
"results": {{ phishing_deployment_results | default({}) | to_json }},
|
||||
"status": "completed"
|
||||
}
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
---
|
||||
# Linode Tracker Deployment Playbook
|
||||
|
||||
- name: Deploy Linode email tracking server
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
connection: local
|
||||
vars_files:
|
||||
- vars.yaml
|
||||
vars:
|
||||
# Default values - using minimalist settings
|
||||
linode_region: "{{ linode_region | default(region_choices | random) }}"
|
||||
plan: "{{ plan | default('g6-nanode-1') }}" # Use smallest plan for tracker
|
||||
tracker_image: "{{ image | default('linode/debian12') }}" # Use Debian instead of Kali
|
||||
|
||||
# Generate random instance name if not provided
|
||||
tracker_name: "{{ tracker_name | default('t-' + 9999999999 | random | to_uuid | hash('md5') | truncate(8, True, '')) }}"
|
||||
|
||||
tasks:
|
||||
- name: Validate required Linode token
|
||||
assert:
|
||||
that:
|
||||
- linode_token is defined and linode_token != ""
|
||||
fail_msg: "Linode API token is required. Set linode_token in vars.yaml."
|
||||
|
||||
- name: Set region for tracker
|
||||
set_fact:
|
||||
tracker_region_value: "{{ selected_region | default(region, true) | default(linode_region, true) | default('us-east', true) }}"
|
||||
when: region_choices is not defined or region_choices|length == 0
|
||||
|
||||
- name: Set random region for tracker
|
||||
set_fact:
|
||||
tracker_region_value: "{{ region_choices | random }}"
|
||||
when: region_choices is defined and region_choices|length > 0 and tracker_region_value is not defined
|
||||
|
||||
- name: Create tracker Linode instance
|
||||
community.general.linode_v4:
|
||||
access_token: "{{ linode_token }}"
|
||||
label: "{{ tracker_name }}"
|
||||
type: "{{ plan }}"
|
||||
region: "{{ tracker_region_value }}"
|
||||
image: "{{ image | default('linode/debian12') }}"
|
||||
root_pass: "{{ lookup('password', '/dev/null length=16') }}"
|
||||
authorized_keys:
|
||||
- "{{ lookup('file', ssh_key_path) }}"
|
||||
state: present
|
||||
register: tracker_instance
|
||||
|
||||
- name: Set tracker_ip for later use
|
||||
set_fact:
|
||||
tracker_ip: "{{ tracker_instance.instance.ipv4[0] }}"
|
||||
tracker_instance_id: "{{ tracker_instance.instance.id }}"
|
||||
|
||||
- name: Wait for tracker SSH to be available
|
||||
wait_for:
|
||||
host: "{{ tracker_ip }}"
|
||||
port: 22
|
||||
delay: 60
|
||||
timeout: 300
|
||||
state: started
|
||||
|
||||
- name: Add tracker to inventory
|
||||
add_host:
|
||||
name: "tracker"
|
||||
groups: "trackers"
|
||||
ansible_host: "{{ tracker_ip }}"
|
||||
ansible_user: "root"
|
||||
ansible_ssh_private_key_file: "{{ ssh_key_path | replace('.pub', '') }}"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||
|
||||
- name: Configure tracker server
|
||||
hosts: trackers
|
||||
become: true
|
||||
gather_facts: true
|
||||
vars_files:
|
||||
- vars.yaml
|
||||
vars:
|
||||
tracker_domain: "{{ tracker_domain | default('track.' + domain) }}"
|
||||
tasks:
|
||||
- name: Update and install minimal required packages
|
||||
apt:
|
||||
name:
|
||||
- nginx
|
||||
- certbot
|
||||
- python3-certbot-nginx
|
||||
- git
|
||||
- python3-pip
|
||||
- python3-venv
|
||||
update_cache: yes
|
||||
state: present
|
||||
|
||||
- name: Clone email tracker repository
|
||||
git:
|
||||
repo: https://github.com/Datalux/Osint-Tracker.git
|
||||
dest: /root/Tools/tracker
|
||||
version: master
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Install tracker dependencies
|
||||
pip:
|
||||
requirements: /root/Tools/tracker/requirements.txt
|
||||
virtualenv: /root/Tools/tracker/venv
|
||||
virtualenv_command: python3 -m venv
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Configure tracker settings
|
||||
template:
|
||||
src: "../../modules/tracker/templates/tracker-config.j2"
|
||||
dest: /root/Tools/tracker/config.py
|
||||
mode: '0644'
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Set up SSL if requested
|
||||
block:
|
||||
- name: Install Let's Encrypt certificate
|
||||
shell: |
|
||||
certbot --nginx -d {{ tracker_domain }} --non-interactive --agree-tos -m {{ tracker_email }}
|
||||
args:
|
||||
creates: /etc/letsencrypt/live/{{ tracker_domain }}/fullchain.pem
|
||||
when: tracker_setup_ssl | default(true) | bool
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Create systemd service for tracker
|
||||
template:
|
||||
src: "../../modules/tracker/templates/tracker.service.j2"
|
||||
dest: /etc/systemd/system/tracker.service
|
||||
mode: '0644'
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Update tracker service to use virtualenv
|
||||
blockinfile:
|
||||
path: /etc/systemd/system/tracker.service
|
||||
insertafter: "^\\[Service\\]"
|
||||
block: |
|
||||
Environment="PATH=/root/Tools/tracker/venv/bin:$PATH"
|
||||
ExecStart=/root/Tools/tracker/venv/bin/python /root/Tools/tracker/app.py
|
||||
|
||||
- name: Start and enable tracker service
|
||||
systemd:
|
||||
name: tracker
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Print deployment summary
|
||||
debug:
|
||||
msg:
|
||||
- "Email Tracker Deployment Complete!"
|
||||
- "------------------------------"
|
||||
- "Tracker IP: {{ ansible_host }}"
|
||||
- "Tracker Domain: {{ tracker_domain }} (Update DNS A record)"
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
# Linode provision tasks for one WEBRUNNER node
|
||||
# Called in a loop — loop_var: node_chunk
|
||||
# Requires: linode_token, ssh_key_name, webrunner_name, deployment_id,
|
||||
# linode_instance_type, linode_region, scanner_ip_log, results_dir
|
||||
|
||||
- name: Generate root password for {{ node_chunk.node_name }}
|
||||
set_fact:
|
||||
wr_root_pass: "{{ lookup('password', '/dev/null chars=ascii_letters,digits length=32') }}"
|
||||
|
||||
- name: Read public key
|
||||
slurp:
|
||||
src: "~/.ssh/{{ ssh_key_name }}.pub"
|
||||
register: wr_pubkey
|
||||
|
||||
- name: Register SSH key in Linode ({{ node_chunk.node_name }})
|
||||
uri:
|
||||
url: "https://api.linode.com/v4/profile/sshkeys"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ linode_token }}"
|
||||
Content-Type: "application/json"
|
||||
body_format: json
|
||||
body:
|
||||
label: "{{ webrunner_name }}"
|
||||
ssh_key: "{{ wr_pubkey.content | b64decode | trim }}"
|
||||
status_code: [200, 201]
|
||||
ignore_errors: true
|
||||
|
||||
- name: Create Linode instance {{ node_chunk.node_name }}
|
||||
uri:
|
||||
url: "https://api.linode.com/v4/linode/instances"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ linode_token }}"
|
||||
Content-Type: "application/json"
|
||||
body_format: json
|
||||
body:
|
||||
label: "{{ node_chunk.node_name }}"
|
||||
type: "{{ linode_instance_type | default('g6-standard-2') }}"
|
||||
region: "{{ linode_region | default('us-east') }}"
|
||||
image: "linode/debian12"
|
||||
root_pass: "{{ wr_root_pass }}"
|
||||
authorized_keys:
|
||||
- "{{ wr_pubkey.content | b64decode | trim }}"
|
||||
booted: true
|
||||
backups_enabled: false
|
||||
private_ip: false
|
||||
tags:
|
||||
- "webrunner"
|
||||
- "{{ webrunner_name }}"
|
||||
- "{{ deployment_id }}"
|
||||
status_code: [200, 201]
|
||||
register: wr_linode
|
||||
|
||||
- name: Extract node IP
|
||||
set_fact:
|
||||
wr_node_ip: "{{ wr_linode.json.ipv4[0] }}"
|
||||
|
||||
- name: Log scanner IP
|
||||
lineinfile:
|
||||
path: "{{ scanner_ip_log }}"
|
||||
line: "{{ node_chunk.node_name }}: {{ wr_node_ip }}"
|
||||
create: true
|
||||
|
||||
- name: Wait for SSH on {{ node_chunk.node_name }} ({{ wr_node_ip }})
|
||||
wait_for:
|
||||
host: "{{ wr_node_ip }}"
|
||||
port: 22
|
||||
delay: 30
|
||||
timeout: 300
|
||||
|
||||
- name: Add {{ node_chunk.node_name }} to inventory
|
||||
add_host:
|
||||
name: "{{ wr_node_ip }}"
|
||||
groups: webrunner_nodes
|
||||
ansible_host: "{{ wr_node_ip }}"
|
||||
ansible_user: root
|
||||
ansible_ssh_private_key_file: "~/.ssh/{{ ssh_key_name }}"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
||||
node_name: "{{ node_chunk.node_name }}"
|
||||
node_cidrs: "{{ node_chunk.cidrs }}"
|
||||
node_ip_count: "{{ node_chunk.ip_count }}"
|
||||
node_idx: "{{ node_chunk.idx }}"
|
||||
linode_instance_id: "{{ wr_linode.json.id }}"
|
||||
provider: linode
|
||||
|
||||
- name: Show {{ node_chunk.node_name }} ready
|
||||
debug:
|
||||
msg: "Linode node ready: {{ node_chunk.node_name }} @ {{ wr_node_ip }} ({{ node_chunk.ip_count | int | string }} IPs)"
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
# Linode provision tasks for one WEBRUNNER node
|
||||
# Called in a loop — loop_var: node_chunk
|
||||
# Requires: linode_token, ssh_key_name, webrunner_name, deployment_id,
|
||||
# linode_instance_type, linode_region, scanner_ip_log, results_dir
|
||||
|
||||
- name: Generate root password for {{ node_chunk.node_name }}
|
||||
set_fact:
|
||||
wr_root_pass: "{{ lookup('password', '/dev/null chars=ascii_letters,digits length=32') }}"
|
||||
|
||||
- name: Read public key
|
||||
slurp:
|
||||
src: "~/.ssh/{{ ssh_key_name }}.pub"
|
||||
register: wr_pubkey
|
||||
|
||||
- name: Register SSH key in Linode ({{ node_chunk.node_name }})
|
||||
uri:
|
||||
url: "https://api.linode.com/v4/profile/sshkeys"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ linode_token }}"
|
||||
Content-Type: "application/json"
|
||||
body_format: json
|
||||
body:
|
||||
label: "{{ webrunner_name }}"
|
||||
ssh_key: "{{ wr_pubkey.content | b64decode | trim }}"
|
||||
status_code: [200, 201]
|
||||
ignore_errors: true
|
||||
|
||||
- name: Create Linode instance {{ node_chunk.node_name }}
|
||||
uri:
|
||||
url: "https://api.linode.com/v4/linode/instances"
|
||||
method: POST
|
||||
headers:
|
||||
Authorization: "Bearer {{ linode_token }}"
|
||||
Content-Type: "application/json"
|
||||
body_format: json
|
||||
body:
|
||||
label: "{{ node_chunk.node_name }}"
|
||||
type: "{{ linode_instance_type | default('g6-standard-2') }}"
|
||||
region: "{{ linode_region | default('us-east') }}"
|
||||
image: "linode/debian12"
|
||||
root_pass: "{{ wr_root_pass }}"
|
||||
authorized_keys:
|
||||
- "{{ wr_pubkey.content | b64decode | trim }}"
|
||||
booted: true
|
||||
backups_enabled: false
|
||||
private_ip: false
|
||||
tags:
|
||||
- "webrunner"
|
||||
- "{{ webrunner_name }}"
|
||||
- "{{ deployment_id }}"
|
||||
status_code: [200, 201]
|
||||
register: wr_linode
|
||||
|
||||
- name: Extract node IP
|
||||
set_fact:
|
||||
wr_node_ip: "{{ wr_linode.json.ipv4[0] }}"
|
||||
|
||||
- name: Log scanner IP
|
||||
lineinfile:
|
||||
path: "{{ scanner_ip_log }}"
|
||||
line: "{{ node_chunk.node_name }}: {{ wr_node_ip }}"
|
||||
create: true
|
||||
|
||||
- name: Wait for SSH on {{ node_chunk.node_name }} ({{ wr_node_ip }})
|
||||
wait_for:
|
||||
host: "{{ wr_node_ip }}"
|
||||
port: 22
|
||||
delay: 30
|
||||
timeout: 300
|
||||
|
||||
- name: Add {{ node_chunk.node_name }} to inventory
|
||||
add_host:
|
||||
name: "{{ wr_node_ip }}"
|
||||
groups: webrunner_nodes
|
||||
ansible_host: "{{ wr_node_ip }}"
|
||||
ansible_user: root
|
||||
ansible_ssh_private_key_file: "~/.ssh/{{ ssh_key_name }}"
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
||||
node_name: "{{ node_chunk.node_name }}"
|
||||
node_cidrs: "{{ node_chunk.cidrs }}"
|
||||
node_ip_count: "{{ node_chunk.ip_count }}"
|
||||
node_idx: "{{ node_chunk.idx }}"
|
||||
linode_instance_id: "{{ wr_linode.json.id }}"
|
||||
provider: linode
|
||||
|
||||
- name: Show {{ node_chunk.node_name }} ready
|
||||
debug:
|
||||
msg: "Linode node ready: {{ node_chunk.node_name }} @ {{ wr_node_ip }} ({{ node_chunk.ip_count | int | string }} IPs)"
|
||||
Reference in New Issue
Block a user