├── tasks
├── main.yml
├── letsencrypt.yml
└── apache.yml
├── handlers
└── main.yaml
├── templates
├── apache_vhost.conf.j2
└── apache_sslvhost.conf.j2
└── README.md
/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - include: "apache.yml"
3 | - include: "letsencrypt.yml"
--------------------------------------------------------------------------------
/handlers/main.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: restart apache2
3 | service: name=apache2 state=restarted
4 |
--------------------------------------------------------------------------------
/tasks/letsencrypt.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install letsencrypt
3 | apt: name=letsencrypt update_cache=yes state=latest
4 |
5 | - name: add repository for ubuntu
6 | apt_repository:
7 | repo: 'ppa:certbot/certbot'
8 | state: present
9 | when: ansible_distribution == 'Ubuntu'
10 |
11 | - name: Install python-certbot-apache
12 | apt: name=python-certbot-apache update_cache=yes state=latest
13 |
14 | - name: run letsencrypt
15 | command: certbot --installer apache --authenticator standalone --pre-hook "service apache2 stop" --post-hook "service apache2 start" --agree-tos --email {{le_email}} --expand -n -q -d {{ vhosts|map(attribute='servername')| join(',') |quote}}
16 | ignore_errors: yes
17 |
18 |
--------------------------------------------------------------------------------
/tasks/apache.yml:
--------------------------------------------------------------------------------
1 |
2 | ---
3 |
4 | - name: Install Apache web server
5 | apt: name=apache2 update_cache=yes state=latest
6 |
7 | - name: create https virtual host files, one per servername
8 | template: src=apache_sslvhost.conf.j2 dest=/etc/apache2/sites-available/rtssl_{{ item.servername }}.conf
9 | with_items: "{{ vhosts }}"
10 |
11 | - name: create http virtual host files, one per servername
12 | template: src=apache_vhost.conf.j2 dest=/etc/apache2/sites-available/rt_{{ item.servername }}.conf
13 | with_items: "{{ vhosts }}"
14 |
15 |
16 |
17 |
18 | - name: Install php
19 | apt: name=php update_cache=yes state=latest
20 | ignore_errors: true
21 |
22 |
23 | - name: Install mod php
24 | apt: name=libapache2-mod-php update_cache=yes state=latest
25 | ignore_errors: true
26 |
27 |
28 |
29 |
30 | - apache2_module: name=headers state=present
31 | - apache2_module: name=rewrite state=present
32 | - apache2_module: name=proxy state=present
33 | - apache2_module: name=ssl state=present
34 | - apache2_module: name=proxy_http state=present
35 | - apache2_module: name=cache state=absent
36 | - apache2_module: name=php7.0 state=present
37 | - apache2_module: name=deflate state=absent force=yes
38 |
39 |
40 | - name: copy hop folder
41 | copy:
42 | src: "{{ hop_dir }}/"
43 | dest: /var/www/html/
44 | when: hop_dir is defined
45 |
46 | - name: disable shite sites
47 | command: a2dissite *
48 |
49 |
50 | - name: enable the http sites
51 | command: a2ensite rt_{{ item.servername }}
52 | with_items: "{{ vhosts }}"
53 | notify:
54 | - restart apache2
55 |
56 | - name: enable the https sites
57 | command: a2ensite rtssl_{{ item.servername }}
58 | with_items: "{{ vhosts }}"
59 | notify:
60 | - restart apache2
61 |
62 |
63 | - service: name=apache2 state=restarted
64 |
65 |
66 |
--------------------------------------------------------------------------------
/templates/apache_vhost.conf.j2:
--------------------------------------------------------------------------------
1 |
2 | RewriteEngine On
3 |
4 |
5 | Options Indexes FollowSymLinks
6 | AllowOverride All
7 | Require all granted
8 |
9 |
10 | ProxyPreserveHost On
11 |
12 | ServerName {{ item.servername }}
13 | DocumentRoot /var/www/html
14 | # The ServerName directive sets the request scheme, hostname and port that
15 | # the server uses to identify itself. This is used when creating
16 | # redirection URLs. In the context of virtual hosts, the ServerName
17 | # specifies what hostname must appear in the request's Host: header to
18 | # match this virtual host. For the default virtual host (this file) this
19 | # value is not decisive as it is used as a last resort host regardless.
20 | # However, you must set it for any further virtual host explicitly.
21 | #ServerName www.example.com
22 |
23 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
24 | # error, crit, alert, emerg.
25 | # It is also possible to configure the loglevel for particular
26 | # modules, e.g.
27 | #LogLevel info ssl:warn
28 | ErrorLog ${APACHE_LOG_DIR}/error.log
29 | CustomLog ${APACHE_LOG_DIR}/access.log combined
30 | # For most configuration files from conf-available/, which are
31 | # enabled or disabled at a global level, it is possible to
32 | # include a line for only one particular virtual host. For example the
33 | # following line enables the CGI configuration for this host only
34 | # after it has been globally disabled with "a2disconf".
35 | #Include conf-available/serve-cgi-bin.conf
36 |
37 |
38 | {% if item.config_files is defined %}
39 | {% for config_file in item.config_files %}
40 | {{ lookup('file', config_file) }}
41 | {% endfor %}
42 | {% endif %}
43 |
44 | {% if item.pre_configs is defined %}
45 | {% for config in item.pre_configs %}
46 | {{ config}}
47 | {% endfor %}
48 | {% endif %}
49 |
50 |
51 | {% if item.c2filters is defined %}
52 | {% for c in item.c2filters %}
53 | RewriteCond %{REQUEST_URI} {{ c.rewritefilter }}
54 | RewriteRule ^.*$ https://{{ c.host }}%{REQUEST_URI} [P,NE]
55 | {% endfor %}
56 | {% endif %}
57 |
58 | {% if item.configs is defined %}
59 | {% for config in item.configs %}
60 | {{ config}}
61 | {% endfor %}
62 | {% endif %}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/templates/apache_sslvhost.conf.j2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Options Indexes FollowSymLinks
7 | AllowOverride All
8 | Require all granted
9 |
10 |
11 | ProxyPreserveHost On
12 | RewriteEngine On
13 |
14 | SSLEngine On
15 | SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
16 | SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
17 |
18 |
19 | SSLProxyEngine On
20 | SSLProxyVerify none
21 | SSLProxyCheckPeerCN off
22 | SSLProxyCheckPeerName off
23 | SSLProxyCheckPeerExpire off
24 | ServerName {{ item.servername }}
25 | DocumentRoot /var/www/html
26 | # The ServerName directive sets the request scheme, hostname and port that
27 | # the server uses to identify itself. This is used when creating
28 | # redirection URLs. In the context of virtual hosts, the ServerName
29 | # specifies what hostname must appear in the request's Host: header to
30 | # match this virtual host. For the default virtual host (this file) this
31 | # value is not decisive as it is used as a last resort host regardless.
32 | # However, you must set it for any further virtual host explicitly.
33 | #ServerName www.example.com
34 |
35 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
36 | # error, crit, alert, emerg.
37 | # It is also possible to configure the loglevel for particular
38 | # modules, e.g.
39 | #LogLevel info ssl:warn
40 | ErrorLog ${APACHE_LOG_DIR}/error.log
41 | CustomLog ${APACHE_LOG_DIR}/access.log combined
42 | # For most configuration files from conf-available/, which are
43 | # enabled or disabled at a global level, it is possible to
44 | # include a line for only one particular virtual host. For example the
45 | # following line enables the CGI configuration for this host only
46 | # after it has been globally disabled with "a2disconf".
47 | #Include conf-available/serve-cgi-bin.conf
48 |
49 |
50 | {% if item.config_files is defined %}
51 | {% for config_file in item.config_files %}
52 | {{ lookup('file', config_file) }}
53 | {% endfor %}
54 | {% endif %}
55 |
56 |
57 | {% if item.pre_configs is defined %}
58 | {% for config in item.pre_configs %}
59 | {{ config}}
60 | {% endfor %}
61 | {% endif %}
62 |
63 | {% if item.c2filters is defined %}
64 | {% for c in item.c2filters %}
65 | RewriteCond %{REQUEST_URI} {{ c.rewritefilter }}
66 | RewriteRule ^.*$ https://{{ c.host }}%{REQUEST_URI} [P,NE]
67 | {% endfor %}
68 | {% endif %}
69 |
70 | {% if item.configs is defined %}
71 | {% for config in item.configs %}
72 | {{ config}}
73 | {% endfor %}
74 | {% endif %}
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Ansible role that allows for quickly deploying a redirector to an existing server with mod_rewrite proxy rules,
2 |
3 | Supports Debian and Ubuntu, tested in Digital ocean and Azure
4 |
5 | See threat.tevora.com/automating-redirector-deployment-with-ansible for a blog walking through redirectors, ansible, and a deep dive on this role
6 |
7 | To get started, clone this repo, install ansible, and place this repo in your roles folder. See sample playbook below for an example of how to build your redirector instance config
8 |
9 | **Instance Playbook Sample provision_redirector_example.yml**
10 | ```language-yaml
11 | - hosts: EnigmaticEmu
12 | gather_facts: False
13 | user: root
14 | pre_tasks:
15 | - name: Install python for Ansible
16 | raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
17 | changed_when: False
18 | - setup: # aka gather_facts
19 | tasks:
20 | - include_role:
21 | name: redirectors
22 | vars:
23 | le_email: 'threat@tevora.com'
24 | hop_dir: hops/empire_hop
25 | vhosts: [
26 | {
27 | servername: 'fakeamazon.com',
28 | http_port: 80,
29 | https_port: 443,
30 | c2filters: [
31 | {
32 | rewritefilter: '^/orders/track/?$',
33 | host: '123.124.125.126'
34 | }
35 | ],
36 | configs: [
37 | 'RewriteRule !\.php$ https://www.amazon.com/%{REQUEST_URI} [L,R=302]'
38 | ]
39 | },
40 | {
41 | servername: 'fakegoogle.com',
42 | http_port: 80,
43 | https_port: 443,
44 | config_files: [
45 | "redirectors.txt",
46 | "apache_tweaks.conf"
47 | ],
48 | }
49 | ]
50 | ```
51 |
52 | Breakdown of the example:
53 |
54 | * `- hosts: EnigmaticEmu`
55 | This specifies the hosts the playbook will execute on.
56 | For this playbook to execute correclty, an inventory file must be used with the hostname or ip of engigmatic emu defined
57 |
58 | * `gather_facts: False`
59 | The ansible gather facts task breaks on newer versions of ubuntu with no python 2 installed
60 | we disable gather facts, so we can make sure to install python 2 first. This is just some compatability bootstrap stuff
61 |
62 | * ` user: root`
63 | this specifies what user to logon to the remote server with, ansible performs all its configurations over ssh
64 | Change this to whatever user you want to use
65 | *if you need to sudo, may need to add `become: true` and call ansible-playbook with `--ask-become-pass`
66 |
67 | * `pre_tasks:`
68 | * these lines install python 2 if it is not there and run gather facts
69 | ` tasks: ` Now we've reached the good part, our playbook tasks!
70 |
71 | * `- include_role:`
72 | as you may imagine, this specfies a role to include, and the next line `name: redirectors` specifies to include our redirectors role
73 |
74 | * `vars:`
75 | here is where we do the meat of our config
76 | Our server contains three vars,le_email, hop_dir, and vhosts
77 | Le email is used to specify the email address we use for lets_encrypt, be sure to use one you control
78 | remember the hop_dir we used in the copy task of our role? Here is where it gets defined! The hop dir should located in ./files/ relative to the location of the playbook itself
79 |
80 | * `hosts: [...]`
81 | our vhosts variable is a list of vhost config dictionaires. This allows us to setup multiple vhosts with different domains and C2 profiles per server.
82 | Remember that this variable is a list, as it will be important in how we template our config files and other ways we use these variables in our redirectors role
83 |
84 | * `servername: 'fakeamazon.com'`
85 | this is the hostname of our server, and you MUST have a dns record for this hostname pointing at the IP of the server.
86 |
87 | * `http_port` and `https_port` are pretty self explanatory. Choose what ports http and https will listen on
88 |
89 | * notice before we go on we have multiple ways to define mod rewrite rules in the config. This is largely due to experimentation, and plans to integrate this playbook with a python API
90 | * `c2filters: [...]`
91 | a list of c2filter dicts
92 | Any request whose URI matches `rewritefilter` will proxy the connection to `host`
93 |
94 | * `configs[...]`
95 | a list of config strings
96 | Each string will be added to the file.
97 |
98 | * `config_files[...]` filename or path
99 | list of filenames or paths. The content of each file will be added to aache
100 | usefull for leveraging mod_rewrite automation tool output such as @Inspired-Secs awesome tool: https://blog.inspired-sec.com/archive/2017/04/17/Mod-Rewrite-Automatic-Setup.html
101 |
102 | We formatted this config mostly in JSON (YAML is a superset of JSON) but you can format it however you like as long as it matches up.
103 |
104 | Notice in the config how there are multiple vhosts, and each one can use one or more methods of specifying how it is injecting into the configuration templates
105 |
106 | We create one configuration file per vhost is that Letsencrypt, specifically the certbot-apache component, does not support more than one vhost per config file. Because of this we will be provisioning multiple configuration files to the server.
107 |
108 | **Running the Playbook**
109 |
110 | To run the role. Create your playbook in the form of the example we covered and run `ansible-playbook -i `. Ensure that this roles is in the roles directory in the same path of your playbook, and your hop and/or config files are placed correctly. Your directory layout should look like:
111 | ```
112 | ├── my_playbook.yml
113 | ├── files
114 | │ └── empire_hop
115 | │ └── news
116 | │ └── login.php
117 | └── roles
118 | ├── redirectors
119 | │ ├── files
120 | │ ├── handlers
121 | │ │ └── main.yaml
122 | │ ├── meta
123 | │ ├── tasks
124 | │ │ ├── apache.yml
125 | │ │ ├── letsencrypt.yml
126 | │ │ └── main.yml
127 | │ ├── templates
128 | │ │ ├── apache_sslvhost.conf.j2
129 | │ │ └── apache_vhost.conf.j2
130 | │ └── vars
131 | ```
132 |
--------------------------------------------------------------------------------