Skip to content

Latest commit

 

History

History
831 lines (700 loc) · 22.1 KB

File metadata and controls

831 lines (700 loc) · 22.1 KB

Security Hub Findings API Documentation

Overview

The Security Hub Findings API provides endpoints for retrieving AWS Security Hub findings and managing analyst session tracking. The API is built on AWS Lambda and API Gateway with Cognito JWT authentication.

Base URL

https://rshuboi4oh.execute-api.us-east-1.amazonaws.com/prod

Authentication

All API endpoints require JWT ID token authentication via the Authorization header:

Authorization: Bearer {jwt-id-token}

Token Requirements

  • Token Type: JWT ID Token (not access token)
  • Issuer: AWS Cognito User Pool (us-east-1_4QCl4RqHO)
  • Audience: Cognito Client ID (4a2vb2k79v0niig5o1vj6vev00)
  • Required Claims: email, sub, token_use: "id"

Endpoints Summary

Method Path Description
GET /findings Retrieve all Security Hub findings
PUT /findings Update workflow status for a finding
OPTIONS /findings CORS preflight
GET /findings/{findingId}/details Get specific finding by ID
GET /findings/{findingId}/session/status Check session status for a finding
POST /findings/{findingId}/session/start Start analyst session
POST /findings/{findingId}/session/end End analyst session
GET /open-cases Get all workflow tracker records
OPTIONS /open-cases CORS preflight
GET /{proxy+} Proxy route (handled by Lambda)
POST /{proxy+} Proxy route (handled by Lambda)
OPTIONS /{proxy+} CORS preflight for proxy routes

Endpoints

1. Get Security Hub Findings

Retrieve all Security Hub findings with optional session information.

GET /findings

Headers

Authorization: Bearer {jwt-id-token}
Content-Type: application/json

Response

Success (200 OK):

{
  "findings": [
    {
      "id": "f64d5ee8-6f7a-48e8-9b59-12a750361",
      "title": "S3.13 S3 buckets should have lifecycle configuration",
      "severity": "HIGH",
      "status": "NEW",
      "description": "This control checks whether Amazon S3 buckets have lifecycle configuration.",
      "complianceStatus": "FAILED",
      "recordState": "ACTIVE",
      "workflowState": "NEW",
      "createdAt": "2024-12-29T08:15:30Z",
      "updatedAt": "2024-12-29T08:15:30Z",
      "resources": [
        {
          "id": "arn:aws:s3:::example-bucket",
          "type": "AwsS3Bucket",
          "region": "us-east-1",
          "partition": "aws",
          "details": {
            "AwsS3Bucket": {
              "name": "example-bucket",
              "ownerId": "641484007123"
            }
          }
        }
      ],
      "remediation": {
        "recommendation": {
          "text": "Configure lifecycle rules for S3 bucket",
          "url": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html"
        }
      },
      "activeSession": {
        "analystEmail": "analyst@company.com",
        "openedAt": "2024-12-29T10:30:00Z"
      }
    },
    {
      "id": "another-finding-id",
      "title": "EC2.1 Amazon EBS snapshots should not be public",
      "severity": "CRITICAL",
      "status": "NEW",
      "description": "This control checks whether Amazon EBS snapshots are restorable by everyone.",
      "complianceStatus": "FAILED",
      "recordState": "ACTIVE",
      "workflowState": "NEW",
      "createdAt": "2024-12-29T09:20:15Z",
      "updatedAt": "2024-12-29T09:20:15Z",
      "resources": [...],
      "remediation": {...}
      // No activeSession field - no analyst currently reviewing
    }
  ],
  "totalCount": 367,
  "timestamp": "2024-12-29T12:45:30Z",
  "requestId": "req-20241229124530"
}

Error Responses:

// Unauthorized (401)
{
  "message": "Unauthorized"
}

// Internal Server Error (500)
{
  "statusCode": 500,
  "error": "Internal server error",
  "details": "Error retrieving findings from Security Hub"
}

Session Information

  • activeSession: Present only if an analyst is currently reviewing the finding
  • analystEmail: Email of the analyst who opened the session
  • openedAt: ISO 8601 timestamp when the session was started
  • Missing activeSession: Indicates no analyst is currently reviewing the finding

2. Get Open Cases

Retrieve all workflow tracker records (active and resolved sessions).

GET /open-cases

Headers

Authorization: Bearer {jwt-id-token}
Content-Type: application/json

Response

Success (200 OK):

{
  "cases": [
    {
      "finding_id": "f64d5ee8-6f7a-48e8-9b59-12a750361",
      "opener_email": "analyst@company.com",
      "open_timestamp": "2024-12-29T10:30:00Z",
      "resolver_email": "",
      "resolution_timestamp": "",
      "ttl": 1735689000
    },
    {
      "finding_id": "another-finding-id",
      "opener_email": "analyst2@company.com",
      "open_timestamp": "2024-12-29T09:15:00Z",
      "resolver_email": "analyst2@company.com",
      "resolution_timestamp": "2024-12-29T11:00:00Z",
      "ttl": 1735689000
    }
  ],
  "count": 2,
  "timestamp": "2024-12-29T12:45:30Z",
  "requestId": "req-20241229124530"
}

Error Responses:

// Unauthorized (401)
{
  "message": "Unauthorized"
}

// Internal Server Error (500)
{
  "error": "Internal Server Error",
  "message": "Failed to fetch open cases: {error details}"
}

Open Cases Features

  • All Records: Returns all DynamoDB workflow tracker records
  • Session Status: Records without resolver_email are active sessions
  • Audit Trail: Resolved sessions include resolver and resolution timestamp
  • TTL: Records have automatic cleanup via DynamoDB TTL

3. Start Session Tracking

Start tracking an analyst session for a specific finding.

POST /findings/{findingId}/session/start

Path Parameters

  • findingId: The Security Hub finding ID (extracted from ARN)

Headers

Authorization: Bearer {jwt-id-token}
Content-Type: application/json

Request Body

{
  "findingId": "f64d5ee8-6f7a-48e8-9b59-12a750361",
  "analystEmail": "analyst@company.com",
  "timestamp": "2024-12-29T10:30:00Z"
}

Request Body Fields

  • findingId: Must match the path parameter
  • analystEmail: Email address of the analyst (extracted from JWT token)
  • timestamp: ISO 8601 timestamp when session started

Response

Success (200 OK):

{
  "statusCode": 200,
  "message": "Session started successfully",
  "sessionData": {
    "findingId": "f64d5ee8-6f7a-48e8-9b59-12a750361",
    "analystEmail": "analyst@company.com",
    "openTimestamp": "2024-12-29T10:30:00Z"
  }
}

Error Responses:

// Bad Request (400)
{
  "statusCode": 400,
  "error": "Invalid finding ID format"
}

// Conflict (409)
{
  "statusCode": 409,
  "error": "Session already exists for this finding",
  "existingSession": {
    "analystEmail": "other-analyst@company.com",
    "openedAt": "2024-12-29T09:15:00Z"
  }
}

// Internal Server Error (500)
{
  "statusCode": 500,
  "error": "Failed to create session record"
}

4. End Session Tracking

End an analyst session and record resolution information.

POST /findings/{findingId}/session/end

Path Parameters

  • findingId: The Security Hub finding ID

Headers

Authorization: Bearer {jwt-id-token}
Content-Type: application/json

Request Body

{
  "findingId": "f64d5ee8-6f7a-48e8-9b59-12a750361",
  "status": "RESOLVED",
  "resolverEmail": "analyst@company.com",
  "timestamp": "2024-12-29T11:45:00Z"
}

Request Body Fields

  • findingId: Must match the path parameter
  • status: Resolution status ("RESOLVED" or "SUPPRESSED")
  • resolverEmail: Email of analyst resolving the finding
  • timestamp: ISO 8601 timestamp when session ended

Response

Success (200 OK):

{
  "statusCode": 200,
  "message": "Session ended successfully",
  "sessionData": {
    "findingId": "f64d5ee8-6f7a-48e8-9b59-12a750361",
    "openerEmail": "analyst@company.com",
    "openTimestamp": "2024-12-29T10:30:00Z",
    "resolverEmail": "analyst@company.com",
    "resolutionTimestamp": "2024-12-29T11:45:00Z",
    "status": "RESOLVED"
  }
}

Error Responses:

// Bad Request (400)
{
  "statusCode": 400,
  "error": "Invalid status. Must be RESOLVED or SUPPRESSED"
}

// Not Found (404)
{
  "statusCode": 404,
  "error": "No active session found for this finding"
}

// Internal Server Error (500)
{
  "statusCode": 500,
  "error": "Failed to update session record"
}

5. Update Workflow Status

Update the workflow status for a specific finding.

PUT /findings

Headers

Authorization: Bearer {jwt-id-token}
Content-Type: application/json

Request Body

{
  "findingId": "arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/S3.13/finding/f64d5ee8-6f7a-48e8-9b59-12a750361",
  "workflowStatus": "RESOLVED",
  "resolverEmail": "analyst@company.com"
}

Request Body Fields

  • findingId: The full Security Hub finding ARN or extracted ID
  • workflowStatus: New workflow status ("NEW", "NOTIFIED", "RESOLVED", "SUPPRESSED")
  • resolverEmail: (Optional) Email of analyst resolving the finding

Response

Success (200 OK):

{
  "success": true,
  "message": "Workflow status updated successfully",
  "findingId": "f64d5ee8-6f7a-48e8-9b59-12a750361",
  "workflowStatus": "RESOLVED"
}

Error Responses:

// Bad Request (400)
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "findingId is required in request body"
}

// Bad Request (400)
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "workflowStatus is required in request body"
}

6. Get Finding Details

Retrieve details for a specific finding by ID.

GET /findings/{findingId}/details

Path Parameters

  • findingId: The Security Hub finding ID (URL-encoded if contains special characters)

Headers

Authorization: Bearer {jwt-id-token}
Content-Type: application/json

Response

Success (200 OK):

{
  "finding": {
    "id": "f64d5ee8-6f7a-48e8-9b59-12a750361",
    "title": "S3.13 S3 buckets should have lifecycle configuration",
    "severity": "HIGH",
    "status": "NEW",
    "description": "This control checks whether Amazon S3 buckets have lifecycle configuration.",
    "complianceStatus": "FAILED",
    "recordState": "ACTIVE",
    "workflowState": "NEW",
    "createdAt": "2024-12-29T08:15:30Z",
    "updatedAt": "2024-12-29T08:15:30Z",
    "resources": [...],
    "remediation": {...},
    "activeSession": {
      "analystEmail": "analyst@company.com",
      "openedAt": "2024-12-29T10:30:00Z"
    }
  },
  "found": true,
  "timestamp": "2024-12-29T12:45:30Z",
  "requestId": "req-20241229124530"
}

Error Responses:

// Not Found (404)
{
  "finding": null,
  "found": false,
  "message": "Finding not found",
  "timestamp": "2024-12-29T12:45:30Z",
  "requestId": "req-20241229124530"
}

// Bad Request (400)
{
  "error": "Bad Request",
  "message": "Invalid path format"
}

7. Get Session Status

Check the session status for a specific finding.

GET /findings/{findingId}/session/status

Path Parameters

  • findingId: The Security Hub finding ID

Headers

Authorization: Bearer {jwt-id-token}
Content-Type: application/json

Response

Success - Active Session (200 OK):

{
  "hasActiveSession": true,
  "sessionExists": true,
  "openerEmail": "analyst@company.com",
  "openTimestamp": "2024-12-29T10:30:00Z",
  "resolverEmail": "",
  "resolutionTimestamp": "",
  "findingId": "f64d5ee8-6f7a-48e8-9b59-12a750361"
}

Success - Resolved Session (200 OK):

{
  "hasActiveSession": false,
  "sessionExists": true,
  "openerEmail": "analyst@company.com",
  "openTimestamp": "2024-12-29T10:30:00Z",
  "resolverEmail": "analyst@company.com",
  "resolutionTimestamp": "2024-12-29T11:45:00Z",
  "findingId": "f64d5ee8-6f7a-48e8-9b59-12a750361"
}

Success - No Session (200 OK):

{
  "hasActiveSession": false,
  "sessionExists": false,
  "findingId": "f64d5ee8-6f7a-48e8-9b59-12a750361"
}

Error Responses:

// Bad Request (400)
{
  "error": "Bad Request",
  "message": "Invalid path format"
}

// Internal Server Error (500)
{
  "error": "Internal Server Error",
  "message": "Failed to check session status"
}

8. CORS Preflight (OPTIONS)

Handle CORS preflight requests for all endpoints.

OPTIONS /findings
OPTIONS /open-cases
OPTIONS /{proxy+}

Response

Success (200 OK):

Headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Accept,Origin,Referer
Access-Control-Allow-Methods: GET,PUT,POST,OPTIONS,DELETE
Access-Control-Allow-Credentials: false

9. Proxy Routes

Catch-all proxy routes that forward requests to the Lambda function.

GET /{proxy+}
POST /{proxy+}
OPTIONS /{proxy+}

These routes handle any paths not explicitly defined, allowing the Lambda function to process custom routes dynamically.

Data Models

Finding Object

interface Finding {
  id: string;                    // Extracted from Security Hub ARN
  title: string;                 // Finding title
  severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
  status: 'NEW' | 'NOTIFIED' | 'RESOLVED' | 'SUPPRESSED';
  description: string;           // Finding description
  complianceStatus: 'PASSED' | 'FAILED' | 'WARNING' | 'NOT_AVAILABLE';
  recordState: 'ACTIVE' | 'ARCHIVED';
  workflowState: 'NEW' | 'ASSIGNED' | 'IN_PROGRESS' | 'DEFERRED' | 'RESOLVED';
  createdAt: string;            // ISO 8601 timestamp
  updatedAt: string;            // ISO 8601 timestamp
  resources: Resource[];         // Associated AWS resources
  remediation?: Remediation;     // Remediation recommendations
  activeSession?: ActiveSession; // Current analyst session (if any)
}

Active Session Object

interface ActiveSession {
  analystEmail: string;    // Email of reviewing analyst
  openedAt: string;       // ISO 8601 timestamp when session started
}

Session Record (DynamoDB)

interface SessionRecord {
  finding_id: string;           // Primary key
  opener_email: string;         // Analyst who opened the session
  open_timestamp: string;       // ISO 8601 timestamp
  resolver_email?: string;      // Analyst who resolved (if resolved)
  resolution_timestamp?: string; // ISO 8601 timestamp (if resolved)
  ttl: number;                 // Unix timestamp for automatic cleanup
}

Error Handling

HTTP Status Codes

  • 200 OK: Successful request
  • 400 Bad Request: Invalid request parameters or body
  • 401 Unauthorized: Missing or invalid JWT token
  • 404 Not Found: Resource not found
  • 409 Conflict: Resource conflict (e.g., session already exists)
  • 500 Internal Server Error: Server-side error

Error Response Format

{
  "statusCode": number,
  "error": string,
  "details": string,        // Optional additional details
  "timestamp": string       // ISO 8601 timestamp
}

Graceful Degradation

  • If DynamoDB is unavailable, /findings endpoint returns findings without session data
  • Session tracking failures don't affect core finding retrieval functionality
  • All errors are logged to CloudWatch for monitoring and debugging

Rate Limits

  • Findings Endpoint: No explicit rate limit (protected by API Gateway throttling)
  • Session Endpoints: Designed for human interaction patterns (low frequency)
  • API Gateway Throttling: Default AWS API Gateway limits apply

Monitoring and Logging

CloudWatch Logs

  • API Gateway Access Logs: Request/response logging
  • Lambda Function Logs: Detailed execution logging with correlation IDs
  • Error Tracking: All errors logged with context for debugging

X-Ray Tracing

  • Distributed Tracing: Full request flow from API Gateway → Lambda → DynamoDB/Security Hub
  • Performance Monitoring: Latency and error analysis
  • Service Map: Visual representation of service dependencies

Metrics

  • Request Count: Number of API requests per endpoint
  • Error Rate: Percentage of failed requests
  • Latency: Response time percentiles (p50, p95, p99)
  • Session Activity: Number of active sessions and session duration

Security Considerations

Authentication Security

  • JWT Validation: All tokens validated against Cognito User Pool
  • Token Expiration: ID tokens expire after 1 hour
  • Scope Validation: Tokens must include required claims (email, sub)

Data Security

  • Encryption in Transit: All API communication over HTTPS
  • Encryption at Rest: DynamoDB encryption enabled
  • Access Control: IAM roles with minimal required permissions
  • Audit Trail: Complete session tracking for compliance

Input Validation

  • Finding ID Format: Validated against expected ARN format
  • Email Format: Validated against standard email format
  • Timestamp Format: Validated as ISO 8601 format
  • Status Values: Restricted to allowed values (RESOLVED, SUPPRESSED)

Integration Examples

Frontend Integration (TypeScript/Angular)

// Session Tracking Service
@Injectable()
export class SessionTrackingService {
  constructor(private http: HttpClient) {}

  startSession(findingId: string): Observable<any> {
    const payload = {
      findingId,
      analystEmail: this.getAnalystEmail(),
      timestamp: new Date().toISOString()
    };
    
    return this.http.post(
      `/findings/${findingId}/session/start`,
      payload
    );
  }

  endSession(findingId: string, status: string): Observable<any> {
    const payload = {
      findingId,
      status,
      resolverEmail: this.getAnalystEmail(),
      timestamp: new Date().toISOString()
    };
    
    return this.http.post(
      `/findings/${findingId}/session/end`,
      payload
    );
  }

  private getAnalystEmail(): string {
    // Extract email from JWT token
    const token = localStorage.getItem('id_token');
    const payload = JSON.parse(atob(token.split('.')[1]));
    return payload.email;
  }
}

Backend Integration (Python/Lambda)

import json
import boto3
from datetime import datetime

class SessionTracker:
    def __init__(self):
        self.dynamodb = boto3.resource('dynamodb')
        self.table = self.dynamodb.Table('workflow_tracker')
    
    def start_session(self, finding_id: str, analyst_email: str) -> dict:
        """Start a new analyst session"""
        try:
            response = self.table.put_item(
                Item={
                    'finding_id': finding_id,
                    'opener_email': analyst_email,
                    'open_timestamp': datetime.utcnow().isoformat(),
                    'ttl': int(datetime.utcnow().timestamp()) + (30 * 24 * 3600)  # 30 days
                },
                ConditionExpression='attribute_not_exists(finding_id)'
            )
            return {'statusCode': 200, 'message': 'Session started successfully'}
        except ClientError as e:
            if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
                return {'statusCode': 409, 'error': 'Session already exists'}
            raise
    
    def end_session(self, finding_id: str, resolver_email: str, status: str) -> dict:
        """End an existing session"""
        try:
            response = self.table.update_item(
                Key={'finding_id': finding_id},
                UpdateExpression='SET resolver_email = :resolver, resolution_timestamp = :timestamp',
                ExpressionAttributeValues={
                    ':resolver': resolver_email,
                    ':timestamp': datetime.utcnow().isoformat()
                },
                ConditionExpression='attribute_exists(finding_id) AND attribute_not_exists(resolution_timestamp)'
            )
            return {'statusCode': 200, 'message': 'Session ended successfully'}
        except ClientError as e:
            if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
                return {'statusCode': 404, 'error': 'No active session found'}
            raise

Testing

Unit Testing

# Test session creation
def test_session_creation():
    tracker = SessionTracker()
    result = tracker.start_session('test-finding-id', 'test@example.com')
    assert result['statusCode'] == 200

# Test session conflict
def test_session_conflict():
    tracker = SessionTracker()
    tracker.start_session('test-finding-id', 'analyst1@example.com')
    result = tracker.start_session('test-finding-id', 'analyst2@example.com')
    assert result['statusCode'] == 409

Integration Testing

# Test API endpoints
curl -X POST \
  https://rshuboi4oh.execute-api.us-east-1.amazonaws.com/prod/findings/test-id/session/start \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"findingId":"test-id","analystEmail":"test@example.com","timestamp":"2024-12-29T10:30:00Z"}'

Changelog

Version 2.3.0 (January 2025)

  • Added Open Cases endpoint (/open-cases) for active session management
  • Enhanced session tracking with duration calculations
  • Improved navigation and user experience with dedicated Open Cases dashboard
  • Added comprehensive session analytics and workload distribution features

Version 2.2.0 (January 2025)

  • Fixed finding ID resolution for complete Security Hub ARNs
  • Added URL decoding for proper finding ID handling
  • Eliminated dependency on "last 100 findings" window for finding details
  • Improved API performance with direct Security Hub queries

Version 2.1.0 (January 2025)

  • Added session tracking endpoints (/session/start, /session/end)
  • Enhanced /findings endpoint with active session information
  • Implemented DynamoDB-based session storage with TTL
  • Added comprehensive error handling and graceful degradation
  • Integrated with existing JWT authentication system

Version 2.0.0 (December 2024)

  • Initial API implementation with Security Hub findings retrieval
  • JWT authentication with Cognito User Pool integration
  • Professional Angular frontend with AWS Console-style UI
  • Comprehensive monitoring and logging with CloudWatch and X-Ray