Skip to content

Commit 36f08d1

Browse files
Merge pull request #11 from ait-testbed/install_as_api_server
Install as api server
2 parents e4e89d6 + 329666f commit 36f08d1

File tree

13 files changed

+329
-46
lines changed

13 files changed

+329
-46
lines changed

README.md

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,63 @@ It is further possible to roll out playbooks.
66

77
## Requirements
88

9-
- Debian or Ubuntu
9+
- Debian 13
10+
11+
- Ubuntu 24.04
12+
13+
- Kali Linux (Rolling)
14+
15+
16+
Note on gRPC: To ensure high performance and fast deployment, pre-compiled Python wheels (.whl) for grpc are automatically fetched by the role from our internal image server for the distributions listed above. For other operating systems, the role will fallback to compiling gRPC locally, which may significantly increase the initial setup time.
1017

1118
(Currently there are no packages defined for RedHat distributions)
1219

1320
## Role Variables
1421

1522
| Variable name | Type | Default | Description |
1623
| ------------------------------ | ------------ | ----------------------------------------- | -------------------------------------------------------- |
17-
| attackmate_url | url | https://github.com/ait-aecid/attackmate.git | Official attackmate repository |
18-
| attackmate_version | version-str | main | Version/Branch of the Git-Repository in attackmate_url |
19-
| attackmate_shared_dir | path | /usr/local/share | Installation path |
20-
| attackmate_dest | path | `{{ attackmate_shared_dir }}/attackmate` | Installation path of the attackmate repository |
21-
| attackmate_sliverfix | bool | True | [Install sliver-fix](https://aeciddocs.ait.ac.at/attackmate/development/installation/sliverfix.html#sliver-fix) |
22-
| attackmate_grpc_dest | path | `{{ attackmate_shared_dir }}/grpc` | Temporary install grpc to this path if sliverfix is enabled |
23-
| attackmate_bindir | path | /usr/local/bin | Installpath for the tmux-wrapper |
24-
| attackmate_tmux | bool | True | Deploy tmux-wrapper |
25-
| attackmate_tmux_session | str | attackmate | Use this existing session-name for the tmux-wrapper |
26-
| attackmate_tmux_window | str | attackmate | The name of the tmux-window for attackmate |
27-
| attackmate_config_dir | path | /etc/attackmate | Path to the config-directory |
28-
| attackmate_playbook_path | path | `{{ attackmate_config_dir }}/playbooks` | Path to the playbooks-directory |
29-
| attackmate_playbooks | list of playbook-templates(j2) | `[]` | List of playbooks to deploy |
30-
| attackmate_config_tpl | str | attackmate.yml.j2 | Name of the config-template(jinja) |
31-
| attackmate_sliver_config | path | **None** | Path to the generated sliver-config. (only needed for sliver-commands) |
32-
| attackmate_msf_server | hostname | **None** | Hostname of the Metasploit rpcd. (only needed for msf-commands) |
33-
| attackmate_msf_passwd | password | **None** | Password for the Metasploit rpcd. (only needed for msf-commands) |
34-
| attackmate_playwright | bool | True | Whether to install Playwright and its dependencies |
35-
| command_delay | float | **None** | delay in seconds before commands for the CommandConfig |
36-
| attackmate_remote_config | dict | {} | Optional map of named remote AttackMate connections. Each entry requires url, username, password, and optionally cafile. If empty, no remote_config section is written to the config file. |
24+
| attackmate_url | url | https://github.com/ait-aecid/attackmate.git | Official attackmate repository |
25+
| attackmate_version | version-str | main | Version/Branch of the Git-Repository in attackmate_url |
26+
| attackmate_shared_dir | path | /usr/local/share | Installation path |
27+
| attackmate_dest | path | `{{ attackmate_shared_dir }}/attackmate` | Installation path of the attackmate repository |
28+
| attackmate_sliverfix | bool | True | [Install sliver-fix](https://aeciddocs.ait.ac.at/attackmate/development/installation/sliverfix.html#sliver-fix) |
29+
| attackmate_grpc_dest | path | `{{ attackmate_shared_dir }}/grpc` | Temporary install grpc to this path if sliverfix is enabled |
30+
| attackmate_bindir | path | /usr/local/bin | Installpath for the tmux-wrapper |
31+
| attackmate_tmux | bool | True | Deploy tmux-wrapper |
32+
| attackmate_tmux_session | str | attackmate | Use this existing session-name for the tmux-wrapper |
33+
| attackmate_tmux_window | str | attackmate | The name of the tmux-window for attackmate |
34+
| attackmate_config_dir | path | /etc/attackmate | Path to the config-directory |
35+
| attackmate_playbook_path | path | `{{ attackmate_config_dir }}/playbooks` | Path to the playbooks-directory |
36+
| attackmate_playbooks | list of playbook-templates(j2) | `[]` | List of playbooks to deploy |
37+
| attackmate_config_tpl | str | attackmate.yml.j2 | Name of the config-template(jinja) |
38+
| attackmate_sliver_config | path | **None** | Path to the generated sliver-config. (only needed for sliver-commands) |
39+
| attackmate_msf_server | hostname | **None** | Hostname of the Metasploit rpcd. (only needed for msf-commands) |
40+
| attackmate_msf_passwd | password | **None** | Password for the Metasploit rpcd. (only needed for msf-commands) |
41+
| attackmate_playwright | bool | True | Whether to install Playwright and its dependencies |
42+
| command_delay | float | **None** | delay in seconds before commands for the CommandConfig |
43+
| attackmate_remote_config | dict | {} | Optional map of named remote AttackMate connections. Each entry requires url, username, password, and optionally cafile. If empty, no remote_config section is written to the config file.|
44+
45+
## Additional role Variables for installation as Api server
46+
| Variable name | Type | Default | Description |
47+
| ------------------------------ | ------------ | ----------------------------------------- | -------------------------------------------------------- |
48+
| attackmate_api_server | bool | False | Install the attackmate-api-server |
49+
| attackmate_api_server_url | url | https://github.com/ait-testbed/attackmate-api-server.git | Repository URL for the api server |
50+
| attackmate_api_server_version | version-str | main | Version/Branch of the api server repository |
51+
| attackmate_api_server_dest | path | {{ attackmate_shared_dir }}/attackmate-api-server | Installation path of the api server |
52+
| attackmate_api_bin_path | path | /usr/local/bin/attackmate-api | Installation path for the attackmate-api executable |
53+
| attackmate_api_service_path | path | /etc/systemd/system/attackmate-api.service | Path for the systemd service unit file |
54+
| attackmate_api_log_dir | path | /var/log/attackmate-api | Directory for API server log files |
55+
| attackmate_api_logs_to_disk | bool | False | Whether to write playbook logs to disk |
56+
| attackmate_ssl_key_path | path | /etc/ssl/private/attackmate.key | Path for the generated RSA private key |
57+
| attackmate_ssl_cert_path | path | /etc/ssl/certs/attackmate.pem | Path for the generated self-signed certificate |
58+
| attackmate_api_plain_ users | dict | {} | Map of username to plaintext password used to generate argon2 hashes at deploy time. Format: {"username": "password", ...}. Leave empty to skip user generation. Never commit plaintext passwords! |
59+
60+
61+
> [!WARNING]
62+
> `attackmate_api_plain_users` contains plaintext passwords and must **never** be committed to version control.
63+
> Only define this variable locally on the machine you are running the playbook from, either by passing it via
64+
> `--extra-vars` at runtime or in a local vars file that is excluded from your repository via `.gitignore`.
65+
> The hashing and deployment happen in memory only — the plaintext passwords are never written to the target host.
3766
3867
## Example Playbook
3968

@@ -66,6 +95,30 @@ This role installs to executables:
6695
* **/usr/local/bin/attackm8**: a wrapper for attackmate that uses the virtual environment
6796
* **/usr/local/bin/attackmate-tmux**: a wrapper that executes attackmate in a tmux-session
6897
98+
## Installing as API Server
99+
100+
AttackMate can optionally be installed together with the [AttackMate API Server](https://github.com/ait-testbed/attackmate-api-server),
101+
which exposes AttackMate's functionality via a REST API and allows remote instances to be controlled over the network.
102+
The API server is installed into the same virtual environment as AttackMate, since it depends on it.
103+
104+
To enable the API server, set `attackmate_api_server: True` in your playbook:
105+
```yaml
106+
- name: Install attackmate with API server
107+
become: true
108+
hosts: localhost
109+
roles:
110+
- role: attackmate
111+
vars:
112+
attackmate_api_server: True
113+
attackmate_api_plain_users:
114+
admin: "securepassword"
115+
116+
```
117+
118+
installs to executables:
119+
120+
* **/usr/local/bin/attackmate-api-server**: symlink to the attackmate-api-server executable in the virtual environment
121+
69122
## Role testing with molecule
70123

71124
### Role testing locally

defaults/main.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,22 @@ distribution_lower: "{{ ansible_distribution | lower }}"
5151
# username: user
5252
# password: anotherpassword
5353
# cafile: "/path/to/another_cert.pem"
54-
attackmate_remote_config: {}
54+
55+
# If no Remote AttackMate connections configured set to empty dict
56+
attackmate_remote_config: {}
57+
58+
# Installation as api server
59+
attackmate_api_server: False
60+
attackmate_api_server_url: "https://github.com/ait-testbed/attackmate-api-server.git"
61+
attackmate_api_server_version: "main"
62+
attackmate_api_server_dest: "{{ attackmate_shared_dir }}/attackmate-api-server"
63+
64+
attackmate_api_logs_to_disk: False
65+
attackmate_api_log_dir: "/var/log/attackmate-api"
66+
67+
attackmate_api_bin_path: "/usr/local/bin/attackmate-api"
68+
attackmate_api_service_path: "/etc/systemd/system/attackmate-api.service"
69+
attackmate_ssl_key_path: "/etc/ssl/private/attackmate.key"
70+
attackmate_ssl_cert_path: "/etc/ssl/certs/attackmate.pem"
71+
attackmate_api_server_port: 8445
72+
attackmate_api_plain_users: {}

handlers/main.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- name: Restart attackmate-api
2+
become: true
3+
ansible.builtin.systemd:
4+
name: attackmate-api
5+
state: restarted
6+
daemon_reload: yes

molecule/default/molecule.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,10 @@ platforms:
99
image: kalilinux/kali-rolling # Or a specific tagged version if preferred
1010
ansible_user: root
1111
pre_build_image: true # Pulls image beforehand
12-
- name: instance-ubuntu2204
13-
image: ubuntu:22.04
14-
ansible_user: root
15-
pre_build_image: true
1612
- name: instance-ubuntu2404
1713
image: ubuntu:24.04
1814
ansible_user: root
1915
pre_build_image: true
20-
- name: instance-debian12
21-
image: debian:12
22-
ansible_user: root
23-
pre_build_image: true
2416
- name: instance-debian13
2517
image: debian:13
2618
ansible_user: root

tasks/api_server.yml

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
- name: Create dedicated API service user
2+
become: true
3+
ansible.builtin.user:
4+
name: attackmate-api
5+
system: yes
6+
shell: /usr/sbin/nologin
7+
create_home: no
8+
9+
# --- Git checkout ---
10+
11+
- name: Git checkout attackmate-api-server
12+
ansible.builtin.git:
13+
repo: "{{ attackmate_api_server_url }}"
14+
dest: "{{ attackmate_api_server_dest }}"
15+
version: "{{ attackmate_api_server_version }}"
16+
register: attackmate_api_git
17+
tags:
18+
- molecule-idempotence-notest
19+
20+
# --- Installation ---
21+
22+
- name: Install attackmate-api-server with uv
23+
become: true
24+
ansible.builtin.shell:
25+
chdir: "{{ attackmate_api_server_dest }}"
26+
cmd: "uv pip install --python {{ attackmate_dest }}/.venv/bin/python ."
27+
when: attackmate_api_git.changed
28+
changed_when: true
29+
tags:
30+
- molecule-idempotence-notest
31+
notify: Restart attackmate-api
32+
33+
- name: Create symlink for attackmate-api-server executable
34+
become: true
35+
ansible.builtin.file:
36+
src: "{{ attackmate_dest }}/.venv/bin/attackmate-api"
37+
dest: "{{ attackmate_api_bin_path }}"
38+
state: link
39+
force: true
40+
41+
42+
- name: Create API log directory
43+
become: true
44+
ansible.builtin.file:
45+
path: "{{ attackmate_api_log_dir }}"
46+
state: directory
47+
owner: attackmate-api
48+
group: attackmate-api
49+
mode: '0755'
50+
51+
- name: Ensure SSL directories exist with correct permissions
52+
become: true
53+
ansible.builtin.file:
54+
path: "{{ item.path }}"
55+
state: directory
56+
owner: root
57+
group: root
58+
mode: "{{ item.mode }}"
59+
loop:
60+
- { path: '/etc/ssl/private', mode: '0711' }
61+
- { path: '/etc/ssl/certs', mode: '0755' }
62+
63+
# --- SSL certificate generation ---
64+
65+
- name: Ensure OpenSSL is installed
66+
ansible.builtin.package:
67+
name: openssl
68+
state: present
69+
70+
- name: Generate Private Key
71+
community.crypto.openssl_privatekey:
72+
path: "{{ attackmate_ssl_key_path }}"
73+
type: RSA
74+
size: 4096
75+
owner: root
76+
group: attackmate-api
77+
mode: '0640'
78+
# Only regenerate if the key is absent
79+
regenerate: never
80+
notify: Restart attackmate-api
81+
82+
- name: Generate Certificate Signing Request (CSR)
83+
community.crypto.openssl_csr:
84+
path: "/etc/ssl/private/attackmate.csr"
85+
privatekey_path: "{{ attackmate_ssl_key_path }}"
86+
common_name: "{{ ansible_host }}"
87+
subject_alt_name:
88+
- "IP:{{ ansible_host }}"
89+
- "DNS:localhost"
90+
force: false
91+
92+
- name: Generate Self-Signed Certificate
93+
community.crypto.x509_certificate:
94+
path: "{{ attackmate_ssl_cert_path }}"
95+
privatekey_path: "{{ attackmate_ssl_key_path }}"
96+
csr_path: "/etc/ssl/private/attackmate.csr"
97+
provider: selfsigned
98+
selfsigned_not_after: "+825d"
99+
selfsigned_digest: sha256
100+
101+
102+
- name: Fix permissions on SSL keys for the service user
103+
become: true
104+
ansible.builtin.file:
105+
path: "{{ item.path }}"
106+
owner: root
107+
group: attackmate-api
108+
mode: "{{ item.mode }}"
109+
loop:
110+
- { path: '/etc/ssl/private', mode: '0710' }
111+
- { path: "{{ attackmate_ssl_key_path }}", mode: '0640' }
112+
- { path: "{{ attackmate_ssl_cert_path }}", mode: '0644' }
113+
114+
# --- User hashes for .env file (Optional) ---
115+
116+
- name: Install argon2-cffi for hash generation
117+
ansible.builtin.pip:
118+
name: argon2-cffi
119+
state: present
120+
delegate_to: localhost
121+
become: false
122+
when: attackmate_api_plain_users | length > 0
123+
124+
- name: Generate argon2 hashes for API users
125+
ansible.builtin.shell:
126+
cmd: "python3 -c \"from argon2 import PasswordHasher; ph = PasswordHasher(); print(ph.hash('{{ item.value }}'))\""
127+
loop: "{{ attackmate_api_plain_users | dict2items }}"
128+
register: argon2_hashes
129+
changed_when: false
130+
delegate_to: localhost
131+
become: false
132+
#no_log: true
133+
when: attackmate_api_plain_users | length > 0
134+
135+
- name: Build USERS json string
136+
ansible.builtin.set_fact:
137+
attackmate_api_users: >-
138+
{{ dict(argon2_hashes.results | map(attribute='item.key') |
139+
zip(argon2_hashes.results | map(attribute='stdout'))) | to_json }}
140+
#no_log: true
141+
when: attackmate_api_plain_users | length > 0
142+
143+
# --- Env file ---
144+
145+
- name: Create .env file in project root
146+
become: true
147+
ansible.builtin.template:
148+
src: api-env.j2
149+
dest: "{{ attackmate_api_server_dest }}/.env"
150+
owner: attackmate-api
151+
group: attackmate-api
152+
mode: '0600'
153+
notify: Restart attackmate-api
154+
155+
# --- Systemd service ---
156+
157+
- name: Install AttackMate API Systemd service
158+
become: true
159+
ansible.builtin.copy:
160+
dest: "{{ attackmate_api_service_path }}"
161+
content: |
162+
[Unit]
163+
Description=AttackMate API Server
164+
After=network.target
165+
166+
[Service]
167+
Type=simple
168+
User=attackmate-api
169+
Group=attackmate-api
170+
WorkingDirectory={{ attackmate_api_server_dest }}
171+
ExecStart={{ attackmate_api_bin_path }}
172+
Restart=on-failure
173+
174+
NoNewPrivileges=yes
175+
PrivateTmp=yes
176+
ProtectSystem=full
177+
ProtectHome=yes
178+
179+
[Install]
180+
WantedBy=multi-user.target
181+
notify: Restart attackmate-api
182+
183+
- name: Start and enable AttackMate API
184+
become: true
185+
ansible.builtin.systemd:
186+
name: attackmate-api
187+
state: started
188+
enabled: yes
189+
daemon_reload: yes

tasks/main.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,12 @@
156156
tags:
157157
- playwright
158158
when: attackmate_playwright
159+
160+
- name: Include api_server setup
161+
ansible.builtin.include_tasks:
162+
file: "api_server.yml"
163+
apply:
164+
tags:
165+
- api_server
166+
when: attackmate_api_server
167+

0 commit comments

Comments
 (0)