Skip to content

Commit dfddca7

Browse files
TGJLSclaude
andcommitted
Add GitHub Actions CI: build and integration test workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 095f39b commit dfddca7

5 files changed

Lines changed: 292 additions & 1 deletion

File tree

.github/ci/adaptixc2_profile.yaml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
Teamserver:
2+
interface: "0.0.0.0"
3+
port: 4321
4+
endpoint: "/endpoint"
5+
password: "cipass"
6+
only_password: true
7+
operators:
8+
ci: "cipass"
9+
cert: "server.rsa.crt"
10+
key: "server.rsa.key"
11+
extenders:
12+
- "extenders/beacon_listener_http/config.yaml"
13+
- "extenders/beacon_listener_smb/config.yaml"
14+
- "extenders/beacon_listener_tcp/config.yaml"
15+
- "extenders/beacon_listener_dns/config.yaml"
16+
- "extenders/beacon_agent/config.yaml"
17+
- "extenders/gopher_listener_tcp/config.yaml"
18+
- "extenders/gopher_agent/config.yaml"
19+
axscripts:
20+
access_token_live_hours: 1
21+
refresh_token_live_hours: 2
22+
23+
HttpServer:
24+
error:
25+
status: 404
26+
headers:
27+
Content-Type: "text/html; charset=UTF-8"
28+
Server: "AdaptixC2"
29+
Adaptix-Version: "v1.2"
30+
page: "404page.html"
31+
http:
32+
max_header_bytes: 8192
33+
read_header_timeout_sec: 0
34+
read_timeout_sec: 0
35+
write_timeout_sec: 0
36+
idle_timeout_sec: 0
37+
request_timeout_sec: 300
38+
request_timeout_message: "504 Gateway Timeout"
39+
disable_keep_alives: false
40+
enable_http2: true
41+
tls:
42+
min_version: "TLS1.2"
43+
max_version: "TLS1.3"
44+
prefer_server_cipher_suites: false
45+
cipher_suites:
46+
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
47+
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
48+
- "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
49+
- "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
50+
- "TLS_RSA_WITH_AES_128_GCM_SHA256"
51+
- "TLS_RSA_WITH_AES_256_GCM_SHA384"

.github/workflows/build.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- uses: astral-sh/setup-uv@v5
15+
with:
16+
python-version: "3.14"
17+
18+
- name: Install dependencies
19+
run: uv sync
20+
21+
- name: Build wheel
22+
run: uv build
23+
24+
- uses: actions/upload-artifact@v4
25+
with:
26+
name: dist
27+
path: dist/

.github/workflows/test.yaml

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
name: Integration Tests
2+
3+
# Runs entirely on a single GitHub-hosted windows-latest runner.
4+
# AdaptixC2 and adaptix-testing run inside WSL2 (Ubuntu).
5+
# The beacon runs on the Windows host.
6+
# SSH delivery goes from WSL → Windows via the Hyper-V bridge IP.
7+
# Beacon callbacks go from Windows → WSL via the WSL veth IP.
8+
# All IPs are detected at runtime — no static config needed.
9+
#
10+
# Hardcoded CI credentials are intentional: these containers are
11+
# ephemeral, hold no real secrets, and only exist for testing.
12+
13+
on:
14+
push:
15+
pull_request:
16+
workflow_dispatch:
17+
18+
env:
19+
ADAPTIXC2_REPO: Adaptix-Framework/AdaptixC2
20+
ADAPTIXC2_VERSION: v1.2
21+
CI_USER: ci_runner
22+
CI_PASS: Ci_Test_Pass1!
23+
CI_AGENT_DIR: 'C:\ci'
24+
CI_AGENT_PATH: 'C:\ci\agent.exe'
25+
26+
jobs:
27+
integration-test:
28+
runs-on: windows-latest
29+
30+
steps:
31+
- uses: actions/checkout@v4
32+
33+
- name: Pass CI variables into WSL
34+
shell: powershell
35+
run: echo "WSLENV=CI_USER/u:CI_PASS/u:CI_AGENT_PATH/u:ADAPTIXC2_REPO/u:ADAPTIXC2_VERSION/u" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
36+
37+
# ── Windows: CI user, OpenSSH, agent directory ──────────────────────────
38+
39+
- name: Create CI user
40+
shell: powershell
41+
run: |
42+
$pass = ConvertTo-SecureString $env:CI_PASS -AsPlainText -Force
43+
if (-not (Get-LocalUser $env:CI_USER -ErrorAction SilentlyContinue)) {
44+
New-LocalUser $env:CI_USER -Password $pass -PasswordNeverExpires
45+
Add-LocalGroupMember -Group Administrators -Member $env:CI_USER
46+
} else {
47+
Set-LocalUser $env:CI_USER -Password $pass
48+
}
49+
50+
- name: Start OpenSSH Server with password auth
51+
shell: powershell
52+
run: |
53+
$cap = Get-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
54+
if ($cap.State -ne 'Installed') {
55+
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
56+
}
57+
Set-Service sshd -StartupType Automatic
58+
Start-Service sshd
59+
$cfg = "$env:ProgramData\ssh\sshd_config"
60+
(Get-Content $cfg) `
61+
-replace '^#?PasswordAuthentication\s+\w+', 'PasswordAuthentication yes' |
62+
Set-Content $cfg
63+
Restart-Service sshd
64+
65+
- name: Create agent drop directory
66+
shell: powershell
67+
run: |
68+
New-Item -ItemType Directory -Force -Path $env:CI_AGENT_DIR | Out-Null
69+
New-Item -ItemType Directory -Force -Path C:\tmp | Out-Null
70+
71+
# ── WSL: Ubuntu with required packages ──────────────────────────────────
72+
73+
- uses: Vampire/setup-wsl@v3
74+
with:
75+
distribution: Ubuntu-22.04
76+
additional-packages: sshpass python3-pip openssl mingw-w64 make gcc g++ g++-mingw-w64
77+
78+
- name: Install Go 1.25.4
79+
shell: wsl-bash {0}
80+
run: |
81+
wget -q https://go.dev/dl/go1.25.4.linux-amd64.tar.gz -O /tmp/go1.25.4.linux-amd64.tar.gz
82+
sudo rm -rf /usr/local/go /usr/local/bin/go
83+
sudo tar -C /usr/local -xzf /tmp/go1.25.4.linux-amd64.tar.gz
84+
sudo ln -s /usr/local/go/bin/go /usr/local/bin/go
85+
86+
- name: Install uv
87+
shell: wsl-bash {0}
88+
run: pip3 install -q uv
89+
90+
# ── WSL: clone, build, generate cert, write profile, start server ────────
91+
92+
- name: Clone and build AdaptixC2
93+
shell: wsl-bash {0}
94+
run: |
95+
git clone --depth 1 --branch "$ADAPTIXC2_VERSION" "https://github.com/$ADAPTIXC2_REPO" /tmp/adaptixc2
96+
cd /tmp/adaptixc2
97+
make server-ext
98+
99+
- name: Generate TLS certificate
100+
shell: wsl-bash {0}
101+
run: |
102+
openssl req -x509 -nodes -newkey rsa:2048 \
103+
-keyout /tmp/adaptixc2/dist/server.rsa.key \
104+
-out /tmp/adaptixc2/dist/server.rsa.crt \
105+
-days 1 -subj "/CN=ci"
106+
107+
- name: Write server profile
108+
shell: wsl-bash {0}
109+
run: |
110+
WIN_WS=$(cmd.exe /c "echo %GITHUB_WORKSPACE%" 2>/dev/null | tr -d '\r')
111+
cp "$(wslpath "$WIN_WS")/.github/ci/adaptixc2_profile.yaml" /tmp/adaptixc2/dist/profile.yaml
112+
113+
- name: Start AdaptixC2 server
114+
shell: wsl-bash {0}
115+
run: |
116+
cd /tmp/adaptixc2/dist
117+
./adaptixserver -profile profile.yaml &
118+
echo $! > /tmp/adaptixc2.pid
119+
sleep 2
120+
121+
# ── WSL: install testing kit ─────────────────────────────────────────────
122+
123+
- name: Install adaptix-testing
124+
shell: wsl-bash {0}
125+
run: |
126+
WIN_WS=$(cmd.exe /c "echo %GITHUB_WORKSPACE%" 2>/dev/null | tr -d '\r')
127+
cp -r "$(wslpath "$WIN_WS")" /tmp/testing-kit
128+
cd /tmp/testing-kit
129+
uv sync
130+
131+
# ── WSL: SSH setup + config + run ────────────────────────────────────────
132+
133+
- name: Generate SSH keypair
134+
shell: wsl-bash {0}
135+
run: |
136+
ssh-keygen -t ed25519 -N "" -f ~/.ssh/ci_key
137+
cp ~/.ssh/ci_key.pub /mnt/c/tmp/ci_key.pub
138+
139+
- name: Install SSH public key on Windows
140+
shell: powershell
141+
run: |
142+
$authFile = "$env:ProgramData\ssh\administrators_authorized_keys"
143+
New-Item -Force -ItemType File -Path $authFile | Out-Null
144+
Get-Content C:\tmp\ci_key.pub | Add-Content $authFile
145+
icacls $authFile /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
146+
147+
- name: Write CI config
148+
shell: wsl-bash {0}
149+
run: |
150+
# Windows → WSL: use the WSL veth IP so the beacon can call home
151+
WSL_IP=$(hostname -I | awk '{print $1}')
152+
WINDOWS_IP=$(cmd.exe /c ipconfig 2>/dev/null | tr -d '\r' | awk '/vEthernet/{f=1} f && /IPv4 Address/{print $NF; exit}')
153+
154+
cat > /tmp/ci_config.yaml << EOF
155+
server:
156+
url: https://127.0.0.1:4321
157+
endpoint: /endpoint
158+
operator:
159+
name: ci
160+
password: cipass
161+
setup:
162+
project: ci
163+
agent_output: /tmp/ci_agent.exe
164+
listener:
165+
name: ci_http
166+
type: BeaconHTTP
167+
config:
168+
host: "$WSL_IP"
169+
path: /beacon
170+
port: 8080
171+
use_tls: false
172+
agent:
173+
agent: beacon
174+
listener: ci_http
175+
listener_type: BeaconHTTP
176+
config:
177+
format: EXE
178+
jitter: 0
179+
sleep: "0s"
180+
ssh:
181+
host: "$WINDOWS_IP"
182+
username: "$CI_USER"
183+
key_path: ~/.ssh/ci_key
184+
source_path: /tmp/ci_agent.exe
185+
agent_path: '$CI_AGENT_PATH'
186+
terminate: true
187+
EOF
188+
189+
- name: Run integration tests
190+
shell: wsl-bash {0}
191+
run: |
192+
cd /tmp/testing-kit
193+
uv run adaptix-testing -c /tmp/ci_config.yaml -t tasks.yaml
194+
195+
# ── Cleanup ──────────────────────────────────────────────────────────────
196+
197+
- name: Stop AdaptixC2 server
198+
if: always()
199+
shell: wsl-bash {0}
200+
run: |
201+
[ -f /tmp/adaptixc2.pid ] && kill "$(cat /tmp/adaptixc2.pid)" 2>/dev/null || true
202+
203+
- name: Remove SSH key
204+
if: always()
205+
shell: wsl-bash {0}
206+
run: rm -f ~/.ssh/ci_key ~/.ssh/ci_key.pub

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,10 @@ dependencies = [
1111

1212
[project.scripts]
1313
adaptix-testing = "run:main"
14+
15+
[build-system]
16+
requires = ["hatchling"]
17+
build-backend = "hatchling.build"
18+
19+
[tool.hatch.build.targets.wheel]
20+
include = ["run.py"]

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)