Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions .github/workflows/build-and-push-templates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ env:
PYTHON_VERSION: 3.13.7
TASK_VERSION: 3.45.5
TASK_X_REMOTE_TASKFILES: 1
WARPGATE_VERSION: "v4.6.0"
WARPGATE_VERSION: "v4.7.0"

jobs:
discover-templates:
Expand Down Expand Up @@ -192,15 +192,16 @@ jobs:
DEPENDENCY_MAP="{}"
PLATFORMS_MAP="{}"
ANSIBLE_MAP="{}"
AMI_TEMPLATES=()
NON_CONTAINER_TEMPLATES=()
for template_dir in "$GITHUB_WORKSPACE"/warpgate-templates/templates/*/; do
if [ -f "${template_dir}warpgate.yaml" ]; then
template_name=$(basename "$template_dir")

# Skip AMI-only templates - they cannot be built as containers
if grep -q "type: ami" "${template_dir}warpgate.yaml" && ! grep -q "type: container" "${template_dir}warpgate.yaml"; then
echo " $template_name: SKIPPED (AMI-only template)"
AMI_TEMPLATES+=("$template_name")
# Skip templates without a container target (e.g. AMI-only, Azure-only)
# — this workflow builds and pushes container images.
if ! grep -q "type: container" "${template_dir}warpgate.yaml"; then
echo " $template_name: SKIPPED (no container target)"
NON_CONTAINER_TEMPLATES+=("$template_name")
continue
fi

Expand Down Expand Up @@ -282,13 +283,13 @@ jobs:
]
}')

# Remove AMI-only templates from matrix (they have no dependency/platform maps)
if [ ${#AMI_TEMPLATES[@]} -gt 0 ]; then
AMI_JSON=$(printf '%s\n' "${AMI_TEMPLATES[@]}" | jq -R . | jq -s .)
FULL_MATRIX=$(echo "$FULL_MATRIX" | jq --argjson ami "$AMI_JSON" '{
include: [.include[] | select(.name as $n | $ami | index($n) | not)]
# Remove non-container templates from matrix (they have no dependency/platform maps)
if [ ${#NON_CONTAINER_TEMPLATES[@]} -gt 0 ]; then
EXCLUDE_JSON=$(printf '%s\n' "${NON_CONTAINER_TEMPLATES[@]}" | jq -R . | jq -s .)
FULL_MATRIX=$(echo "$FULL_MATRIX" | jq --argjson exclude "$EXCLUDE_JSON" '{
include: [.include[] | select(.name as $n | $exclude | index($n) | not)]
}')
echo "Excluded AMI-only templates from container build matrix: ${AMI_TEMPLATES[*]}"
echo "Excluded non-container templates from container build matrix: ${NON_CONTAINER_TEMPLATES[*]}"
fi

# Apply template filter if provided via workflow_dispatch
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/test-template-builds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ concurrency:
env:
DEBIAN_FRONTEND: noninteractive
PYTHON_VERSION: "3.13.7"
WARPGATE_VERSION: "v4.6.0"
WARPGATE_VERSION: "v4.7.0"

jobs:
detect-changes:
Expand Down Expand Up @@ -100,9 +100,10 @@ jobs:
continue
fi

# Skip AMI templates - they require AWS credentials and cannot be tested in CI
if grep -qE '^\s+- type: ami' "${template_dir}/warpgate.yaml"; then
echo "Skipping $template_name: AMI target type cannot be tested in CI"
# Skip templates without a container target (AMI-only, Azure-only, etc.)
# — this workflow tests container builds in CI.
if ! grep -q "type: container" "${template_dir}/warpgate.yaml"; then
echo "Skipping $template_name: no container target (only container builds are tested in CI)"
continue
fi

Expand Down
24 changes: 20 additions & 4 deletions .github/workflows/validate-templates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ on:
workflow_dispatch:

env:
WARPGATE_VERSION: "v4.6.0"
WARPGATE_VERSION: "v4.7.0"
PYTHON_VERSION: "3.13.7"
TASK_VERSION: "3.45.5"
TASK_X_REMOTE_TASKFILES: 1
Expand Down Expand Up @@ -103,6 +103,17 @@ jobs:
} >> "$GITHUB_OUTPUT"

- name: Validate templates with warpgate (syntax-only)
# warpgate validate resolves ${VAR} references before checking required
# fields, so placeholders are needed for env-driven values (Azure target
# IDs, GITHUB_TOKEN). Real values are supplied at build time.
env:
GITHUB_TOKEN: placeholder
AZURE_SUBSCRIPTION_ID: 00000000-0000-0000-0000-000000000000
AZURE_LOCATION: centralus
AZURE_RESOURCE_GROUP: placeholder-rg
AZURE_GALLERY_NAME: placeholder-gallery
AZURE_IDENTITY_ID: /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/placeholder-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/placeholder-uami
AZURE_VM_SIZE: Standard_D4s_v3
run: |
failed=0
while IFS= read -r template; do
Expand Down Expand Up @@ -150,11 +161,14 @@ jobs:
fi

- name: Validate YAML syntax
# Reuse the project-wide yamllint config (.hooks/linters/yamllint.yaml)
# so this step matches what pre-commit runs locally — keeps shell-heavy
# provisioner inlines from drowning the log in line-length warnings.
run: |
pip install yamllint
find warpgate-templates -name '*.yaml' -o -name '*.yml' | while read -r file; do
echo "Checking YAML syntax: $file"
yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" "$file"
yamllint -c .hooks/linters/yamllint.yaml "$file"
done

- name: Validate against JSON schema
Expand All @@ -169,8 +183,10 @@ jobs:
from pathlib import Path
from jsonschema import Draft7Validator

# Download the schema
schema_url = "https://raw.githubusercontent.com/cowdogmoo/warpgate/v4.4.0/schema/warpgate-template.json"
# Download the schema (version pinned to the warpgate binary version
# from the workflow env so schema and validator stay in lockstep)
warpgate_version = "${{ env.WARPGATE_VERSION }}"
schema_url = f"https://raw.githubusercontent.com/cowdogmoo/warpgate/{warpgate_version}/schema/warpgate-template.json"
print(f"Downloading schema from: {schema_url}")

try:
Expand Down
51 changes: 51 additions & 0 deletions warpgate-templates/templates/ares-golden-azure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# ares-golden-azure

Azure variant of the Ares golden image. Builds a Kali Linux image via Azure
VM Image Builder and publishes a version into a Compute Gallery, with feature
parity against the AWS `ares-golden-image` AMI.

Ships the same red-team toolchain installed by
`ansible/playbooks/ares/goad_attack_box.yml`:

- recon, credential access, privilege escalation
- password cracking (hashcat from source, GPU-accelerated)
- lateral movement, ACL abuse, coercion
- Alloy telemetry agent
- NVIDIA driver + CUDA toolkit for T4 GPU acceleration

## Prerequisites

The template's `targets[].azure` fields are parameterized via environment
variables so the same template works across subscriptions and environments.
The values below are placeholders — substitute your own.

Provisioned manually (one-time):

- An Azure subscription (`${AZURE_SUBSCRIPTION_ID}`)
- A resource group (`${AZURE_RESOURCE_GROUP}`) in your chosen region
(`${AZURE_LOCATION}`, e.g. `centralus`)
- A Compute Gallery (`${AZURE_GALLERY_NAME}`)
- Image definition `ares-golden-azure` (Linux, Generalized, HyperV V2,
publisher=`dreadnode`, offer=`ares`, sku=`golden`)
- A user-assigned managed identity (`${AZURE_IDENTITY_ID}` — full resource ID)
with Contributor on the resource group
- Quota for the chosen `${AZURE_VM_SIZE}` in `${AZURE_LOCATION}`
(e.g. `Standard_NC4as_T4_v3` for T4 GPU, `Standard_D4s_v3` for CPU-only)
- Kali Marketplace terms accepted on the subscription:
`az vm image terms accept --publisher kali-linux --offer kali --plan kali-2026-1`

## Build

Export the required env vars, then build:

```bash
export AZURE_SUBSCRIPTION_ID=<your-subscription-id>
export AZURE_LOCATION=centralus
export AZURE_RESOURCE_GROUP=<your-rg>
export AZURE_GALLERY_NAME=<your-gallery>
export AZURE_IDENTITY_ID=/subscriptions/<sub>/resourcegroups/<rg>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<uami>
export AZURE_VM_SIZE=Standard_NC4as_T4_v3
export GITHUB_TOKEN=<token-with-repo-read>

warpgate build path/to/ares-golden-azure --target azure
```
100 changes: 100 additions & 0 deletions warpgate-templates/templates/ares-golden-azure/warpgate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/cowdogmoo/warpgate/main/schema/warpgate-template.json
metadata:
name: ares-golden-azure
version: 1.0.0
description: Azure variant of the Ares golden image with all red team tools - recon, credential access, privesc, cracking, lateral movement, ACL abuse, and coercion
author: Dreadnode <info@dreadnode.io>
license: MIT
tags:
- ares
- golden-image
- azure
- red-team
- reconnaissance
- credential-access
- privilege-escalation
- password-cracking
- lateral-movement
- acl
- coercion
requires:
warpgate: '>=1.0.0'

name: ares-golden-azure
version: latest

base:
image: kalilinux/kali-rolling@sha256:dddc31e0f4bc57b4b91e9027762544506bf91c7cdd7ff52104daaa4449b4c726

provisioners:
# Install pipx + Ansible, then fetch the nimbus_range collection on the build VM.
# We re-clone in shell rather than using warpgate's `sources` + `type: file`
# pattern (see ares-golden-image) because Azure Image Builder expands `type: file`
# into one customizer per file and times out on the 2000+ file ansible/ tree.
# Token is passed via a credential helper so it never appears in the clone URL
# or AIB customizer logs; ref tracks the AMI variant.
- type: shell
inline:
- apt-get update
- apt-get install -y --no-install-recommends ca-certificates git procps sudo python3-apt python3-pip python3-venv pipx
- 'sed -i ''s|^PATH="|PATH="/root/.local/bin:/root/.cargo/bin:|'' /etc/environment || echo ''PATH="/root/.local/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"'' > /etc/environment'
- pipx install --force uv
- pipx install --force ansible-core
- pipx ensurepath
- GITHUB_TOKEN=${GITHUB_TOKEN} git -c 'credential.helper=!f() { echo username=x-access-token; echo password=$GITHUB_TOKEN; }; f' clone --depth 1 --branch feat/more-attack-cov https://github.com/dreadnode/ares.git /tmp/nimbus_range
- mkdir -p /root/.ansible/collections/ansible_collections/dreadnode/nimbus_range
- cp -r /tmp/nimbus_range/ansible/. /root/.ansible/collections/ansible_collections/dreadnode/nimbus_range/
- rm -rf /tmp/nimbus_range

# Attack Box - all red team tools + Alloy telemetry
# NOTE: Using shell instead of ansible provisioner because the playbook
# exceeds Azure VM Image Builder's customizer length limit when inlined.
- type: shell
inline:
- PATH=/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ansible-galaxy collection install -r /root/.ansible/collections/ansible_collections/dreadnode/nimbus_range/requirements.yml --force
- HOME=/root ANSIBLE_REMOTE_TMP=/tmp/ansible-tmp-$USER PATH=/root/.local/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ansible-playbook /root/.ansible/collections/ansible_collections/dreadnode/nimbus_range/playbooks/ares/goad_attack_box.yml -i localhost, -c local -e ansible_shell_executable=/bin/bash -e ansible_python_interpreter=/usr/bin/python3 -e cracking_tools_gpu_support=true -e cracking_tools_hashcat_from_source=true -e cracking_tools_nvidia_opencl_icd=true

# NVIDIA GPU drivers + CUDA toolkit for hashcat GPU acceleration on NCas T4 v3.
# Kernel headers + dkms are required so the nvidia module builds for the
# running kernel. The image then works on GPU instances without manual driver
# setup. nvidia-smi may not be available during image build if no GPU is attached.
- type: shell
inline:
- apt-get update
- DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends linux-headers-$(uname -r) dkms nvidia-driver nvidia-cuda-toolkit firmware-misc-nonfree
- nvidia-smi || echo "nvidia-smi not available during image build (expected if no GPU attached)"

# Cleanup
- type: shell
inline:
- apt-get clean
- rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
- echo "Ares golden azure build completed successfully"

targets:
- type: azure
os_type: Linux
subscription_id: ${AZURE_SUBSCRIPTION_ID}
location: ${AZURE_LOCATION}
resource_group: ${AZURE_RESOURCE_GROUP}
gallery: ${AZURE_GALLERY_NAME}
gallery_image_definition: ares-golden-azure
identity_id: ${AZURE_IDENTITY_ID}
vm_size: ${AZURE_VM_SIZE}
# For GPU: set AZURE_VM_SIZE=Standard_NC4as_T4_v3
# For CPU-only test builds: set AZURE_VM_SIZE=Standard_D4s_v3
source_image:
marketplace:
publisher: kali-linux
offer: kali
sku: kali-2026-1
version: latest
plan:
name: kali-2026-1
product: kali
publisher: kali-linux
image_tags:
Project: ares
Role: RedTeamAttackBox
ManagedBy: warpgate
Tools: recon,credential-access,privesc,cracker,lateral-movement,acl-abuse,coercion
20 changes: 11 additions & 9 deletions warpgate-templates/templates/ares-golden-image/warpgate.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/cowdogmoo/warpgate/main/schema/warpgate-template.json
metadata:
name: ares-golden-image
Expand Down Expand Up @@ -33,12 +33,12 @@
most_recent: true

sources:
- name: nimbus_range
git:
repository: https://github.com/dreadnode/ansible-collection-nimbus_range.git
depth: 1
auth:
token: ${GITHUB_TOKEN}
# Use the in-repo ansible/ tree directly so builds match the working copy
# (no GITHUB_TOKEN, no branch ref drift). Path is relative to this template's
# directory; requires warpgate >= v4.7.0 (local source type).
- name: ares
local:
path: ../../../ansible

provisioners:
# Install pipx and Ansible
Expand All @@ -51,15 +51,17 @@
- pipx install --force ansible-core
- pipx ensurepath

# Copy ansible collection from source (cloned securely by warpgate without embedding token in shell commands)
# Copy ansible collection from the local source (the in-repo ansible/ tree).
# The destination keeps the `nimbus_range` name because the ansible collection is published as
# `dreadnode.nimbus_range`; subsequent steps install it under that namespace.
- type: file
source: ${sources.nimbus_range}
source: ${sources.ares}
destination: /tmp/nimbus_range

- type: shell
inline:
- mkdir -p /root/.ansible/collections/ansible_collections/dreadnode/nimbus_range
- cp -r /tmp/nimbus_range/* /root/.ansible/collections/ansible_collections/dreadnode/nimbus_range/
- cp -r /tmp/nimbus_range/. /root/.ansible/collections/ansible_collections/dreadnode/nimbus_range/
- rm -rf /tmp/nimbus_range

# Install NVIDIA drivers for GPU-accelerated hashcat on g4dn (T4 GPU)
Expand Down
Loading