JumpCloud Hardening Guide
Cloud directory and identity management hardening for JumpCloud SSO, MFA, and device management
Overview
JumpCloud is a cloud-based directory platform providing identity management, SSO, MFA, and device management for over 200,000 organizations. As a unified directory replacing traditional Active Directory, JumpCloud security configurations directly impact access control across all integrated resources including systems, applications, and networks.
Intended Audience
- Security engineers managing JumpCloud deployments
- IT administrators configuring directory policies
- GRC professionals assessing identity controls
- Third-party risk managers evaluating directory services
How to Use This Guide
- L1 (Baseline): Essential controls for all organizations
- L2 (Hardened): Enhanced controls for security-sensitive environments
- L3 (Maximum Security): Strictest controls for regulated industries
Scope
This guide covers JumpCloud Admin Portal security, MFA policies, conditional access, device management, and system policies.
Table of Contents
- Admin Account Security
- Multi-Factor Authentication
- Conditional Access
- Device & System Management
- Monitoring & Detection
- Compliance Quick Reference
1. Admin Account Security
1.1 Secure Admin Portal Access
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 5.4 |
| NIST 800-53 | AC-6(1) |
Description
Secure JumpCloud Admin Portal access with MFA and role-based access controls. Admin accounts with unrestricted access are high-value targets.
Rationale
Why This Matters:
- Admin Portal controls all identity and access settings
- Compromised admin can disable security controls
- MFA for admins is critical but often overlooked
ClickOps Implementation
Step 1: Enable Admin MFA
- Navigate to: JumpCloud Admin Portal → Security → MFA for Admins
- Enable Require MFA for Admin Portal
- Configure allowed MFA methods:
- TOTP Authenticator: Recommended
- WebAuthn: Highly recommended
- JumpCloud Go: Recommended
Step 2: Configure Admin Roles
- Navigate to: Settings → Admin Roles
- Review default roles:
- Administrator: Full access (limit to 2-3)
- Manager: User and group management
- Help Desk: Password reset, limited user view
- Read Only: View-only access
- Create custom roles for specific functions
- Assign minimum required permissions
Step 3: Audit Admin Accounts
- Navigate to: Admins
- Review all administrator accounts
- Remove unnecessary admin access
- Verify all admins have MFA enrolled
Time to Complete: ~30 minutes
Code Pack: API Script
# Require MFA for all admin portal logins
ORG_ID=$(jc_get_v1 "/organizations" | jq -r '.[0].id // empty')
if [ -z "${ORG_ID}" ]; then
fail "1.1 Unable to determine organization ID"
increment_failed; summary; exit 0
fi
CURRENT=$(jc_get_v1 "/organizations/${ORG_ID}" | jq -r '.settings.requireAdminMFA')
info "1.1 Current admin MFA requirement: ${CURRENT}"
if [ "${CURRENT}" = "true" ]; then
pass "1.1 Admin MFA already required"
increment_applied
else
info "1.1 Enabling admin MFA requirement..."
RESPONSE=$(jc_put_v1 "/organizations/${ORG_ID}" '{
"settings": {
"requireAdminMFA": true
}
}') || {
fail "1.1 Failed to enable admin MFA"
increment_failed; summary; exit 0
}
RESULT=$(echo "${RESPONSE}" | jq -r '.settings.requireAdminMFA')
[ "${RESULT}" = "true" ] && { pass "1.1 Admin MFA requirement enabled"; increment_applied; } || { fail "1.1 Admin MFA not confirmed"; increment_failed; }
fi
Code Pack: Sigma Detection Rule
detection:
selection:
event_type: 'admin_login_attempt'
filter_failure:
success: false
condition: selection and filter_failure
fields:
- timestamp
- event_type
- initiated_by.email
- client_ip
- success
- mfa_meta.type
1.2 Implement Least Privilege Administration
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 5.4 |
| NIST 800-53 | AC-6(1) |
Description
Implement tiered administration following the principle of least privilege.
Implementation
Tier 0 (Critical):
- Full Administrator role
- Limit to 2-3 trusted admins
- Require strongest MFA (WebAuthn/hardware keys)
Tier 1 (Standard Admin):
- Manager role for user/group management
- Day-to-day administration tasks
Tier 2 (Support):
- Help Desk role for password resets
- Read Only for auditors
2. Multi-Factor Authentication
2.1 Enforce Organization-Wide MFA
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 6.5 |
| NIST 800-53 | IA-2(1) |
Description
Require MFA for all user authentication to protected resources including the User Portal, applications, and systems.
Rationale
Why This Matters:
- MFA blocks 99.9% of automated attacks
- JumpCloud supports multiple MFA methods
- Organization-wide enforcement prevents gaps
ClickOps Implementation
Step 1: Enable User MFA
- Navigate to: Security → MFA
- Enable Require MFA for User Portal
- Configure enforcement:
- All Users: Recommended for most organizations
- User Groups: For phased rollout
Step 2: Configure Allowed Methods
- Select allowed MFA methods:
- TOTP: Enabled (Google Authenticator, Authy)
- WebAuthn (Security Keys): Enabled
- WebAuthn (Platform): Enabled (Touch ID, Windows Hello)
- JumpCloud Go: Enabled (recommended)
- SMS/Voice: Disable if possible (less secure)
- Set Default MFA Method preference
Step 3: Enable JumpCloud Go
- Navigate to: Security → JumpCloud Go
- Enable JumpCloud Go for passwordless authentication
- This uses device authenticators with biometrics
Time to Complete: ~20 minutes
Code Pack: API Script
# Create a conditional access policy requiring MFA for all user portal logins
EXISTING=$(jc_get_v2 "/authn/policies" | jq -r '.[] | select(.name == "HTH: Require MFA - All Users") | .id') || true
if [ -n "${EXISTING}" ]; then
info "2.1 MFA policy already exists (id: ${EXISTING})"
pass "2.1 User portal MFA policy configured"
increment_applied
else
info "2.1 Creating MFA enforcement policy..."
RESPONSE=$(jc_post_v2 "/authn/policies" '{
"name": "HTH: Require MFA - All Users",
"type": "user_portal",
"disabled": false,
"effect": {
"action": "allow",
"obligations": {
"mfa": { "required": true },
"mfaFactors": ["TOTP", "WEBAUTHN", "PUSH"],
"userVerification": { "requirement": "required" }
}
},
"targets": {
"resources": [{ "type": "user_portal" }]
}
}') || {
fail "2.1 Failed to create MFA policy"
increment_failed; summary; exit 0
}
POLICY_ID=$(echo "${RESPONSE}" | jq -r '.id')
pass "2.1 MFA policy created (id: ${POLICY_ID})"
increment_applied
fi
# Audit users without MFA enrolled
info "2.1 Auditing user MFA enrollment..."
USERS=$(jc_get_v1 "/systemusers?limit=100")
NO_MFA=$(echo "${USERS}" | jq '[.results[] | select(.totp_enabled == false and .enable_user_portal_multifactor == false)] | length')
TOTAL=$(echo "${USERS}" | jq '.totalCount')
info "2.1 Users without MFA: ${NO_MFA} / ${TOTAL}"
if [ "${NO_MFA}" -gt 0 ]; then
warn "2.1 Users without MFA enrollment:"
echo "${USERS}" | jq -r '.results[] | select(.totp_enabled == false and .enable_user_portal_multifactor == false) | " - \(.email)"'
fi
Code Pack: Sigma Detection Rule
detection:
selection:
event_type: 'organization_update'
condition: selection
fields:
- timestamp
- event_type
- initiated_by.email
- client_ip
- changes
2.2 Configure MFA for System Access
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| CIS Controls | 6.5 |
| NIST 800-53 | IA-2(1) |
Description
Require MFA for system login (Windows, macOS, Linux) and SSH access.
ClickOps Implementation
Step 1: Enable MFA for Systems
- Navigate to: Security → MFA
- Enable Require MFA for System Login
- Configure per-OS settings:
- Windows: Enable MFA at Windows logon
- macOS: Enable MFA at macOS login
- Linux: Enable MFA for SSH
Step 2: Configure SSH MFA
- For Linux systems, configure JumpCloud agent
- Enable MFA Required for SSH connections
- Users will need to complete MFA after password
3. Conditional Access
3.1 Configure Conditional Access Policies
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| CIS Controls | 6.4 |
| NIST 800-53 | AC-2(11) |
Description
Configure conditional access policies to enforce context-aware security controls based on location, device, and risk signals.
Rationale
Why This Matters:
- Context-aware access enables Zero Trust
- Block access from risky locations or devices
- Dynamically adjust MFA requirements
ClickOps Implementation
Step 1: Create Conditional Access Policy
- Navigate to: Security → Conditional Access
- Click Create New Policy
- Configure policy conditions:
- Location: Define trusted/untrusted locations
- Device Trust: Require managed devices
- User Groups: Apply to specific groups
Step 2: Define Policy Actions
- Configure actions based on conditions:
- Allow access: From trusted locations
- Require MFA: From unknown locations
- Block access: From blocked countries
- Set policy priority
Code Pack: API Script
# Create IP list for trusted corporate networks
EXISTING_LIST=$(jc_get_v2 "/iplists" | jq -r '.[] | select(.name == "HTH: Corporate IPs") | .id') || true
if [ -z "${EXISTING_LIST}" ]; then
info "3.1 Creating corporate IP list..."
IP_LIST=$(jc_post_v2 "/iplists" '{
"name": "HTH: Corporate IPs",
"description": "Trusted corporate network ranges",
"ips": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
}') || {
fail "3.1 Failed to create IP list"
increment_failed; summary; exit 0
}
EXISTING_LIST=$(echo "${IP_LIST}" | jq -r '.id')
pass "3.1 Corporate IP list created (id: ${EXISTING_LIST})"
fi
# Create policy: require MFA from non-corporate networks
EXISTING_POLICY=$(jc_get_v2 "/authn/policies" | jq -r '.[] | select(.name == "HTH: MFA Outside Corporate") | .id') || true
if [ -n "${EXISTING_POLICY}" ]; then
info "3.1 Conditional access policy already exists (id: ${EXISTING_POLICY})"
else
info "3.1 Creating conditional access policy..."
RESPONSE=$(jc_post_v2 "/authn/policies" "$(jq -n --arg list_id "${EXISTING_LIST}" '{
"name": "HTH: MFA Outside Corporate",
"type": "user_portal",
"disabled": false,
"conditions": {
"not": {
"ipAddressIn": [$list_id]
}
},
"effect": {
"action": "allow",
"obligations": {
"mfa": { "required": true },
"mfaFactors": ["TOTP", "WEBAUTHN"],
"userVerification": { "requirement": "required" }
}
},
"targets": {
"resources": [{ "type": "user_portal" }]
}
}')") || {
fail "3.1 Failed to create conditional access policy"
increment_failed; summary; exit 0
}
pass "3.1 Conditional access policy created"
fi
increment_applied
Time to Complete: ~45 minutes
Code Pack: API Script
# Create IP list for trusted corporate networks
EXISTING_LIST=$(jc_get_v2 "/iplists" | jq -r '.[] | select(.name == "HTH: Corporate IPs") | .id') || true
if [ -z "${EXISTING_LIST}" ]; then
info "3.1 Creating corporate IP list..."
IP_LIST=$(jc_post_v2 "/iplists" '{
"name": "HTH: Corporate IPs",
"description": "Trusted corporate network ranges",
"ips": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
}') || {
fail "3.1 Failed to create IP list"
increment_failed; summary; exit 0
}
EXISTING_LIST=$(echo "${IP_LIST}" | jq -r '.id')
pass "3.1 Corporate IP list created (id: ${EXISTING_LIST})"
fi
# Create policy: require MFA from non-corporate networks
EXISTING_POLICY=$(jc_get_v2 "/authn/policies" | jq -r '.[] | select(.name == "HTH: MFA Outside Corporate") | .id') || true
if [ -n "${EXISTING_POLICY}" ]; then
info "3.1 Conditional access policy already exists (id: ${EXISTING_POLICY})"
else
info "3.1 Creating conditional access policy..."
RESPONSE=$(jc_post_v2 "/authn/policies" "$(jq -n --arg list_id "${EXISTING_LIST}" '{
"name": "HTH: MFA Outside Corporate",
"type": "user_portal",
"disabled": false,
"conditions": {
"not": {
"ipAddressIn": [$list_id]
}
},
"effect": {
"action": "allow",
"obligations": {
"mfa": { "required": true },
"mfaFactors": ["TOTP", "WEBAUTHN"],
"userVerification": { "requirement": "required" }
}
},
"targets": {
"resources": [{ "type": "user_portal" }]
}
}')") || {
fail "3.1 Failed to create conditional access policy"
increment_failed; summary; exit 0
}
pass "3.1 Conditional access policy created"
fi
increment_applied
3.2 Configure Device Trust
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| CIS Controls | 4.1 |
| NIST 800-53 | AC-2(11) |
Description
Configure device trust to verify endpoint compliance before granting access to protected resources.
ClickOps Implementation
Step 1: Enable Device Trust
- Navigate to: Security → Conditional Access
- Create policy with device conditions
- Configure:
- Require JumpCloud Agent: Verify device is managed
- Require encryption: Verify disk encryption
- Require updated OS: Verify minimum OS version
Step 2: Create Device Trust Policy
- Integrate with conditional access
- Block access from non-compliant devices
- Or require additional authentication
4. Device & System Management
4.1 Configure System Policies
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 4.1 |
| NIST 800-53 | CM-7 |
Description
Configure JumpCloud system policies to enforce security settings across managed devices.
ClickOps Implementation
Step 1: Access System Policies
- Navigate to: Device Management → Policies
- Review available policy types
Step 2: Configure Security Policies Create and apply these essential policies:
Screen Lock Policy:
- Create new policy for screen lock
- Configure:
- Lock after inactivity: 5 minutes
- Require password: Yes
- Apply to all systems
Full Disk Encryption Policy:
- Create policy for FDE
- Configure:
- Windows: BitLocker
- macOS: FileVault
- Linux: LUKS
- Enforce encryption with key escrow
Firewall Policy:
- Create policy enabling firewall
- Configure:
- Windows Firewall: Enabled
- macOS Firewall: Enabled
- Apply to all systems
System Updates Policy:
- Create policy for OS updates
- Configure update schedule
- Enforce critical security patches
Time to Complete: ~1 hour
Code Pack: API Script
# List all device policies and check compliance status
POLICIES=$(jc_get_v2 "/policies") || {
fail "4.1 Unable to retrieve policies"
increment_failed; summary; exit 0
}
TOTAL_POLICIES=$(echo "${POLICIES}" | jq 'length')
info "4.1 Total device policies: ${TOTAL_POLICIES}"
# Check each policy for compliance status
for POLICY_ID in $(echo "${POLICIES}" | jq -r '.[].id'); do
POLICY_NAME=$(echo "${POLICIES}" | jq -r ".[] | select(.id == \"${POLICY_ID}\") | .name")
STATUSES=$(jc_get_v2 "/policies/${POLICY_ID}/policystatuses" 2>/dev/null) || continue
TOTAL=$(echo "${STATUSES}" | jq 'length')
COMPLIANT=$(echo "${STATUSES}" | jq '[.[] | select(.status == "success")] | length')
NON_COMPLIANT=$(echo "${STATUSES}" | jq '[.[] | select(.status != "success")] | length')
if [ "${NON_COMPLIANT}" -gt 0 ]; then
warn "4.1 Policy '${POLICY_NAME}': ${COMPLIANT}/${TOTAL} compliant (${NON_COMPLIANT} non-compliant)"
else
info "4.1 Policy '${POLICY_NAME}': ${COMPLIANT}/${TOTAL} compliant"
fi
done
pass "4.1 System policy compliance audit complete"
increment_applied
4.2 Configure LDAP & RADIUS Security
Profile Level: L2 (Hardened)
| Framework | Control |
|---|---|
| CIS Controls | 3.10 |
| NIST 800-53 | SC-8 |
Description
Secure JumpCloud’s cloud LDAP and RADIUS services for directory and network authentication.
ClickOps Implementation
Step 1: Configure Cloud LDAP
- Navigate to: Settings → Cloud LDAP
- Review bound applications
- Use dedicated service accounts for LDAP binds
- Enable TLS for LDAP connections
Step 2: Configure Cloud RADIUS
- Navigate to: Settings → Cloud RADIUS
- Configure for WiFi/VPN authentication
- Require MFA for RADIUS authentication
- Configure shared secrets securely
5. Monitoring & Detection
5.1 Enable Directory Insights
Profile Level: L1 (Baseline)
| Framework | Control |
|---|---|
| CIS Controls | 8.2 |
| NIST 800-53 | AU-2, AU-6 |
Description
Enable JumpCloud Directory Insights for comprehensive audit logging and security monitoring.
ClickOps Implementation
Step 1: Access Directory Insights
- Navigate to: Reports → Directory Insights
- Review available log types:
- Admin events
- User authentication
- System events
- SSO events
Step 2: Configure Log Export
- Navigate to: Settings → Directory Insights
- Configure SIEM integration:
- AWS S3
- Azure Blob Storage
- Webhook (generic SIEM)
- Configure retention period
Time to Complete: ~30 minutes
Code Pack: API Script
# Enable Directory Insights feature
ORG_ID=$(jc_get_v1 "/organizations" | jq -r '.[0].id // empty')
if [ -z "${ORG_ID}" ]; then
fail "5.1 Unable to determine organization ID"
increment_failed; summary; exit 0
fi
CURRENT=$(jc_get_v1 "/organizations/${ORG_ID}" | jq -r '.settings.features.directoryInsights.enabled // false')
info "5.1 Directory Insights enabled: ${CURRENT}"
if [ "${CURRENT}" != "true" ]; then
info "5.1 Enabling Directory Insights..."
jc_put_v1 "/organizations/${ORG_ID}" '{
"settings": {
"features": {
"directoryInsights": { "enabled": true }
}
}
}' || {
fail "5.1 Failed to enable Directory Insights"
increment_failed; summary; exit 0
}
pass "5.1 Directory Insights enabled"
fi
# Query recent security-relevant events
START_TIME=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-7d +%Y-%m-%dT%H:%M:%SZ)
info "5.1 Querying admin events from last 7 days..."
ADMIN_EVENTS=$(jc_insights "/events" "{
\"service\": [\"directory\"],
\"start_time\": \"${START_TIME}\",
\"limit\": 20,
\"sort\": \"DESC\",
\"search_term\": {
\"and\": [
{\"event_type\": {\"\$in\": [
\"admin_login_attempt\",
\"admin_update\",
\"admin_create\",
\"organization_update\"
]}}
]
}
}") || {
warn "5.1 Unable to query Directory Insights (may require Platform plan)"
increment_applied; summary; exit 0
}
EVENT_COUNT=$(echo "${ADMIN_EVENTS}" | jq 'length')
info "5.1 Admin/org events in last 7 days: ${EVENT_COUNT}"
if [ "${EVENT_COUNT}" -gt 0 ]; then
echo "${ADMIN_EVENTS}" | jq -r '.[:5][] | " - \(.timestamp): \(.event_type) by \(.initiated_by.email // "unknown")"'
fi
pass "5.1 Directory Insights configured and operational"
increment_applied
Code Pack: Sigma Detection Rule
detection:
selection:
event_type:
- 'admin_create'
- 'admin_update'
condition: selection
fields:
- timestamp
- event_type
- initiated_by.email
- client_ip
- changes
5.2 Key Events to Monitor
| Event Type | Detection Use Case |
|---|---|
| Admin login | Unauthorized admin access |
| Admin changes | Policy modifications |
| MFA bypass | Security control circumvention |
| Failed authentication | Brute force attempts |
| New device enrollment | Unauthorized device |
| Policy changes | Configuration drift |
6. Compliance Quick Reference
SOC 2 Trust Services Criteria Mapping
| Control ID | JumpCloud Control | Guide Section |
|---|---|---|
| CC6.1 | Admin MFA | 1.1 |
| CC6.1 | User MFA | 2.1 |
| CC6.2 | Admin roles | 1.2 |
| CC6.6 | Conditional access | 3.1 |
| CC7.2 | Directory Insights | 5.1 |
NIST 800-53 Rev 5 Mapping
| Control | JumpCloud Control | Guide Section |
|---|---|---|
| IA-2(1) | MFA enforcement | 2.1 |
| AC-6(1) | Least privilege | 1.2 |
| AC-2(11) | Conditional access | 3.1 |
| CM-7 | System policies | 4.1 |
| AU-2 | Logging | 5.1 |
Appendix A: Plan Compatibility
| Feature | Free | Core | Platform | Platform Plus |
|---|---|---|---|---|
| User MFA | ✅ (10 users) | ✅ | ✅ | ✅ |
| Admin MFA | ✅ | ✅ | ✅ | ✅ |
| Conditional Access | ❌ | ❌ | ✅ | ✅ |
| System Policies | Limited | ✅ | ✅ | ✅ |
| Directory Insights | ❌ | ❌ | ✅ | ✅ |
| JumpCloud Go | ❌ | ❌ | ✅ | ✅ |
Appendix B: References
Official JumpCloud Documentation:
- JumpCloud Security Practices
- JumpCloud Support
- Best Practices: Secure Your Organization
- MFA for Admins
- Conditional Access
API & Developer Resources:
Compliance Frameworks:
- SOC 2 Type II, ISO 27001 – via JumpCloud Security
Security Incidents:
- July 2023: JumpCloud disclosed a security incident involving a nation-state threat actor who compromised JumpCloud’s internal systems, targeting a small set of customers. JumpCloud invalidated all admin API keys and notified affected customers. The incident was attributed to a North Korean state-sponsored group.
Changelog
| Date | Version | Maturity | Changes | Author |
|---|---|---|---|---|
| 2025-02-05 | 0.1.0 | draft | Initial guide with admin security, MFA, and conditional access | Claude Code (Opus 4.5) |
Contributing
Found an issue or want to improve this guide?
- Report outdated information: Open an issue with tag
content-outdated - Propose new controls: Open an issue with tag
new-control - Submit improvements: See Contributing Guide