Azure Container Instance Distributed Operations
Project description
acido (0.17)
Acido stands for Azure Container Instance Distributed Operations, with acido you can easily deploy container instances in Azure and distribute the workload of a particular task, for example, a port scanning task which has an input file with x hosts is splitted and distributed between y instances.
This tool is inspired by axiom where you can just spin up hundreds of instances to perform a distributed nmap/nuclei/screenshotting scan, and then delete them after they have finished.
Depending on your quota limit you may need to open a ticket to Azure to request container group limits increase.
A little diagram on how the acido CLI works, for example with Nuclei:
Add an alias in .bashrc / .zshrc:
alias acido='python3 -m acido.cli'
Usage:
usage: acido [-h] [-c] [-f FLEET] [-im IMAGE_NAME] [--create-ip CREATE_IP] [--ip] [-n NUM_INSTANCES] [-t TASK] [-e EXEC_CMD] [-i INPUT_FILE] [-w WAIT] [-s SELECT] [-l] [-r REMOVE] [-in]
[-sh SHELL] [-d DOWNLOAD_INPUT] [-o WRITE_TO_FILE] [-rwd]
optional arguments:
-h, --help show this help message and exit
-c, --config Start configuration of acido.
-f FLEET, --fleet FLEET
Create new fleet.
-im IMAGE_NAME, --image IMAGE_NAME
Deploy an specific image.
--create-ip CREATE_IP Create a new IPv4 address and network profile for routing container traffic.
--ip Select an existing IPv4 address to route containers through.
-n NUM_INSTANCES, --num-instances NUM_INSTANCES
Instances that the operation affect
-t TASK, --task TASK Execute command as an entrypoint in the fleet.
-e EXEC_CMD, --exec EXEC_CMD
Execute command on the selected instances.
-i INPUT_FILE, --input-file INPUT_FILE
The name of the file to use on the task.
-w WAIT, --wait WAIT Set max timeout for the instance to finish.
-s SELECT, --select SELECT
Select instances matching name/regex.
-l, --list List all instances.
-r REMOVE, --rm REMOVE
Remove instances matching name/regex.
-in, --interactive Start interactive acido session.
-sh SHELL, --shell SHELL
Execute command and upload to blob.
-d DOWNLOAD_INPUT, --download DOWNLOAD_INPUT
Download file contents remotely from the acido blob.
-o WRITE_TO_FILE, --output WRITE_TO_FILE
Save the output of the machines in JSON format.
-rwd, --rm-when-done Remove the container groups after finish.
Example usage with nmap
In this example we are going to:
- Create our base container image with acido (required) and nmap.
- Create 20 containers.
- Run a nmap scan using the 20 containers.
Step 1: Create the base image
Dockerfile (merabytes.azurecr.io/ubuntu:latest):
FROM ubuntu:20.04
RUN apt-get update && apt-get install python3 python3-pip python3-dev -y
RUN python3 -m pip install acido
RUN apt-get install nmap -y
CMD ["sleep", "infinity"]
This will install acido & nmap on our base docker image (merabytes.azurecr.io/ubuntu:latest).
To upload the image to the registry, as always go to the folder of your Dockerfile and:
docker login merabytes.azurecr.io
docker build -t ubuntu .
docker tag ubuntu merabytes.azurecr.io/ubuntu:latest
docker push merabytes.azurecr.io/ubuntu:latest
Step 2: Run the scan
$ cat file.txt
merabytes.com
uber.com
facebook.com
...
$ acido -f ubuntu \
-n 20 \
--image merabytes.azurecr.io/ubuntu:latest \
-t 'nmap -iL input -p 0-200' \
-i file.txt \
-o output
[+] Selecting I/O storage account (acido).
[+] Splitting into 20 files.
[+] Uploaded 20 targets lists.
[+] Successfully created new group/s: [ ubuntu-01 ubuntu-02 ]
[+] Successfully created new instance/s: [ ubuntu-01-01 ubuntu-01-02 ubuntu-01-03 ubuntu-01-04 ubuntu-01-05 ubuntu-01-06 ubuntu-01-07 ubuntu-01-08 ubuntu-01-09 ubuntu-01-10 ubuntu-02-01 ubuntu-02-02 ubuntu-02-03 ubuntu-02-04 ubuntu-02-05 ubuntu-02-06 ubuntu-02-07 ubuntu-02-08 ubuntu-02-09 ubuntu-02-10 ]
[+] Waiting 2 minutes until the machines get provisioned...
[+] Waiting for outputs...
[+] Executed command on ubuntu-02-01. Output: [
Starting Nmap 7.80 ( https://nmap.org ) at ...
...
]
[+] Executed command on ubuntu-02-02. Output: [
Starting Nmap 7.80 ( https://nmap.org ) at ...
...
]
...
[+] Saved container outputs at: output.json
[+] Saved merged outputs at: all_output.txt.
The result of doing this, is that acido automatically creates 2 container groups with 10 instances, splits the targets file into 20 chunks, uploads the chunks to the instances with the name "input", runs the command provided with -t and after finishing, saves the output to a JSON file.
Requirements
OS: Mac OS / Linux / Windows
Requirement 1: Login to Azure & Create an Azure Container Registry
$ az login
$ az acr create --resource-group Merabytes \
--name merabytes --sku Basic
Note: For production use or CI/CD pipelines, consider creating a Service Principal with appropriate permissions. See .github/AZURE_PERMISSIONS.md for detailed instructions on setting up Azure permissions and authentication.
Requirement 2: Install acido and configure your RG & Registry
pip install acido
python3 -m acido.cli -c
$ acido -c
[+] Selecting I/O storage account (acido).
[!] Please provide a Resource Group Name to deploy the ACIs: Merabytes
[!] Image Registry Server: merabytes.azurecr.io
[!] Image Registry Username: merabytes
[!] Image Registry Password: *********
$
Troubleshooting
Setting Flags for OpenSSL on Devices using Apple Silicon
If you are on an Apple Silicon device, follow these steps to install openssl@1.1 and set the necessary environment variables:
-
Install OpenSSL@1.1: Use Homebrew to install
openssl@1.1.brew install openssl@1.1
-
Set Environment Variables: Export the necessary environment variables to point to the correct library and include directories.
export LDFLAGS="-L/opt/homebrew/opt/openssl@1.1/lib" export CPPFLAGS="-I/opt/homebrew/opt/openssl@1.1/include"
-
Verify Your Configuration: You can verify the installation and see the suggested environment variables by checking the information provided by Homebrew.
brew info openssl
By following these steps, you should have openssl@1.1 installed and the necessary flags set for your Apple Silicon device.
Optional requirement (--exec): Install tmux & Patch Azure CLI
If you want to use --exec (similar to ssh) to execute commands on running containers having tmux installed and on PATH is mandatory.
Also, for the --exec command to work properly, you need to monkey-patch a bug inside az container exec command in the sys.stdout.write function.
File: /lib/python3.9/site-packages/azure/cli/command_modules/container/custom.py
Line: 684
def _cycle_exec_pipe(ws):
r, _, _ = select.select([ws.sock, sys.stdin], [], [])
if ws.sock in r:
data = ws.recv()
sys.stdout.write(data.decode() if isinstance(data, bytes) else data) # MODIFY THE LINE LIKE THIS
sys.stdout.flush()
if sys.stdin in r:
x = sys.stdin.read(1)
if not x:
return True
ws.send(x)
return True
Architecture
Internal Architecture Overview
Acido is designed with a modular architecture that makes it easy to support multiple security tools and efficiently distribute workloads across Azure Container Instances. The system consists of several key components that work together seamlessly:
Core Components
┌─────────────────────────────────────────────────────────────────┐
│ Acido CLI │
│ (User Interface) │
└─────────────────┬───────────────────────────────────────────────┘
│
├──────────────────────────────────────────────┐
│ │
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────┐
│ InstanceManager │ │ BlobManager │
│ - Deploy containers │ │ - Upload/download files │
│ - Manage lifecycle │◄────────────►│ - Store outputs │
│ - Execute commands │ │ - Input distribution │
└────────┬────────────────┘ └──────────┬───────────────┘
│ │
│ Uses Managed Identity │
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────┐
│ Azure Container │ │ Azure Blob Storage │
│ Instances (ACIs) │ │ - Container: acido │
│ - Run security tools │◄────────────►│ - Inputs & Outputs │
│ - Process data chunks │ Download │ - UUIDs for tracking │
└─────────────────────────┘ via MI └──────────────────────────┘
How It Works
1. Input File Distribution via Blob Storage
When you run a distributed scan with an input file, acido follows this workflow:
Host Machine Blob Storage Container Instances
┌─────────────┐ ┌─────────────┐ ┌──────────────────┐
│ input.txt │ │ │ │ Container 1 │
│ (1000 lines)│ │ │ │ ┌────────────┐ │
└──────┬──────┘ │ │ │ │ input (50) │ │
│ │ │ │ └────────────┘ │
│ 1. Split into chunks │ │ └──────────────────┘
├────────────────────────┤ │
│ Chunk 1 (50 lines) │──Upload────►│ UUID-1 │ ┌──────────────────┐
│ Chunk 2 (50 lines) │──Upload────►│ UUID-2 │ │ Container 2 │
│ Chunk 3 (50 lines) │──Upload────►│ UUID-3 │ │ ┌────────────┐ │
│ ... │ │ │ │ │ input (50) │ │
│ Chunk 20 (50 lines) │──Upload────►│ UUID-20 │ │ └────────────┘ │
└────────────────────────┘ │ │ └──────────────────┘
│ │ ...
│ │ ┌──────────────────┐
│ │ │ Container 20 │
└────Download──►│ ┌────────────┐ │
via MI │ │ │ input (50) │ │
│ │ └────────────┘ │
│ └──────────────────┘
Steps:
- File Splitting: The CLI splits the input file into N chunks (where N = number of instances)
- Blob Upload: Each chunk is uploaded to blob storage with a unique UUID identifier
- Container Deployment: Containers are deployed with environment variables containing:
- Blob storage account name
- Managed Identity client ID
- UUID of their assigned input chunk
- Download in Container: Each container uses Managed Identity to authenticate and download its input chunk
- Processing: The security tool processes its chunk independently
- Output Collection: Results are uploaded back to blob storage and collected by the CLI
2. Managed Identity for Secure Access
Acido uses Azure Managed Identity to provide containers with secure, credential-free access to blob storage:
┌────────────────────────────────────────────────────────────────┐
│ Container Instance (ACI) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Environment Variables: │ │
│ │ - IDENTITY_CLIENT_ID: <managed-identity-client-id> │ │
│ │ - STORAGE_ACCOUNT_NAME: <storage-account> │ │
│ │ - RG: <resource-group> │ │
│ └────────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ ManagedAuthentication Class │ │
│ │ - Detects cloud environment (MSI_ENDPOINT) │ │
│ │ - Uses ManagedIdentityCredential with client_id │ │
│ │ - Obtains access token for storage.azure.com │ │
│ └────────────────────────┬─────────────────────────────────┘ │
└───────────────────────────┼─────────────────────────────────────┘
│ Token-based authentication
▼
┌─────────────────────────────┐
│ Azure Blob Storage │
│ - Validates MI token │
│ - Grants read/write access │
└─────────────────────────────┘
Benefits:
- No credentials in code or environment: No storage account keys needed in containers
- Automatic token management: Azure handles token lifecycle
- Fine-grained permissions: RBAC controls what each identity can access
- Audit trail: All access logged in Azure Activity Logs
3. Supporting Multiple Security Tools
The architecture is designed to be tool-agnostic, making it easy to support any security tool:
┌─────────────────────────────────────────────────────────────────┐
│ Acido Abstraction Layer │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Generic Command Execution Framework │ │
│ │ - Input file handling (--input-file) │ │
│ │ - Command wrapper (--task) │ │
│ │ - Output collection (--output) │ │
│ └────────────────────────────────────────────────────────────┘ │
└─────────────────────────┬───────────────────────────────────────┘
│
┌───────────────┼───────────────┬────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐
│ nmap │ │ nuclei │ │ masscan │ │ nikto │
│ │ │ │ │ │ │ │
│ -iL │ │ -list │ │ -iL │ │ -h │
│ input │ │ input │ │ input │ │ input │
└─────────┘ └─────────┘ └──────────┘ └──────────┘
Key Design Principles:
- Standard Input/Output Pattern: All tools read from a file named
inputand write to stdout - Docker Entrypoint Execution: Commands run via
-tflag use the container's entrypoint - Environment Variable Injection: Tools can access blob storage, registry credentials, etc.
- Result Aggregation: Outputs are collected via blob storage and merged by CLI
Example with different tools:
# Nmap scan
acido -f nmap-fleet -n 20 -im registry.io/nmap:latest \
-t 'nmap -iL input -p 1-1000' -i targets.txt
# Nuclei scan
acido -f nuclei-fleet -n 50 -im registry.io/nuclei:latest \
-t 'nuclei -list input -t /nuclei-templates/' -i urls.txt
# Masscan scan
acido -f masscan-fleet -n 30 -im registry.io/masscan:latest \
-t 'masscan -iL input -p0-65535' -i targets.txt
Data Flow Summary
1. User invokes CLI with task and input file
↓
2. CLI authenticates to Azure (az login or environment credentials)
↓
3. Input file split into N chunks (N = number of instances)
↓
4. Chunks uploaded to blob storage (acido container)
↓
5. Container groups created with:
- Base image with security tool + acido installed
- Managed Identity attached
- Environment variables (IDENTITY_CLIENT_ID, STORAGE_ACCOUNT_NAME, etc.)
- Command: download input chunk → run security tool → upload output
↓
6. Containers start and use Managed Identity to:
- Download their assigned input chunk from blob
- Execute the security tool command
- Upload results back to blob storage
↓
7. CLI polls containers for completion
↓
8. CLI downloads outputs from blob storage
↓
9. Results aggregated and saved (JSON + merged text file)
↓
10. Optional: Containers deleted (--rm-when-done)
Component Details
InstanceManager
- Purpose: Manages Azure Container Instance lifecycle
- Key Methods:
deploy(): Creates container groups with specified configurationprovision(): Configures individual container instancesrm(): Deletes container groupsls(): Lists all running instances
- Features:
- Automatic splitting of >10 instances into multiple container groups (Azure limit)
- Managed Identity attachment for blob access
- Image registry authentication
- Network profile support for custom IP routing
BlobManager
- Purpose: Handles all blob storage operations
- Key Methods:
upload(): Uploads data with UUID namingdownload(): Retrieves files by UUIDuse_container(): Selects/creates storage container
- Authentication: Supports managed identity, environment credentials, and connection strings
- Features: Automatic UUID generation for file tracking
ManagedAuthentication
- Purpose: Provides unified authentication across Azure services
- Credential Chain:
- Cloud: Managed Identity → Environment Credentials
- Local: Azure CLI → Environment Credentials → Client Secret
- Auto-detection: Automatically detects cloud vs. local environment
NetworkManager
- Purpose: Manages virtual networks and public IPs for container groups
- Key Methods:
create_ipv4(): Creates public IP addressescreate_virtual_network(): Sets up VNetscreate_network_profile(): Creates network profiles for ACIs
- Use Case: Route all container traffic through a single public IP
Traffic Routing for Security Audits
Acido supports routing all container traffic through a single public IPv4 address, which is particularly valuable for security audits and penetration testing engagements:
How It Works
┌────────────────────────────────────────────────────────────────┐
│ Single Public IP (e.g., 20.123.45.67) │
│ Created with: acido --create-ip my-pentest-ip │
└────────────────┬───────────────────────────────────────────────┘
│ All outbound traffic routes through this IP
│
┌────────────┼────────────┬────────────┬────────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│Container│ │Container│ │Container│ │Container│ │Container│
│ 1 │ │ 2 │ │ 3 │ │ ... │ │ 50 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │ │
└────────────┴────────────┴────────────┴────────────┘
│
Distributed scanning
▼
┌─────────────────┐
│ Target Network │
│ (Client site) │
└─────────────────┘
Benefits for Penetration Testing
-
Simplified IP Whitelisting:
- Instead of whitelisting dozens or hundreds of container IPs, security teams only need to whitelist a single IP address
- Reduces firewall rule complexity and management overhead
- Makes it easier to coordinate with client security teams
-
Larger Scale Testing:
- Deploy 50, 100, or more containers for distributed scanning
- All traffic appears to originate from the whitelisted IP
- Achieve significantly higher throughput than traditional single-machine scans
- Complete comprehensive scans in a fraction of the time
-
Audit Trail and Compliance:
- All scan traffic is associated with a single, documented IP address
- Easier to track and correlate security testing activities
- Simplifies incident response if alerts are triggered
- Meets compliance requirements for authorized testing
-
Professional Engagement Workflow:
- Create IP before engagement:
acido --create-ip client-pentest-2024 - Provide IP to client for whitelisting
- Deploy fleet with IP routing:
acido -f scan-fleet -n 50 --ip -t '...' - All containers automatically use the whitelisted IP
- Clean up after engagement
- Create IP before engagement:
Example Usage:
# Create a new public IP for the pentest engagement
acido --create-ip acme-corp-pentest
# Provide the IP address (shown in output) to client for whitelisting
# Example: 20.123.45.67
# Once whitelisted, deploy your fleet routing through this IP
acido -f nmap-fleet -n 50 --ip \
-im registry.io/nmap:latest \
-t 'nmap -iL input -p- -T4' \
-i targets.txt
# All 50 containers will scan through the single whitelisted IP
# Achieving 50x parallelization while maintaining a single source IP
This approach combines the scale and speed of distributed scanning with the simplicity and control required for professional security engagements.
Upcoming features
- Add argument to specify docker image of the fleet
- Add argument to execute scans through the Docker ENTRYPOINT (-t / --task)
- Test on Windows
- Add argument to retrieve ACI logs
- Add argument to create the fleet with a Network Group (route the traffic from all instances to a single Public IP)
- Get rid of monkey-patching of Azure CLI for --exec
Credits / Acknowledgements
- Xavier Álvarez (xalvarez@merabytes.com)
- Juan Ramón Higueras Pica (jrhigueras@dabbleam.com)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file acido-0.17.tar.gz.
File metadata
- Download URL: acido-0.17.tar.gz
- Upload date:
- Size: 31.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c290b9d573b8bbaeb5a9a03355abd0d5e38f334d528495d6cdc633e93e6c171
|
|
| MD5 |
ce9333b2de552755f3d7671bcaa5c351
|
|
| BLAKE2b-256 |
66b5ef8c72e8dfa90dc0fa26756c8c78059265122025de071c7ddd354a82c2b9
|
File details
Details for the file acido-0.17-py3-none-any.whl.
File metadata
- Download URL: acido-0.17-py3-none-any.whl
- Upload date:
- Size: 26.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d9173c81f6f0716ddb878777ac61024ef78d5ec03a68440213f937c4f3bfc33
|
|
| MD5 |
ffde46880d9145fa27d15e66fd18a239
|
|
| BLAKE2b-256 |
3787875858601b099d08383ef7fdbf2157fa240b631386200738e01968ed21ce
|