Files
cve-dashboard/.kiro/specs/atlas-action-plans/tasks.md
root 4c04c9870a Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.

Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync

Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row

Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00

263 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Implementation Plan: Atlas Action Plans Integration
## Overview
Integrate the Atlas InfoSec action plans API into the STEAM Security Dashboard. The implementation follows the existing proxy-and-cache pattern — backend helper for HTTP communication, SQLite cache for fast page loads, Express routes for proxied CRUD, and React frontend components for badge display and plan management. Tasks are ordered for incremental progress: environment config, backend helper, migration, routes, server wiring, then frontend components.
## Tasks
- [x] 1. Add Atlas environment variables to `.env.example`
- Append `ATLAS_API_URL`, `ATLAS_API_USER`, `ATLAS_API_PASS`, and `ATLAS_SKIP_TLS` to `backend/.env.example` with descriptive comments, following the existing Ivanti variable block pattern
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
- [ ] 2. Implement Atlas API helper module
- [x] 2.1 Create `backend/helpers/atlasApi.js` with `atlasRequest`, `atlasGet`, `atlasPut`, `atlasPatch`, `atlasPost` functions
- Read `ATLAS_API_URL`, `ATLAS_API_USER`, `ATLAS_API_PASS`, `ATLAS_SKIP_TLS` from `process.env` at module load
- Compute `Authorization: Basic <base64(user:pass)>` header at request time
- Use Node.js `https` module following the `ivantiApi.js` pattern
- Support GET, PUT, PATCH, POST methods with `rejectUnauthorized` controlled by `ATLAS_SKIP_TLS`
- Default timeout 15s for single-host endpoints, 60s via `options.timeout` for bulk
- Resolve non-2xx responses with `{ status, body }` without throwing
- Reject on network errors/timeouts with a message containing the HTTP method and URL path
- Log a warning at module load if required env vars are missing
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8_
- [ ] 2.2 Write property test: Basic Auth header round-trip
- **Property 1: Basic Auth header round-trip**
- Generate random (username, password) string pairs, verify base64 decode yields `username:password`
- **Validates: Requirements 1.2**
- [ ] 2.3 Write property test: Non-2xx responses resolve with status and body
- **Property 2: Non-2xx responses resolve with status and body**
- Generate integers 400599 and random body strings, verify promise resolves with `{ status, body }`
- **Validates: Requirements 1.7**
- [ ] 2.4 Write property test: Error messages contain method and path
- **Property 3: Error messages contain method and path**
- Generate random method names and URL path strings, verify rejection message includes both
- **Validates: Requirements 1.8**
- [x] 3. Checkpoint — Verify Atlas helper module
- Ensure all tests pass, ask the user if questions arise.
- [ ] 4. Create Atlas cache migration
- [x] 4.1 Create `backend/migrations/add_atlas_action_plans_cache.js`
- Follow the existing migration pattern from `add_ivanti_findings_tables.js`: open `backend/cve_database.db`, use `db.serialize()`, log progress, close on completion
- Create `atlas_action_plans_cache` table with columns: `id` (INTEGER PRIMARY KEY AUTOINCREMENT), `host_id` (INTEGER NOT NULL UNIQUE), `has_action_plan` (INTEGER NOT NULL DEFAULT 0), `plan_count` (INTEGER NOT NULL DEFAULT 0), `plans_json` (TEXT NOT NULL DEFAULT '[]'), `synced_at` (DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP)
- Create index `idx_atlas_cache_host_id` on `host_id`
- Use `CREATE TABLE IF NOT EXISTS` and `CREATE INDEX IF NOT EXISTS` for idempotency
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_
- [ ]* 4.2 Write unit tests for migration idempotency
- Verify migration runs twice without errors
- Verify table and index exist after migration
- _Requirements: 2.5_
- [ ] 5. Implement Atlas router with all endpoints
- [x] 5.1 Create `backend/routes/atlas.js` with `createAtlasRouter(db, requireAuth)` factory function
- Import `requireGroup` from `../middleware/auth`, `logAudit` from `../helpers/auditLog`, and Atlas helper functions from `../helpers/atlasApi`
- Check Atlas helper availability; return 503 if Atlas is not configured
- _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7_
- [x] 5.2 Implement `GET /status` endpoint
- Require authentication (any group)
- Return all rows from `atlas_action_plans_cache` as JSON array with `host_id`, `has_action_plan`, `plan_count`, `synced_at`
- _Requirements: 4.1, 4.2, 4.3, 10.1_
- [x] 5.3 Implement `POST /sync` endpoint
- Require authentication and Admin or Standard_User group
- Extract unique non-null `hostId` values from `ivanti_findings_cache.findings_json`
- Call `atlasGet('/hosts/' + hostId + '/action-plans')` for each host with concurrency limit of 5 using `Promise.allSettled`
- On 2xx: upsert cache row with `plan_count`, `has_action_plan`, `plans_json`, and current timestamp
- On non-2xx: increment failure counter, log warning, continue
- Return `{ synced, withPlans, failed }` summary
- Log audit entry with initiating user and result summary
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 10.3_
- [x] 5.4 Implement `GET /hosts/:hostId/action-plans` proxy endpoint
- Require authentication (any group)
- Validate `hostId` is a positive integer
- Proxy to Atlas API `GET /hosts/{host_id}/action-plans` and return the response
- Forward non-2xx Atlas responses to the client
- _Requirements: 5.1, 5.12, 10.2_
- [x] 5.5 Implement `PUT /hosts/:hostId/action-plans` proxy endpoint
- Require authentication and Admin or Standard_User group
- Validate `hostId` is a positive integer
- Validate `plan_type` is one of: decommission, remediation, false_positive, risk_acceptance, scan_exclusion
- Validate `commit_date` is present and matches `YYYY-MM-DD` format
- Proxy validated body to Atlas API `PUT /hosts/{host_id}/action-plans`
- Log audit entry with user, hostId, and plan type
- _Requirements: 5.2, 5.3, 5.4, 5.5, 5.12, 5.13, 10.4_
- [x] 5.6 Implement `PATCH /hosts/:hostId/action-plans` proxy endpoint
- Require authentication and Admin or Standard_User group
- Validate `action_plan_id` is a non-empty string and `updates` is a non-null object
- Proxy validated body to Atlas API `PATCH /hosts/{host_id}/action-plans`
- Log audit entry with user, hostId, and plan type
- _Requirements: 5.6, 5.7, 5.8, 5.12, 5.13, 10.5_
- [x] 5.7 Implement `POST /hosts/bulk-action-plans` proxy endpoint
- Require authentication and Admin or Standard_User group
- Validate `host_ids` is a non-empty array of positive integers, `plan_type` is valid, `commit_date` matches `YYYY-MM-DD`
- Proxy validated body to Atlas API `POST /hosts/create-bulk-action-plans`
- _Requirements: 5.9, 5.10, 5.11, 5.12, 10.6_
- [ ] 5.8 Write property test: Unique host ID extraction
- **Property 4: Unique host ID extraction**
- Generate arrays of finding objects with optional numeric `hostId` fields, verify extracted set has no duplicates and no nulls
- **Validates: Requirements 3.2**
- [ ] 5.9 Write property test: Cache upsert derives correct plan_count and has_action_plan
- **Property 5: Cache upsert derives correct plan_count and has_action_plan**
- Generate (hostId, planArray) pairs, verify `plan_count` equals array length and `has_action_plan` equals 1 if non-empty, 0 otherwise
- **Validates: Requirements 3.4**
- [ ] 5.10 Write property test: Sync response count invariant
- **Property 6: Sync response count invariant**
- Generate (totalHosts, failureCount) pairs, verify `synced + failed = totalHosts` and `withPlans <= synced`
- **Validates: Requirements 3.6**
- [ ] 5.11 Write property test: Status endpoint returns all cached rows with required fields
- **Property 7: Status endpoint returns all cached rows with required fields**
- Generate N cache rows, insert into DB, verify response count and field presence
- **Validates: Requirements 4.2, 4.3**
- [ ] 5.12 Write property test: plan_type validation
- **Property 8: plan_type validation**
- Generate random strings, verify acceptance if and only if string is one of the five valid types
- **Validates: Requirements 5.3**
- [ ] 5.13 Write property test: commit_date validation
- **Property 9: commit_date validation**
- Generate random strings, verify acceptance if and only if string matches `YYYY-MM-DD` pattern
- **Validates: Requirements 5.4**
- [ ] 5.14 Write property test: PATCH body validation
- **Property 10: PATCH body validation**
- Generate random objects with varying field presence, verify acceptance if and only if `action_plan_id` is a non-empty string and `updates` is a non-null object
- **Validates: Requirements 5.7**
- [ ] 5.15 Write property test: Bulk request validation
- **Property 11: Bulk request validation**
- Generate objects with varying `host_ids`, `plan_type`, `commit_date`, verify acceptance matches combined validation rules
- **Validates: Requirements 5.10**
- [ ]* 5.16 Write property test: Non-2xx Atlas response passthrough
- **Property 12: Non-2xx Atlas response passthrough**
- Generate non-2xx status codes and body strings, verify proxy route returns same status and body
- **Validates: Requirements 5.12**
- [x] 6. Checkpoint — Verify backend routes and properties
- Ensure all tests pass, ask the user if questions arise.
- [x] 7. Mount Atlas router in server.js
- Add `const createAtlasRouter = require('./routes/atlas');` import alongside existing route imports
- Add `app.use('/api/atlas', createAtlasRouter(db, requireAuth));` mount alongside existing route mounts
- _Requirements: 3.1, 4.1, 5.1, 5.2, 5.6, 5.9_
- [ ] 8. Implement frontend Atlas components
- [x] 8.1 Create AtlasBadge component in `frontend/src/components/AtlasBadge.js`
- Accept props: `{ hostId, atlasStatus, onClick }`
- Render nothing if `atlasStatus` is undefined (host not in cache)
- Render warning badge (amber border, "0" text) if `has_action_plan === 0`
- Render success badge (emerald border, plan count text) if `plan_count > 0`
- Use design system badge pattern: monospace font, 0.58rem, inline-flex, pill shape
- _Requirements: 6.2, 6.3, 6.4, 6.5, 6.6_
- [ ]* 8.2 Write property test: Badge visibility and content
- **Property 13: Badge visibility and content**
- Generate findings with `hostId` and atlas status maps, verify badge renders if and only if `hostId` exists in map, and badge text contains plan count when `plan_count > 0`
- **Validates: Requirements 6.2, 6.5, 6.6**
- [x] 8.3 Create AtlasSlideOutPanel component in `frontend/src/components/AtlasSlideOutPanel.js`
- Accept props: `{ hostId, hostName, onClose, canWrite }`
- Fetch action plans from `GET /api/atlas/hosts/:hostId/action-plans` on open
- Display header with hostname, host ID, and close button
- Display each plan with: plan type, commit date, status, VNR/EXC references
- Show create form (plan type dropdown, commit date picker, optional fields: qualys_id, active_host_findings_id, jira_vnr, archer_exc) when `canWrite` is true
- Submit create via `PUT /api/atlas/hosts/:hostId/action-plans`, refresh plan list on success
- Show inline edit capability for existing plans when `canWrite` is true
- Submit updates via `PATCH /api/atlas/hosts/:hostId/action-plans`, refresh plan list on success
- Display error messages from Atlas API inline in the panel
- Close on backdrop click or close button
- Hide create/edit forms for Viewer group (read-only mode)
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 7.10_
- [ ]* 8.4 Write property test: Panel displays all plan fields
- **Property 14: Panel displays all plan fields**
- Generate plan objects with `plan_type`, `commit_date`, `status`, and optional reference fields, verify all non-null field values appear in rendered output
- **Validates: Requirements 7.3**
- [ ] 8.5 Write unit tests for AtlasBadge and AtlasSlideOutPanel
- Test AtlasBadge renders nothing when host not in status map
- Test AtlasBadge renders warning style when `plan_count` is 0
- Test AtlasBadge renders success style when `plan_count > 0`
- Test AtlasSlideOutPanel shows create form for Admin/Standard_User
- Test AtlasSlideOutPanel hides create form for Viewer
- _Requirements: 6.2, 6.3, 6.4, 6.5, 7.4, 7.10_
- [ ] 9. Integrate Atlas badge and sync button into ReportingPage
- [x] 9.1 Add Atlas status state and fetch to ReportingPage
- Add `atlasStatus` state (Map keyed by hostId), `atlasSyncing` boolean, `atlasError` string
- Fetch `GET /api/atlas/status` on mount and build the status map
- _Requirements: 6.1_
- [x] 9.2 Render AtlasBadge in Host column cells
- In the Host column cell renderer, check `atlasStatus` map for the finding's `hostId`
- Render AtlasBadge inline after the hostname text when a match exists
- Wire badge `onClick` to open the AtlasSlideOutPanel with the host's ID and name
- _Requirements: 6.2, 6.3, 6.4, 6.5, 6.6, 7.1_
- [x] 9.3 Add Atlas sync button to ReportingPage toolbar
- Place adjacent to existing Ivanti sync button, using same styling pattern (RefreshCw icon, monospace uppercase text, sky blue accent)
- Differentiate with Database icon prefix and "Atlas" label
- Enable for Admin and Standard_User groups, disable for Viewer with tooltip
- On click: send `POST /api/atlas/sync`, show loading indicator, disable button
- On success: re-fetch `GET /api/atlas/status` and update all badges
- On failure: display error notification
- _Requirements: 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7_
- [x] 9.4 Wire AtlasSlideOutPanel into ReportingPage
- Add state for selected host (`atlasSelectedHostId`, `atlasSelectedHostName`, `atlasPanelOpen`)
- Render AtlasSlideOutPanel conditionally when `atlasPanelOpen` is true
- Pass `canWrite` based on user group (Admin or Standard_User)
- On panel close: clear selected host state
- On plan create/update success: re-fetch atlas status to update badges
- _Requirements: 7.1, 7.2, 7.9, 7.10_
- [x] 10. Checkpoint — Verify full integration
- Ensure all tests pass, ask the user if questions arise.
- [ ] 11. Write integration tests
- [ ]* 11.1 Write integration tests for Atlas sync flow
- Populate Ivanti cache with test findings, trigger sync with mocked Atlas API, verify Atlas cache populated correctly
- Test error resilience: mix of successful and failing hosts during sync
- _Requirements: 3.2, 3.3, 3.4, 3.5, 3.6_
- [ ]* 11.2 Write integration tests for Atlas proxy routes
- Test create plan flow: send PUT, verify Atlas API called, verify audit logged
- Test update plan flow: send PATCH, verify Atlas API called, verify audit logged
- Test bulk create flow: send POST, verify Atlas API called with correct body
- _Requirements: 5.1, 5.2, 5.5, 5.6, 5.8, 5.11, 5.13_
- [ ] 11.3 Write integration tests for access control
- Verify 401 for unauthenticated requests on all endpoints
- Verify 403 for Viewer group on restricted endpoints (sync, PUT, PATCH, bulk POST)
- Verify 200 for Viewer group on read endpoints (status, GET plans)
- _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7_
- [x] 12. Final checkpoint — Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document using fast-check
- Unit tests validate specific examples and edge cases
- The backend follows the existing factory function pattern (`createAtlasRouter(db, requireAuth)`)
- The Atlas helper follows the existing `ivantiApi.js` pattern (promise-based HTTP with Node.js `https` module)
- The migration follows the existing pattern from `add_ivanti_findings_tables.js`