FedRAMP Security Testing Requirements and Automation

FedRAMP Security Testing Requirements and Automation

FedRAMP (Federal Risk and Authorization Management Program) is the U.S. federal government's standard for cloud service security. If your product serves federal agencies, FedRAMP authorization is required. The security testing requirements are among the most rigorous in any compliance framework — and a significant portion can be automated.

FedRAMP Authorization Levels

FedRAMP has three impact levels based on data sensitivity:

Level Impact if data is compromised Typical use
Low Limited adverse effect Publicly available information
Moderate Serious adverse effect Most government systems (80% of FedRAMP)
High Severe/catastrophic effect Law enforcement, financial data, health records

Most commercial cloud products pursue Moderate authorization. This guide focuses on Moderate requirements.

Security Controls: NIST SP 800-53

FedRAMP is built on NIST SP 800-53 security controls. For Moderate, you implement 325 controls across 20 control families. The families most relevant to application security:

  • AC — Access Control (26 controls)
  • AU — Audit and Accountability (16 controls)
  • IA — Identification and Authentication (13 controls)
  • SC — System and Communications Protection (44 controls)
  • SI — System and Information Integrity (23 controls)
  • CA — Assessment, Authorization, Monitoring

Continuous Monitoring Requirements

FedRAMP Moderate requires ongoing security testing, not just a one-time assessment:

  • Monthly: Automated vulnerability scanning of all system components
  • Annually: Full penetration test by a FedRAMP-accredited Third Party Assessment Organization (3PAO)
  • Ongoing: Configuration management, patching within defined windows
  • Real-time: Security event monitoring and incident detection

Automated Vulnerability Scanning

Implement scheduled scanning in CI/CD:

# .github/workflows/fedramp-continuous-monitoring.yml
name: FedRAMP Continuous Monitoring

on:
  schedule:
    - cron: '0 2 * * 1'  # Weekly, Mondays 2am (minimum monthly required)
  workflow_dispatch:

jobs:
  vulnerability-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Container image scan (Trivy)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: '${{ vars.ECR_REGISTRY }}/myapp:latest'
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'
        continue-on-error: true
      
      - name: Infrastructure scan (Trivy IaC)
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'config'
          scan-ref: './infrastructure'
          format: 'sarif'
          output: 'trivy-iac-results.sarif'
      
      - name: NIST NVD vulnerability check
        run: |
          # Check for CVEs in NIST National Vulnerability Database
          docker run --rm \
            -v $PWD:/src \
            owasp/dependency-check:latest \
            --project "MyApp" \
            --scan /src \
            --format JSON \
            --out /src/dependency-check-report.json \
            --nvdApiKey ${{ secrets.NVD_API_KEY }}
      
      - name: Parse and enforce patching SLAs
        run: |
          python3 scripts/check-patch-sla.py \
            --report dependency-check-report.json \
            --critical-days 30 \
            --high-days 60 \
            --moderate-days 90
      
      - name: Upload scan results
        uses: actions/upload-artifact@v3
        with:
          name: vulnerability-scan-${{ github.run_number }}
          path: |
            trivy-results.sarif
            trivy-iac-results.sarif
            dependency-check-report.json
          retention-days: 365  # FedRAMP requires 1 year retention

Patch SLA Enforcement

FedRAMP defines mandatory patching timelines:

# scripts/check-patch-sla.py
import json
import sys
from datetime import datetime, timezone
import argparse

def check_patch_sla(report_path, critical_days, high_days, moderate_days):
    with open(report_path) as f:
        report = json.load(f)
    
    violations = []
    
    for dependency in report.get('dependencies', []):
        for vuln in dependency.get('vulnerabilities', []):
            severity = vuln.get('severity', '').upper()
            published = vuln.get('published')
            
            if not published:
                continue
            
            pub_date = datetime.fromisoformat(published.replace('Z', '+00:00'))
            age_days = (datetime.now(timezone.utc) - pub_date).days
            
            sla_days = {
                'CRITICAL': critical_days,
                'HIGH': high_days,
                'MODERATE': moderate_days,
            }.get(severity)
            
            if sla_days and age_days > sla_days:
                violations.append({
                    'cve': vuln.get('name'),
                    'severity': severity,
                    'age_days': age_days,
                    'sla_days': sla_days,
                    'package': dependency.get('fileName'),
                })
    
    if violations:
        print(f"\nFedRAMP PATCH SLA VIOLATIONS ({len(violations)}):")
        for v in violations:
            print(f"  {v['cve']} ({v['severity']}) in {v['package']}: "
                  f"{v['age_days']} days old (SLA: {v['sla_days']} days)")
        sys.exit(1)
    else:
        print("All vulnerabilities within patch SLA windows.")

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--report', required=True)
    parser.add_argument('--critical-days', type=int, default=30)
    parser.add_argument('--high-days', type=int, default=60)
    parser.add_argument('--moderate-days', type=int, default=90)
    args = parser.parse_args()
    
    check_patch_sla(args.report, args.critical_days, args.high_days, args.moderate_days)

Security Control Testing

AC-2: Account Management

# test/fedramp/test_account_management.py
"""FedRAMP AC-2 Account Management controls"""

class TestAC2AccountManagement:
    def test_account_creation_requires_approval(self, client):
        """AC-2(a): Account creation must go through defined process"""
        response = client.post('/api/admin/users', json={
            'email': 'newuser@agency.gov',
            'role': 'user'
        }, headers={'Authorization': 'Bearer invalid-token'})
        assert response.status_code == 401
    
    def test_inactive_accounts_disabled(self, db):
        """AC-2(3): Disable accounts inactive for 90 days"""
        from src.jobs import disable_inactive_accounts
        
        # Run the job
        result = disable_inactive_accounts(inactive_days=90)
        
        # Verify accounts inactive >90 days are now disabled
        inactive_active = db.query("""
            SELECT COUNT(*) as count FROM users 
            WHERE last_login < NOW() - INTERVAL '90 days'
            AND active = true
        """)[0]['count']
        
        assert inactive_active == 0, \
            f"FedRAMP AC-2(3): {inactive_active} accounts inactive >90 days still active"
    
    def test_account_review_mechanism_exists(self):
        """AC-2(j): Accounts must be reviewed periodically"""
        from src.compliance import get_last_account_review
        
        last_review = get_last_account_review()
        days_since = (datetime.now(timezone.utc) - last_review).days
        
        assert days_since <= 365, \
            f"FedRAMP AC-2(j): Last account review was {days_since} days ago (annual required)"

IA-5: Authenticator Management

# test/fedramp/test_authentication.py
"""FedRAMP IA-5 Authenticator Management"""

class TestIA5AuthenticatorManagement:
    def test_password_complexity_enforced(self, client):
        """IA-5(1)(a): Minimum password complexity"""
        weak_passwords = [
            'password',
            '12345678',
            'abc123',
            'Pa$$w0rd',  # Common pattern
        ]
        
        for password in weak_passwords:
            response = client.post('/api/auth/change-password', json={
                'current_password': 'ValidP@ssw0rd123!',
                'new_password': password
            }, headers={'Authorization': f'Bearer {self.user_token}'})
            assert response.status_code in (400, 422), \
                f"FedRAMP IA-5(1)(a): Weak password accepted: {password}"
    
    def test_password_minimum_length(self, client):
        """IA-5(1)(a): Minimum 12 characters for Moderate baseline"""
        response = client.post('/api/auth/change-password', json={
            'current_password': 'ValidP@ssw0rd123!',
            'new_password': 'Short1!'
        })
        assert response.status_code in (400, 422)
    
    def test_mfa_required_for_privileged_access(self, client):
        """IA-5(11): Hardware/software MFA for privileged accounts"""
        response = client.post('/api/admin/users', json={}, headers={
            'Authorization': f'Bearer {self.admin_token_without_mfa}'
        })
        assert response.status_code in (401, 403), \
            "FedRAMP IA-5(11): Admin access without MFA must be blocked"
    
    def test_session_timeout_enforced(self, client):
        """AC-12: Sessions must timeout after inactivity"""
        from src.auth import create_session_with_age
        
        # Create a session that's been inactive for 30 minutes
        old_token = create_session_with_age(minutes=31)
        
        response = client.get('/api/data', headers={
            'Authorization': f'Bearer {old_token}'
        })
        assert response.status_code == 401, \
            "FedRAMP AC-12: Inactive session not terminated after 30 minutes"

SC-8: Transmission Confidentiality and Integrity

def test_tls_configuration():
    """SC-8: All communications must use TLS 1.2+ with approved cipher suites"""
    import ssl
    import socket
    
    hostname = 'myapp.example.gov'
    context = ssl.create_default_context()
    
    with socket.create_connection((hostname, 443)) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            protocol = ssock.version()
            cipher = ssock.cipher()
    
    assert protocol in ('TLSv1.2', 'TLSv1.3'), \
        f"FedRAMP SC-8: TLS {protocol} not approved (must be 1.2 or 1.3)"
    
    # Check cipher suite is in NIST-approved list
    approved_ciphers = [
        'ECDHE-RSA-AES256-GCM-SHA384',
        'ECDHE-RSA-AES128-GCM-SHA256',
        'TLS_AES_256_GCM_SHA384',
        'TLS_AES_128_GCM_SHA256',
    ]
    assert cipher[0] in approved_ciphers, \
        f"FedRAMP SC-8: Cipher {cipher[0]} not in approved list"

Plan of Action and Milestones (POA&M)

FedRAMP requires tracking open vulnerabilities in a Plan of Action and Milestones (POA&M). Automate POA&M updates from your scanning tools:

# scripts/update-poam.py
"""
Updates FedRAMP POA&M from vulnerability scan results.
"""
import json
from datetime import datetime, timezone, timedelta

def severity_to_sla(severity):
    return {
        'CRITICAL': 30,
        'HIGH': 60,
        'MODERATE': 90,
        'LOW': 180,
    }.get(severity.upper(), 180)

def update_poam(scan_results_path, poam_path):
    with open(scan_results_path) as f:
        scan = json.load(f)
    
    try:
        with open(poam_path) as f:
            poam = json.load(f)
    except FileNotFoundError:
        poam = {'items': []}
    
    existing_cves = {item['cve'] for item in poam['items']}
    
    for vuln in scan.get('vulnerabilities', []):
        cve = vuln.get('id')
        if cve in existing_cves:
            continue  # Already tracked
        
        severity = vuln.get('severity', 'LOW')
        discovered = datetime.now(timezone.utc)
        due_date = discovered + timedelta(days=severity_to_sla(severity))
        
        poam['items'].append({
            'id': f"POA&M-{len(poam['items']) + 1:04d}",
            'cve': cve,
            'severity': severity,
            'description': vuln.get('description'),
            'affected_component': vuln.get('component'),
            'discovered_date': discovered.isoformat(),
            'scheduled_completion': due_date.isoformat(),
            'status': 'Open',
            'milestone': f"Apply vendor patch by {due_date.strftime('%Y-%m-%d')}",
        })
    
    poam['last_updated'] = datetime.now(timezone.utc).isoformat()
    
    with open(poam_path, 'w') as f:
        json.dump(poam, f, indent=2)
    
    print(f"POA&M updated: {len(poam['items'])} total items")

Summary

FedRAMP security testing has three tracks:

  1. Continuous monitoring — automated monthly vulnerability scanning with SLA enforcement
  2. Security control testing — automated tests for NIST 800-53 controls (access control, authentication, encryption)
  3. POA&M management — automated tracking of open vulnerabilities with remediation deadlines

The annual 3PAO penetration test is unavoidable and must be conducted by an accredited assessor. But the 80% of FedRAMP compliance work that happens between assessments — continuous monitoring, patch management, access control verification — can and should be automated to reduce manual effort and ensure nothing slips between annual reviews.

Read more