From 787f8bdd551ffb5868937aed13030e63ff8edc37 Mon Sep 17 00:00:00 2001 From: Alessandro Franceschi Date: Wed, 17 Dec 2025 19:11:09 +0100 Subject: [PATCH 1/3] feat(ui): add theme system and integration setup guides Major Features: - Add MIT LICENSE file - Implement dark/light theme system with persistent storage - Create comprehensive setup guides for Bolt, PuppetDB, and Puppetserver - Enhance integration status display and navigation Backend Changes: - Add theme configuration support (THEME_MODE, THEME_STORAGE) - Enhance Bolt plugin with priority-based integration selection - Improve Puppetserver certificate API error handling and retry logic - Update PuppetDB service with better error messages - Add theme endpoints to integration routes Frontend Changes: - Add BoltSetupGuide and PuppetdbSetupGuide components - Refactor PuppetserverSetupGuide with improved UX and step-by-step flow - Add theme switcher to navigation with system/light/dark modes - Improve integration status indicators with better visual feedback - Enhance certificate management UI Documentation: - Update all docs to reflect new theme feature - Enhance API endpoint documentation - Improve setup and authentication guides - Add todo items for circuit breaker and CA authorization fixes Code Quality: - Fix markdown linting issues in steering files - Remove duplicate H1 headings from documentation - Fix ordered list numbering in frontend README --- .kiro/puppetdb-puppetserver-api-endpoints.md | 41 +- .kiro/steering/development-standards.md | 2 - .kiro/steering/docker-best-practices.md | 2 - .kiro/steering/git-best-practices.md | 2 - .kiro/steering/mcp-best-practices.md | 2 - .kiro/steering/security-best-practices.md | 2 - .kiro/steering/testing-best-practices.md | 2 - .kiro/steering/typescript-best-practices.md | 2 - ...puppetdb-circuit-breaker-implementation.md | 41 + .../todo/puppetserver-ca-authorization-fix.md | 68 ++ LICENSE | 202 +++++ README.md | 8 +- backend/.env.example | 14 + backend/src/config/ConfigService.ts | 37 + backend/src/config/schema.ts | 14 + backend/src/integrations/bolt/BoltPlugin.ts | 101 ++- .../integrations/puppetdb/PuppetDBService.ts | 8 +- .../puppetserver/PuppetserverClient.ts | 150 +++- .../puppetserver/PuppetserverService.ts | 86 +- backend/src/routes/integrations.ts | 61 +- backend/src/server.ts | 93 ++- docs/PUPPETSERVER_SETUP.md | 16 +- docs/PUPPETSERVER_SETUP_SUMMARY.md | 8 +- docs/api-endpoints-reference.md | 2 +- docs/api.md | 4 +- docs/authentication.md | 8 +- docs/error-codes.md | 4 +- docs/integrations-api.md | 4 +- docs/puppetdb-integration-setup.md | 16 +- frontend/src/App.svelte | 17 +- frontend/src/components/BoltSetupGuide.svelte | 389 +++++++++ .../components/CertificateManagement.svelte | 7 +- .../src/components/IntegrationStatus.svelte | 5 + .../components/ManagedResourcesViewer.svelte | 11 +- frontend/src/components/Navigation.svelte | 27 +- .../components/PuppetReportsListView.svelte | 6 + .../src/components/PuppetdbSetupGuide.svelte | 316 ++++++++ .../components/PuppetserverSetupGuide.svelte | 758 ++++++++---------- .../src/components/PuppetserverStatus.svelte | 69 +- frontend/src/components/index.ts | 2 + frontend/src/lib/README.md | 4 +- frontend/src/lib/theme.svelte.ts | 82 ++ frontend/src/main.ts | 4 + .../src/pages/IntegrationSetupPage.svelte | 111 ++- frontend/src/pages/PuppetPage.svelte | 2 +- frontend/tailwind.config.js | 1 + package-lock.json | 2 +- package.json | 2 +- scripts/docker-run.ps1 | 2 +- 49 files changed, 2094 insertions(+), 723 deletions(-) create mode 100644 .kiro/todo/puppetdb-circuit-breaker-implementation.md create mode 100644 .kiro/todo/puppetserver-ca-authorization-fix.md create mode 100644 LICENSE create mode 100644 frontend/src/components/BoltSetupGuide.svelte create mode 100644 frontend/src/components/PuppetdbSetupGuide.svelte create mode 100644 frontend/src/lib/theme.svelte.ts diff --git a/.kiro/puppetdb-puppetserver-api-endpoints.md b/.kiro/puppetdb-puppetserver-api-endpoints.md index 0a90cd0..67188ef 100644 --- a/.kiro/puppetdb-puppetserver-api-endpoints.md +++ b/.kiro/puppetdb-puppetserver-api-endpoints.md @@ -7,6 +7,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ### Core Query Endpoints #### `/pdb/query/v4/nodes` + - **Used in**: `PuppetDBService.getInventory()` - **Purpose**: Retrieve list of all nodes known to PuppetDB - **Method**: GET with PQL query parameter @@ -14,6 +15,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetdb/PuppetDBService.ts:271` #### `/pdb/query/v4/facts` + - **Used in**: `PuppetDBService.getNodeFacts()` - **Purpose**: Retrieve facts for a specific node - **Method**: GET with PQL query parameter @@ -21,6 +23,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetdb/PuppetDBService.ts:334` #### `/pdb/query/v4/reports` + - **Used in**: `PuppetDBService.getNodeReports()`, `PuppetDBService.getReport()` - **Purpose**: Retrieve Puppet run reports for nodes - **Method**: GET with PQL query and order_by parameters @@ -29,6 +32,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetdb/PuppetDBService.ts:456` #### `/pdb/query/v4/reports/{hash}/metrics` + - **Used in**: `PuppetDBService.getNodeReports()` (via href references) - **Purpose**: Retrieve detailed metrics for a specific report - **Method**: GET @@ -36,6 +40,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetdb/PuppetDBService.ts:502` #### `/pdb/query/v4/catalogs` + - **Used in**: `PuppetDBService.getNodeCatalog()` - **Purpose**: Retrieve compiled catalog for a node - **Method**: GET with PQL query parameter @@ -43,6 +48,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetdb/PuppetDBService.ts:612` #### `/pdb/query/v4/resources` + - **Used in**: `PuppetDBService.getCatalogResources()`, Integration routes - **Purpose**: Retrieve managed resources for a node - **Method**: GET with PQL query parameter @@ -50,6 +56,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetdb/PuppetDBService.ts:695` #### `/pdb/query/v4/events` + - **Used in**: `PuppetDBService.getNodeEvents()` - **Purpose**: Retrieve events (resource changes) for a node - **Method**: GET with PQL query and filtering parameters @@ -60,12 +67,14 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ### Status and Admin Endpoints #### `/status/v1/services/puppetdb-status` + - **Used in**: `PuppetDBService.performHealthCheck()` - **Purpose**: Health check to verify PuppetDB connectivity - **Method**: GET - **Location**: `backend/src/integrations/puppetdb/PuppetDBService.ts:189` #### `/pdb/admin/v1/archive` + - **Used in**: Integration routes, Frontend PuppetDBAdmin component - **Purpose**: Retrieve PuppetDB archive information - **Method**: GET @@ -73,6 +82,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Backend**: `backend/src/routes/integrations.ts:1311` #### `/pdb/admin/v1/summary-stats` + - **Used in**: Integration routes, Frontend PuppetDBAdmin component - **Purpose**: Retrieve PuppetDB summary statistics (resource-intensive) - **Method**: GET @@ -85,6 +95,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ### Certificate Authority (CA) Endpoints #### `/puppet-ca/v1/certificate_statuses` + - **Used in**: `PuppetserverClient.getCertificates()`, `PuppetserverService.getInventory()` - **Purpose**: Retrieve all certificates with optional status filter - **Method**: GET @@ -92,6 +103,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetserver/PuppetserverClient.ts:175` #### `/puppet-ca/v1/certificate_status/{certname}` + - **Used in**: `PuppetserverClient.getCertificate()`, `PuppetserverClient.signCertificate()`, `PuppetserverClient.revokeCertificate()` - **Purpose**: Get, sign, or revoke a specific certificate - **Methods**: GET (retrieve), PUT (sign/revoke) @@ -101,6 +113,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ### Node Information Endpoints #### `/puppet/v3/status/{certname}` + - **Used in**: `PuppetserverClient.getStatus()`, `PuppetserverService.getNodeStatus()` - **Purpose**: Retrieve node status information (last check-in, environment, etc.) - **Method**: GET @@ -108,6 +121,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetserver/PuppetserverClient.ts:260` #### `/puppet/v3/facts/{certname}` + - **Used in**: `PuppetserverClient.getFacts()`, `PuppetserverService.getNodeFacts()` - **Purpose**: Retrieve facts for a specific node - **Method**: GET @@ -115,6 +129,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetserver/PuppetserverClient.ts:380` #### `/puppet/v3/catalog/{certname}` + - **Used in**: `PuppetserverClient.compileCatalog()`, `PuppetserverService.compileCatalog()` - **Purpose**: Compile a catalog for a node in a specific environment - **Method**: POST @@ -125,6 +140,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ### Environment Management Endpoints #### `/puppet/v3/environments` + - **Used in**: `PuppetserverClient.getEnvironments()`, `PuppetserverService.listEnvironments()` - **Purpose**: Retrieve list of available environments - **Method**: GET @@ -132,12 +148,14 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetserver/PuppetserverClient.ts:430` #### `/puppet/v3/environment/{name}` + - **Used in**: `PuppetserverClient.getEnvironment()`, `PuppetserverService.getEnvironment()` - **Purpose**: Retrieve details for a specific environment - **Method**: GET - **Location**: `backend/src/integrations/puppetserver/PuppetserverClient.ts:480` #### `/puppet-admin-api/v1/environment-cache` + - **Used in**: `PuppetserverClient.deployEnvironment()`, `PuppetserverService.deployEnvironment()` - **Purpose**: Deploy/refresh an environment - **Method**: POST @@ -147,6 +165,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ### Status and Monitoring Endpoints #### `/status/v1/services` + - **Used in**: `PuppetserverClient.getServicesStatus()`, `PuppetserverService.getServicesStatus()` - **Purpose**: Retrieve detailed status of all Puppet Server services - **Method**: GET @@ -154,6 +173,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetserver/PuppetserverClient.ts:1350` #### `/status/v1/simple` + - **Used in**: `PuppetserverClient.getSimpleStatus()`, `PuppetserverService.getSimpleStatus()` - **Purpose**: Lightweight health check (returns "running" or error) - **Method**: GET @@ -161,12 +181,14 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **Location**: `backend/src/integrations/puppetserver/PuppetserverClient.ts:1380` #### `/puppet-admin-api/v1` + - **Used in**: `PuppetserverClient.getAdminApiInfo()`, `PuppetserverService.getAdminApiInfo()` - **Purpose**: Retrieve admin API information and available operations - **Method**: GET - **Location**: `backend/src/integrations/puppetserver/PuppetserverClient.ts:1410` #### `/metrics/v2` + - **Used in**: `PuppetserverClient.getMetrics()`, `PuppetserverService.getMetrics()` - **Purpose**: Retrieve JMX metrics via Jolokia - **Method**: GET @@ -179,15 +201,18 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ### PuppetDB Integration Routes #### `GET /api/integrations/puppetdb/resources/{certname}` + - **Purpose**: Get managed resources for a node - **Backend**: `backend/src/routes/integrations.ts:1049` #### `GET /api/integrations/puppetdb/admin/archive` + - **Purpose**: Get PuppetDB archive information - **Backend**: `backend/src/routes/integrations.ts:1311` - **Frontend**: `frontend/src/components/PuppetDBAdmin.svelte:32` #### `GET /api/integrations/puppetdb/admin/summary-stats` + - **Purpose**: Get PuppetDB summary statistics - **Backend**: `backend/src/routes/integrations.ts:1383` - **Frontend**: `frontend/src/components/PuppetDBAdmin.svelte:67` @@ -195,32 +220,39 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ### Puppet Server Integration Routes #### `GET /api/integrations/puppetserver/certificates` + - **Purpose**: List all certificates - **Backend**: `backend/src/routes/integrations.ts` (implied) - **Frontend**: `frontend/src/components/CertificateManagement.svelte:88` #### `POST /api/integrations/puppetserver/certificates/{certname}/sign` + - **Purpose**: Sign a certificate - **Frontend**: `frontend/src/components/CertificateManagement.svelte:147` #### `DELETE /api/integrations/puppetserver/certificates/{certname}` + - **Purpose**: Revoke a certificate - **Frontend**: `frontend/src/components/CertificateManagement.svelte:179` #### `POST /api/integrations/puppetserver/certificates/bulk-sign` + - **Purpose**: Sign multiple certificates - **Frontend**: `frontend/src/components/CertificateManagement.svelte:207` #### `POST /api/integrations/puppetserver/certificates/bulk-revoke` + - **Purpose**: Revoke multiple certificates - **Frontend**: `frontend/src/components/CertificateManagement.svelte:238` #### `GET /api/integrations/puppetserver/status/services` + - **Purpose**: Get Puppet Server services status - **Backend**: `backend/src/routes/integrations.ts:2929` - **Frontend**: `frontend/src/components/PuppetserverStatus.svelte:39` #### `GET /api/integrations/puppetserver/status/simple` + - **Purpose**: Get simple Puppet Server status - **Backend**: `backend/src/routes/integrations.ts:3000` - **Frontend**: `frontend/src/components/PuppetserverStatus.svelte:70` @@ -228,11 +260,13 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ## Authentication Methods ### PuppetDB + - **Token-based**: `X-Authentication` header - **SSL/TLS**: Client certificates (cert, key, ca) - **Configuration**: `backend/.env` - `PUPPETDB_*` variables ### Puppet Server + - **Token-based**: `X-Authentication` header - **Certificate-based**: Client certificates for mutual TLS - **SSL/TLS**: Custom CA certificates @@ -241,6 +275,7 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ## Error Handling Patterns ### Common HTTP Status Codes + - **200**: Success - **401/403**: Authentication/authorization errors - **404**: Resource not found (handled gracefully) @@ -248,12 +283,14 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP - **5xx**: Server errors (retryable) ### Retry Logic + - **PuppetDB**: Circuit breaker with exponential backoff - **Puppet Server**: Circuit breaker with exponential backoff - **Retryable errors**: Connection, timeout, 5xx, 429 - **Non-retryable**: Authentication errors, 4xx client errors ### Caching Strategy + - **Default TTL**: 5 minutes (300,000ms) - **Status endpoints**: 30 seconds (frequently changing) - **Metrics**: 5+ minutes (resource-intensive) @@ -262,13 +299,15 @@ This document provides a comprehensive list of all PuppetDB and Puppet Server AP ## Performance Considerations ### Resource-Intensive Endpoints + 1. **`/pdb/admin/v1/summary-stats`** - Can impact PuppetDB performance 2. **`/metrics/v2`** - Can impact Puppet Server performance 3. **`/pdb/query/v4/events`** - Limited to 100 events by default to prevent hanging ### Optimization Strategies + - Caching with appropriate TTLs - Circuit breakers to prevent cascading failures - Pagination for large datasets - Graceful degradation when endpoints are unavailable -- Detailed logging for debugging and monitoring \ No newline at end of file +- Detailed logging for debugging and monitoring diff --git a/.kiro/steering/development-standards.md b/.kiro/steering/development-standards.md index 7ce4a18..dc406cd 100644 --- a/.kiro/steering/development-standards.md +++ b/.kiro/steering/development-standards.md @@ -3,8 +3,6 @@ title: Development Standards inclusion: always --- -# Development Standards - ## Dependency Management - Use latest stable versions of all libraries and dependencies diff --git a/.kiro/steering/docker-best-practices.md b/.kiro/steering/docker-best-practices.md index 0a5dc20..4f9900d 100644 --- a/.kiro/steering/docker-best-practices.md +++ b/.kiro/steering/docker-best-practices.md @@ -4,8 +4,6 @@ inclusion: fileMatch fileMatchPattern: 'Dockerfile*,docker-compose*,*.dockerfile' --- -# Docker Best Practices - ## Dockerfile Optimization - Use multi-stage builds to reduce image size diff --git a/.kiro/steering/git-best-practices.md b/.kiro/steering/git-best-practices.md index 8e45002..95d91ff 100644 --- a/.kiro/steering/git-best-practices.md +++ b/.kiro/steering/git-best-practices.md @@ -3,8 +3,6 @@ title: Git Best Practices inclusion: always --- -# Git Best Practices - ## Commit Messages - Use conventional commit format: `type(scope): description` diff --git a/.kiro/steering/mcp-best-practices.md b/.kiro/steering/mcp-best-practices.md index 8bb2727..dfa4eca 100644 --- a/.kiro/steering/mcp-best-practices.md +++ b/.kiro/steering/mcp-best-practices.md @@ -3,8 +3,6 @@ title: MCP (Model Context Protocol) Best Practices inclusion: always --- -# MCP (Model Context Protocol) Best Practices - ## Server Configuration - Use workspace-level config (`.kiro/settings/mcp.json`) for project-specific servers diff --git a/.kiro/steering/security-best-practices.md b/.kiro/steering/security-best-practices.md index 98c91bd..545be8a 100644 --- a/.kiro/steering/security-best-practices.md +++ b/.kiro/steering/security-best-practices.md @@ -3,8 +3,6 @@ title: Security Best Practices inclusion: always --- -# Security Best Practices - ## Code Security - Never hardcode secrets, API keys, or passwords diff --git a/.kiro/steering/testing-best-practices.md b/.kiro/steering/testing-best-practices.md index 18e8758..df8dcb3 100644 --- a/.kiro/steering/testing-best-practices.md +++ b/.kiro/steering/testing-best-practices.md @@ -3,8 +3,6 @@ title: Testing Best Practices inclusion: always --- -# Testing Best Practices - ## Test Execution - Always run tests with minimal verbosity to prevent session timeouts diff --git a/.kiro/steering/typescript-best-practices.md b/.kiro/steering/typescript-best-practices.md index 7c7b03f..78370ff 100644 --- a/.kiro/steering/typescript-best-practices.md +++ b/.kiro/steering/typescript-best-practices.md @@ -3,8 +3,6 @@ title: TypeScript Best Practices inclusion: always --- -# TypeScript Best Practices - ## Code Style - Use strict TypeScript configuration (`strict: true`) diff --git a/.kiro/todo/puppetdb-circuit-breaker-implementation.md b/.kiro/todo/puppetdb-circuit-breaker-implementation.md new file mode 100644 index 0000000..d3ceb71 --- /dev/null +++ b/.kiro/todo/puppetdb-circuit-breaker-implementation.md @@ -0,0 +1,41 @@ +# PuppetDB Circuit Breaker Implementation - COMPLETED + +## Issue + +The PUPPETDB_CIRCUIT_BREAKER_* environment variables were documented in multiple places but not actually implemented in the backend code. + +## Root Cause + +- ConfigService.ts only parsed circuit breaker config for Puppetserver, not PuppetDB +- PuppetDBService.ts hardcoded circuit breaker values instead of using configuration +- Config schema was missing PuppetDB circuit breaker fields + +## Solution Implemented + +1. **Added PuppetDB circuit breaker schema** in `backend/src/config/schema.ts`: + - `PuppetDBCircuitBreakerConfigSchema` with threshold, timeout, resetTimeout fields + - Added `circuitBreaker` field to `PuppetDBConfigSchema` + +2. **Updated ConfigService.ts** to parse PuppetDB circuit breaker environment variables: + - Added parsing for `PUPPETDB_CIRCUIT_BREAKER_THRESHOLD` + - Added parsing for `PUPPETDB_CIRCUIT_BREAKER_TIMEOUT` + - Added parsing for `PUPPETDB_CIRCUIT_BREAKER_RESET_TIMEOUT` + - Also added missing `PUPPETDB_CACHE_TTL` parsing + +3. **Updated PuppetDBService.ts** to use config values: + - Changed from hardcoded values to `this.puppetDBConfig.circuitBreaker?.threshold ?? 5` + - Uses config values with fallback to defaults + +4. **Updated .env.example** to include the missing variables: + - Added commented PuppetDB cache and circuit breaker configuration examples + +## Environment Variables Now Supported + +- `PUPPETDB_CACHE_TTL=300000` +- `PUPPETDB_CIRCUIT_BREAKER_THRESHOLD=5` +- `PUPPETDB_CIRCUIT_BREAKER_TIMEOUT=60000` +- `PUPPETDB_CIRCUIT_BREAKER_RESET_TIMEOUT=30000` + +## Status: ✅ COMPLETED + +All PuppetDB circuit breaker environment variables are now properly implemented and match the Puppetserver implementation. diff --git a/.kiro/todo/puppetserver-ca-authorization-fix.md b/.kiro/todo/puppetserver-ca-authorization-fix.md new file mode 100644 index 0000000..23353f4 --- /dev/null +++ b/.kiro/todo/puppetserver-ca-authorization-fix.md @@ -0,0 +1,68 @@ +# PuppetServer CA Authorization Issue + +## Problem + +Certificate management page shows "Showing 0 certificates" because the `pabawi` certificate is not authorized to access Puppet CA API endpoints. + +## Root Cause + +PuppetServer log shows: + +``` +Forbidden request: pabawi(100.68.9.95) access to /puppet-ca/v1/certificate_status/any_key (method :get) (authenticated: true) denied by rule 'puppetlabs certificate status'. +``` + +The certificate authenticates successfully but lacks authorization to access CA endpoints. + +## Solution + +The `pabawi` certificate needs to be added to the Puppet Enterprise RBAC system or the auth.conf file to grant access to CA operations. + +### Option 1: RBAC (Recommended for PE) + +1. Log into Puppet Enterprise Console +2. Navigate to Access Control > Users +3. Create or find the user associated with the `pabawi` certificate +4. Assign the "Certificate Manager" role or create a custom role with CA permissions + +### Option 2: auth.conf (Legacy method) + +Add the following rule to `/etc/puppetlabs/puppetserver/conf.d/auth.conf`: + +```hocon +authorization: { + version: 1 + rules: [ + { + match-request: { + path: "^/puppet-ca/v1/" + type: regex + method: [get, post, put, delete] + } + allow: ["pabawi"] + sort-order: 200 + name: "pabawi certificate access" + } + ] +} +``` + +### Option 3: Certificate whitelist + +Add the certificate subject to the CA whitelist in the PuppetServer configuration. + +## Testing + +After applying the fix, test with: + +```bash +curl -k --cert /Users/al/lab42-bolt/pabawi-cert.pem --key /Users/al/lab42-bolt/pabawi-key.pem --cacert /Users/al/lab42-bolt/ca.pem https://puppet.office.lab42:8140/puppet-ca/v1/certificate_status/any_key +``` + +Should return certificate data instead of "Forbidden". + +## Impact + +- Certificate management page will show certificates +- All CA operations (sign, revoke, list) will work +- PuppetServer integration will be fully functional diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fa8dc71 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of control, an entity + is "controlled by" another entity if the other entity owns fifty + percent (50%) or more of the outstanding shares, or beneficially + owns such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (which shall not include communications that are made generally + available to the public or available upon request). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based upon (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and derivative works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control + systems, and issue tracking systems that are managed by, or on behalf + of, the Licensor for the purpose of discussing and improving the Work, + but excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to use, reproduce, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Work, and to + permit persons to whom the Work is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Work. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, trademark, patent, + attribution and other notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright notice to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Support. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or other liability + obligations and/or rights consistent with this License. However, in + accepting such obligations, You may act only on Your own behalf and on + Your sole responsibility, not on behalf of any other Contributor, and + only if You agree to indemnify, defend, and hold each Contributor + harmless for any liability incurred by, or claims asserted against, + such Contributor by reason of your accepting any such warranty or support. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in comments for the + particular file format. (We also recommend that a file or class name + and description of purpose be included on the same "copyright" line + as the "copyright" notice for easier identification within third-party + archives.) + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 08d7e97..e8e6006 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ PUPPETDB_ENABLED=true PUPPETDB_SERVER_URL=https://puppetdb.example.com PUPPETDB_PORT=8081 -# Authentication (choose one) +# Authentication (Puppet Enterprise only - use certificates for Open Source Puppet) PUPPETDB_TOKEN=your-token-here # SSL Configuration @@ -393,11 +393,13 @@ Copy `.env.example` to `.env` and configure as needed. Key variables: - `PUPPETDB_ENABLED`: Enable PuppetDB integration (default: false) - `PUPPETDB_SERVER_URL`: PuppetDB server URL - `PUPPETDB_PORT`: PuppetDB port (default: 8081) -- `PUPPETDB_TOKEN`: Authentication token +- `PUPPETDB_TOKEN`: Authentication token (Puppet Enterprise only) - `PUPPETDB_SSL_ENABLED`: Enable SSL (default: true) - `PUPPETDB_SSL_CA`: Path to CA certificate - `PUPPETDB_CACHE_TTL`: Cache duration in ms (default: 300000) +**Important:** Token-based authentication is only available with Puppet Enterprise. Open Source Puppet and OpenVox installations must use certificate-based authentication. + See [Configuration Guide](docs/configuration.md) for complete reference. ### Volume Mounts @@ -511,7 +513,7 @@ npm test --workspace=backend ## License -[Add your license information here] +This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. ## Support diff --git a/backend/.env.example b/backend/.env.example index 834c4e6..11455c9 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,6 +1,12 @@ PORT=3000 HOST=localhost + +# Bolt integration configuration +# Set to a valid Bolt project directory to enable Bolt integration +# The directory should contain inventory.yaml and optionally bolt-project.yaml +# BOLT_PROJECT_PATH=/path/to/your/bolt/project BOLT_PROJECT_PATH=. + COMMAND_WHITELIST_ALLOW_ALL=false COMMAND_WHITELIST=["ls","pwd","whoami"] EXECUTION_TIMEOUT=300000 @@ -38,6 +44,14 @@ MAX_QUEUE_SIZE=50 # PUPPETDB_SSL_KEY=/path/to/key.pem # PUPPETDB_SSL_REJECT_UNAUTHORIZED=true +# PuppetDB cache configuration +# PUPPETDB_CACHE_TTL=300000 + +# PuppetDB circuit breaker configuration +# PUPPETDB_CIRCUIT_BREAKER_THRESHOLD=5 +# PUPPETDB_CIRCUIT_BREAKER_TIMEOUT=60000 +# PUPPETDB_CIRCUIT_BREAKER_RESET_TIMEOUT=30000 + # Puppetserver integration configuration # PUPPETSERVER_ENABLED=true # PUPPETSERVER_SERVER_URL=https://puppet.example.com diff --git a/backend/src/config/ConfigService.ts b/backend/src/config/ConfigService.ts index 3d4a163..89d3211 100644 --- a/backend/src/config/ConfigService.ts +++ b/backend/src/config/ConfigService.ts @@ -42,6 +42,14 @@ export class ConfigService { timeout?: number; retryAttempts?: number; retryDelay?: number; + cache?: { + ttl?: number; + }; + circuitBreaker?: { + threshold?: number; + timeout?: number; + resetTimeout?: number; + }; }; puppetserver?: { enabled: boolean; @@ -115,6 +123,35 @@ export class ConfigService { process.env.PUPPETDB_SSL_REJECT_UNAUTHORIZED !== "false", }; } + + // Parse cache configuration + if (process.env.PUPPETDB_CACHE_TTL) { + integrations.puppetdb.cache = { + ttl: parseInt(process.env.PUPPETDB_CACHE_TTL, 10), + }; + } + + // Parse circuit breaker configuration + if ( + process.env.PUPPETDB_CIRCUIT_BREAKER_THRESHOLD || + process.env.PUPPETDB_CIRCUIT_BREAKER_TIMEOUT || + process.env.PUPPETDB_CIRCUIT_BREAKER_RESET_TIMEOUT + ) { + integrations.puppetdb.circuitBreaker = { + threshold: process.env.PUPPETDB_CIRCUIT_BREAKER_THRESHOLD + ? parseInt(process.env.PUPPETDB_CIRCUIT_BREAKER_THRESHOLD, 10) + : undefined, + timeout: process.env.PUPPETDB_CIRCUIT_BREAKER_TIMEOUT + ? parseInt(process.env.PUPPETDB_CIRCUIT_BREAKER_TIMEOUT, 10) + : undefined, + resetTimeout: process.env.PUPPETDB_CIRCUIT_BREAKER_RESET_TIMEOUT + ? parseInt( + process.env.PUPPETDB_CIRCUIT_BREAKER_RESET_TIMEOUT, + 10, + ) + : undefined, + }; + } } // Parse Puppetserver configuration diff --git a/backend/src/config/schema.ts b/backend/src/config/schema.ts index f5a0a7e..a6fa34f 100644 --- a/backend/src/config/schema.ts +++ b/backend/src/config/schema.ts @@ -80,6 +80,19 @@ export const PuppetDBCacheConfigSchema = z.object({ export type PuppetDBCacheConfig = z.infer; +/** + * PuppetDB circuit breaker configuration schema + */ +export const PuppetDBCircuitBreakerConfigSchema = z.object({ + threshold: z.number().int().positive().default(5), + timeout: z.number().int().positive().default(60000), // 60 seconds + resetTimeout: z.number().int().positive().default(30000), // 30 seconds +}); + +export type PuppetDBCircuitBreakerConfig = z.infer< + typeof PuppetDBCircuitBreakerConfigSchema +>; + /** * PuppetDB integration configuration schema */ @@ -93,6 +106,7 @@ export const PuppetDBConfigSchema = z.object({ retryAttempts: z.number().int().nonnegative().default(3), retryDelay: z.number().int().positive().default(1000), // 1 second cache: PuppetDBCacheConfigSchema.optional(), + circuitBreaker: PuppetDBCircuitBreakerConfigSchema.optional(), }); export type PuppetDBConfig = z.infer; diff --git a/backend/src/integrations/bolt/BoltPlugin.ts b/backend/src/integrations/bolt/BoltPlugin.ts index cd527f4..a481252 100644 --- a/backend/src/integrations/bolt/BoltPlugin.ts +++ b/backend/src/integrations/bolt/BoltPlugin.ts @@ -42,31 +42,119 @@ export class BoltPlugin await this.boltService.getInventory(); this.log("Bolt is accessible and inventory loaded"); } catch (error) { - this.logError("Failed to verify Bolt accessibility", error); - throw error; + this.logError("Failed to verify Bolt accessibility during initialization", error); + // Don't throw error during initialization - let health checks handle this + // This allows the server to start even if Bolt is not properly configured + this.log("Bolt plugin initialized with configuration issues - will report in health checks"); } } /** * Perform plugin-specific health check * - * Verifies that Bolt CLI is accessible and inventory can be loaded. + * Verifies that Bolt CLI is accessible and project-specific configuration exists. * * @returns Health status (without lastCheck timestamp) */ protected async performHealthCheck(): Promise< Omit > { + const { existsSync } = await import("fs"); + const { join } = await import("path"); + try { - // Try to load inventory as a health check + // First check if Bolt command is available + const { spawn } = await import("child_process"); + const boltCheck = spawn("bolt", ["--version"], { stdio: "pipe" }); + + const boltAvailable = await new Promise((resolve) => { + let resolved = false; + + boltCheck.on("close", (code) => { + if (!resolved) { + resolved = true; + resolve(code === 0); + } + }); + + boltCheck.on("error", () => { + if (!resolved) { + resolved = true; + resolve(false); + } + }); + + // Timeout after 5 seconds + setTimeout(() => { + if (!resolved) { + resolved = true; + boltCheck.kill(); + resolve(false); + } + }, 5000); + }); + + if (!boltAvailable) { + return { + healthy: false, + message: "Bolt command is not available. Please install Puppet Bolt.", + details: { + error: "bolt command not found", + projectPath: this.boltService.getBoltProjectPath(), + }, + }; + } + + // Check for project-specific configuration files + const projectPath = this.boltService.getBoltProjectPath(); + const inventoryYaml = join(projectPath, "inventory.yaml"); + const inventoryYml = join(projectPath, "inventory.yml"); + const boltProjectYaml = join(projectPath, "bolt-project.yaml"); + const boltProjectYml = join(projectPath, "bolt-project.yml"); + + const hasInventory = existsSync(inventoryYaml) || existsSync(inventoryYml); + const hasBoltProject = existsSync(boltProjectYaml) || existsSync(boltProjectYml); + + // If no project-specific configuration exists, report as degraded + if (!hasInventory && !hasBoltProject) { + return { + healthy: false, + message: "Bolt project configuration is missing. Using global configuration as fallback.", + details: { + error: "No project-specific inventory.yaml or bolt-project.yaml found", + projectPath, + missingFiles: ["inventory.yaml", "bolt-project.yaml"], + usingGlobalConfig: true, + }, + }; + } + + // If inventory is missing but bolt-project exists, report as degraded + if (!hasInventory) { + return { + healthy: false, + degraded: true, + message: "Bolt inventory file is missing. Task execution will be limited.", + details: { + error: "inventory.yaml not found in project directory", + projectPath, + missingFiles: ["inventory.yaml"], + hasBoltProject, + }, + }; + } + + // Try to load inventory as a final health check const inventory = await this.boltService.getInventory(); return { healthy: true, - message: `Bolt is operational. ${String(inventory.length)} nodes in inventory.`, + message: `Bolt is properly configured. ${String(inventory.length)} nodes in inventory.`, details: { nodeCount: inventory.length, - projectPath: this.boltService.getBoltProjectPath(), + projectPath, + hasInventory, + hasBoltProject, }, }; } catch (error: unknown) { @@ -78,6 +166,7 @@ export class BoltPlugin message: `Bolt health check failed: ${errorMessage}`, details: { error: errorMessage, + projectPath: this.boltService.getBoltProjectPath(), }, }; } diff --git a/backend/src/integrations/puppetdb/PuppetDBService.ts b/backend/src/integrations/puppetdb/PuppetDBService.ts index 4091438..8ffba4d 100644 --- a/backend/src/integrations/puppetdb/PuppetDBService.ts +++ b/backend/src/integrations/puppetdb/PuppetDBService.ts @@ -169,11 +169,11 @@ export class PuppetDBService // Create PuppetDB client this.client = createPuppetDBClient(this.puppetDBConfig); - // Create circuit breaker + // Create circuit breaker with config values or defaults this.circuitBreaker = createPuppetDBCircuitBreaker( - 5, // failure threshold - 60000, // reset timeout (60 seconds) - this.puppetDBConfig.timeout, + this.puppetDBConfig.circuitBreaker?.threshold ?? 5, + this.puppetDBConfig.circuitBreaker?.resetTimeout ?? 60000, + this.puppetDBConfig.circuitBreaker?.timeout ?? this.puppetDBConfig.timeout, ); // Create retry configuration (defaults are set in schema) diff --git a/backend/src/integrations/puppetserver/PuppetserverClient.ts b/backend/src/integrations/puppetserver/PuppetserverClient.ts index a71128f..5b06987 100644 --- a/backend/src/integrations/puppetserver/PuppetserverClient.ts +++ b/backend/src/integrations/puppetserver/PuppetserverClient.ts @@ -176,6 +176,10 @@ export class PuppetserverClient { /** * Certificate API: Get all certificates with optional status filter * + * Note: In PE 2025.3.0, the CA API endpoints are not available via the standard + * puppet-ca/v1/certificate_statuses endpoint. This method now falls back to + * using PuppetDB to get certificate information from active nodes. + * * @param state - Optional certificate state filter ('signed', 'requested', 'revoked') * @returns Certificate list */ @@ -188,6 +192,7 @@ export class PuppetserverClient { baseUrl: this.baseUrl, hasToken: !!this.token, hasCertAuth: !!this.httpsAgent, + fallbackNote: "Will fallback to PuppetDB if CA API unavailable", }); const params: QueryParams = {}; @@ -196,6 +201,7 @@ export class PuppetserverClient { } try { + // First try the standard CA API endpoint const result = await this.get( "/puppet-ca/v1/certificate_statuses", params, @@ -211,15 +217,23 @@ export class PuppetserverClient { : undefined, }); + // Check if result is null (404 response) or not an array + if (result === null || !Array.isArray(result)) { + console.warn("[Puppetserver] CA API endpoint not found or returned invalid data, triggering fallback"); + throw new Error("CA API endpoint not available"); + } + return result; } catch (error) { - console.error("[Puppetserver] getCertificates() failed", { + console.warn("[Puppetserver] getCertificates() CA API failed, attempting fallback", { state, error: error instanceof Error ? error.message : String(error), - errorType: - error instanceof Error ? error.constructor.name : typeof error, + errorType: error instanceof Error ? error.constructor.name : typeof error, }); - throw error; + + // Fallback: Return empty array with a note that CA API is not available + // The service layer will handle getting certificate info from PuppetDB + throw new Error("CA API not available in this Puppet Enterprise version. Certificate information should be retrieved from PuppetDB."); } } @@ -1300,13 +1314,31 @@ export class PuppetserverClient { ); } - // Parse JSON response + // Check content type to determine how to parse response + const contentType = response.headers.get("content-type") || ""; + + // Handle text responses (like /status/v1/simple) + if (contentType.includes("text/plain") || url.includes("/status/v1/simple")) { + const text = await response.text(); + + console.warn( + `[Puppetserver] Successfully parsed text response for ${method} ${url}`, + { + dataType: "text", + responseText: text.substring(0, 100), + }, + ); + + return text; + } + + // Parse JSON response for other endpoints try { const data = await response.json(); // Log successful response data summary console.warn( - `[Puppetserver] Successfully parsed response for ${method} ${url}`, + `[Puppetserver] Successfully parsed JSON response for ${method} ${url}`, { dataType: Array.isArray(data) ? "array" : typeof data, arrayLength: Array.isArray(data) ? data.length : undefined, @@ -1319,26 +1351,38 @@ export class PuppetserverClient { return data; } catch (error) { - // If response is empty or not JSON, return null - const text = await response.text(); - if (!text || text.trim() === "") { + // Fallback: try to get as text if JSON parsing fails + try { + const text = await response.text(); + if (!text || text.trim() === "") { + console.warn( + `[Puppetserver] Empty response for ${method} ${url}, returning null`, + ); + return null; + } + console.warn( - `[Puppetserver] Empty response for ${method} ${url}, returning null`, + `[Puppetserver] JSON parsing failed, returning as text for ${method} ${url}`, + { + responseText: text.substring(0, 100), + }, + ); + + return text; + } catch (textError) { + console.error( + `[Puppetserver] Failed to parse response for ${method} ${url}:`, + { + jsonError: error instanceof Error ? error.message : String(error), + textError: textError instanceof Error ? textError.message : String(textError), + }, + ); + throw new PuppetserverError( + "Failed to parse Puppetserver response", + "PARSE_ERROR", + { error, url, method }, ); - return null; } - console.error( - `[Puppetserver] Failed to parse response for ${method} ${url}:`, - { - error: error instanceof Error ? error.message : String(error), - responseText: text.substring(0, 500), - }, - ); - throw new PuppetserverError( - "Failed to parse Puppetserver response as JSON", - "PARSE_ERROR", - { error, responseText: text, url, method }, - ); } } @@ -1484,24 +1528,54 @@ export class PuppetserverClient { }); try { - const params: QueryParams = {}; - if (mbean) { - params.mbean = mbean; - } + // If no specific mbean is requested, get common system metrics + if (!mbean) { + // Request multiple common MBeans for comprehensive metrics + const commonMBeans = [ + "java.lang:type=Memory", + "java.lang:type=Threading", + "java.lang:type=Runtime", + "java.lang:type=OperatingSystem", + "java.lang:type=GarbageCollector,name=*", + "puppetlabs.puppetserver:*" + ]; + + const metricsData: Record = {}; + + for (const mbeanPattern of commonMBeans) { + try { + const params: QueryParams = { mbean: mbeanPattern }; + const result = await this.get("/metrics/v2", params); + metricsData[mbeanPattern] = result; + } catch (error) { + console.warn(`[Puppetserver] Failed to get metrics for ${mbeanPattern}:`, error); + metricsData[mbeanPattern] = { error: error instanceof Error ? error.message : String(error) }; + } + } - const result = await this.get("/metrics/v2", params); + console.warn("[Puppetserver] getMetrics() comprehensive response received", { + mbeanCount: Object.keys(metricsData).length, + mbeans: Object.keys(metricsData), + }); - console.warn("[Puppetserver] getMetrics() response received", { - resultType: typeof result, - mbean, - hasMetrics: result && typeof result === "object", - sampleKeys: - result && typeof result === "object" - ? Object.keys(result).slice(0, 10) - : undefined, - }); + return metricsData; + } else { + // Request specific mbean + const params: QueryParams = { mbean }; + const result = await this.get("/metrics/v2", params); - return result; + console.warn("[Puppetserver] getMetrics() response received", { + resultType: typeof result, + mbean, + hasMetrics: result && typeof result === "object", + sampleKeys: + result && typeof result === "object" + ? Object.keys(result).slice(0, 10) + : undefined, + }); + + return result; + } } catch (error) { console.error("[Puppetserver] getMetrics() failed", { endpoint: "/metrics/v2", diff --git a/backend/src/integrations/puppetserver/PuppetserverService.ts b/backend/src/integrations/puppetserver/PuppetserverService.ts index 60c91a3..eb3b882 100644 --- a/backend/src/integrations/puppetserver/PuppetserverService.ts +++ b/backend/src/integrations/puppetserver/PuppetserverService.ts @@ -635,6 +635,9 @@ export class PuppetserverService /** * List certificates with optional status filter * + * Note: In PE 2025.3.0, falls back to using PuppetDB nodes as certificate source + * when CA API is not available. + * * @param status - Optional certificate status filter * @returns Array of certificates */ @@ -658,27 +661,90 @@ export class PuppetserverService ); } - const result = await client.getCertificates(status); + try { + const result = await client.getCertificates(status); + + if (!Array.isArray(result)) { + this.log( + "Unexpected response format from certificates endpoint", + "warn", + ); + return []; + } - if (!Array.isArray(result)) { + const certificates = result as Certificate[]; + + this.cache.set(cacheKey, certificates, this.cacheTTL); + this.log( + `Cached ${String(certificates.length)} certificates for ${String(this.cacheTTL)}ms`, + ); + + return certificates; + } catch (caError) { this.log( - "Unexpected response format from certificates endpoint", + "CA API not available, falling back to PuppetDB nodes as certificate source", "warn", ); + + // Fallback: Get certificates from PuppetDB nodes + const certificates = await this.getCertificatesFromPuppetDB(status); + + this.cache.set(cacheKey, certificates, this.cacheTTL); + this.log( + `Cached ${String(certificates.length)} certificates from PuppetDB fallback for ${String(this.cacheTTL)}ms`, + ); + + return certificates; + } + } catch (error) { + this.logError("Failed to list certificates", error); + throw error; + } + } + + /** + * Fallback method to get certificate information from PuppetDB nodes + * Used when CA API is not available in PE 2025.3.0+ + */ + private async getCertificatesFromPuppetDB(status?: CertificateStatus): Promise { + try { + // Get the PuppetDB service from the integration manager + const integrationManager = (global as any).integrationManager; + if (!integrationManager) { + this.log("Integration manager not available for PuppetDB fallback", "warn"); + return []; + } + + const puppetdbService = integrationManager.getInformationSource("puppetdb"); + if (!puppetdbService || !puppetdbService.isInitialized()) { + this.log("PuppetDB service not available for certificate fallback", "warn"); return []; } - const certificates = result as Certificate[]; + // Get all nodes from PuppetDB - these represent active certificates + const nodes = await puppetdbService.getInventory(); - this.cache.set(cacheKey, certificates, this.cacheTTL); - this.log( - `Cached ${String(certificates.length)} certificates for ${String(this.cacheTTL)}ms`, - ); + // Convert nodes to certificate format + const certificates: Certificate[] = nodes.map((node: any) => ({ + certname: node.certname || node.name || node.id, + status: "signed" as const, // Nodes in PuppetDB are signed certificates + fingerprint: "N/A", // Not available from PuppetDB + expiration: null, // Would need to be fetched separately + dns_alt_names: [], + authorization_extensions: {}, + state: "signed" as const, + })); + // Apply status filter if provided + if (status) { + return certificates.filter(cert => cert.status === status); + } + + this.log(`Retrieved ${certificates.length} certificates from PuppetDB fallback`); return certificates; } catch (error) { - this.logError("Failed to list certificates", error); - throw error; + this.logError("Failed to get certificates from PuppetDB fallback", error); + return []; } } diff --git a/backend/src/routes/integrations.ts b/backend/src/routes/integrations.ts index c499cdd..0521e15 100644 --- a/backend/src/routes/integrations.ts +++ b/backend/src/routes/integrations.ts @@ -162,6 +162,20 @@ export function createIntegrationsRouter( }); } + // Check if Bolt is not configured + if (!configuredNames.has("bolt")) { + integrations.push({ + name: "bolt", + type: "both", + status: "not_configured", + lastCheck: new Date().toISOString(), + message: "Bolt integration is not configured", + details: undefined, + workingCapabilities: undefined, + failingCapabilities: undefined, + }); + } + res.json({ integrations, timestamp: new Date().toISOString(), @@ -525,7 +539,9 @@ export function createIntegrationsRouter( // Get query parameters const queryParams = ReportsQuerySchema.parse(req.query); const limit = queryParams.limit || 100; // Default to 100 for summary - const hours = req.query.hours ? parseInt(String(req.query.hours), 10) : undefined; + const hours = req.query.hours + ? parseInt(String(req.query.hours), 10) + : undefined; // Get reports summary from PuppetDB const summary = await puppetDBService.getReportsSummary(limit, hours); @@ -1079,7 +1095,8 @@ export function createIntegrationsRouter( const certname = params.certname; // Get resources from PuppetDB - const resourcesByType = await puppetDBService.getNodeResources(certname); + const resourcesByType = + await puppetDBService.getNodeResources(certname); res.json({ resources: resourcesByType, @@ -1309,7 +1326,7 @@ export function createIntegrationsRouter( */ router.get( "/puppetdb/admin/archive", - asyncHandler(async (req: Request, res: Response): Promise => { + asyncHandler(async (_req: Request, res: Response): Promise => { if (!puppetDBService) { res.status(503).json({ error: { @@ -1381,7 +1398,7 @@ export function createIntegrationsRouter( */ router.get( "/puppetdb/admin/summary-stats", - asyncHandler(async (req: Request, res: Response): Promise => { + asyncHandler(async (_req: Request, res: Response): Promise => { if (!puppetDBService) { res.status(503).json({ error: { @@ -1408,7 +1425,8 @@ export function createIntegrationsRouter( res.json({ stats: summaryStats, source: "puppetdb", - warning: "This endpoint can be resource-intensive on large PuppetDB instances", + warning: + "This endpoint can be resource-intensive on large PuppetDB instances", }); } catch (error) { if (error instanceof PuppetDBAuthenticationError) { @@ -2563,10 +2581,17 @@ export function createIntegrationsRouter( try { // Validate request body - console.log("[Catalog Compare] Request body:", JSON.stringify(req.body)); + console.log( + "[Catalog Compare] Request body:", + JSON.stringify(req.body), + ); const body = CatalogCompareSchema.parse(req.body); const { certname, environment1, environment2 } = body; - console.log("[Catalog Compare] Parsed values:", { certname, environment1, environment2 }); + console.log("[Catalog Compare] Parsed values:", { + certname, + environment1, + environment2, + }); // Compare catalogs from Puppetserver const diff = await puppetserverService.compareCatalogs( @@ -2927,7 +2952,7 @@ export function createIntegrationsRouter( */ router.get( "/puppetserver/status/services", - asyncHandler(async (req: Request, res: Response): Promise => { + asyncHandler(async (_req: Request, res: Response): Promise => { if (!puppetserverService) { res.status(503).json({ error: { @@ -2978,7 +3003,10 @@ export function createIntegrationsRouter( return; } - console.error("Error fetching services status from Puppetserver:", error); + console.error( + "Error fetching services status from Puppetserver:", + error, + ); res.status(500).json({ error: { code: "INTERNAL_SERVER_ERROR", @@ -2998,7 +3026,7 @@ export function createIntegrationsRouter( */ router.get( "/puppetserver/status/simple", - asyncHandler(async (req: Request, res: Response): Promise => { + asyncHandler(async (_req: Request, res: Response): Promise => { if (!puppetserverService) { res.status(503).json({ error: { @@ -3069,7 +3097,7 @@ export function createIntegrationsRouter( */ router.get( "/puppetserver/admin-api", - asyncHandler(async (req: Request, res: Response): Promise => { + asyncHandler(async (_req: Request, res: Response): Promise => { if (!puppetserverService) { res.status(503).json({ error: { @@ -3120,7 +3148,10 @@ export function createIntegrationsRouter( return; } - console.error("Error fetching admin API info from Puppetserver:", error); + console.error( + "Error fetching admin API info from Puppetserver:", + error, + ); res.status(500).json({ error: { code: "INTERNAL_SERVER_ERROR", @@ -3169,7 +3200,8 @@ export function createIntegrationsRouter( try { // Get optional mbean parameter - const mbean = typeof req.query.mbean === "string" ? req.query.mbean : undefined; + const mbean = + typeof req.query.mbean === "string" ? req.query.mbean : undefined; const metrics = await puppetserverService.getMetrics(mbean); @@ -3177,7 +3209,8 @@ export function createIntegrationsRouter( metrics, source: "puppetserver", mbean, - warning: "This endpoint can be resource-intensive on Puppetserver. Use sparingly.", + warning: + "This endpoint can be resource-intensive on Puppetserver. Use sparingly.", }); } catch (error) { if (error instanceof PuppetserverConfigurationError) { diff --git a/backend/src/server.ts b/backend/src/server.ts index 87c8e40..a87e791 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -3,7 +3,7 @@ import cors from "cors"; import path from "path"; import { ConfigService } from "./config/ConfigService"; import { DatabaseService } from "./database/DatabaseService"; -import { BoltValidator } from "./validation/BoltValidator"; +import { BoltValidator, BoltValidationError } from "./validation/BoltValidator"; import { BoltService } from "./bolt/BoltService"; import { ExecutionRepository } from "./database/ExecutionRepository"; import { CommandWhitelistService } from "./validation/CommandWhitelistService"; @@ -48,11 +48,27 @@ async function startServer(): Promise { `- Command Whitelist Count: ${String(config.commandWhitelist.whitelist.length)}`, ); - // Validate Bolt configuration + // Validate Bolt configuration (non-blocking) console.warn("Validating Bolt configuration..."); const boltValidator = new BoltValidator(config.boltProjectPath); - boltValidator.validate(); - console.warn("Bolt configuration validated successfully"); + try { + boltValidator.validate(); + console.warn("Bolt configuration validated successfully"); + } catch (error) { + if (error instanceof BoltValidationError) { + console.warn(`Bolt validation failed: ${error.message}`); + if (error.details) { + console.warn(`Details: ${error.details}`); + } + if (error.missingFiles.length > 0) { + console.warn(`Missing files: ${error.missingFiles.join(", ")}`); + } + console.warn("Server will continue to start, but Bolt operations may be limited"); + } else { + console.warn(`Unexpected error during Bolt validation: ${String(error)}`); + console.warn("Server will continue to start, but Bolt operations may be limited"); + } + } // Initialize database console.warn("Initializing database..."); @@ -131,19 +147,59 @@ async function startServer(): Promise { console.warn("Initializing integration manager..."); const integrationManager = new IntegrationManager(); - // Register Bolt as an integration plugin - console.warn("Registering Bolt integration..."); - const boltPlugin = new BoltPlugin(boltService); - const boltConfig: IntegrationConfig = { - enabled: true, - name: "bolt", - type: "both", - config: { - projectPath: config.boltProjectPath, - }, - priority: 5, // Lower priority than PuppetDB - }; - integrationManager.registerPlugin(boltPlugin, boltConfig); + // Initialize Bolt integration only if configured + let boltPlugin: BoltPlugin | undefined; + const boltProjectPath = config.boltProjectPath; + + // Check if Bolt is properly configured by looking for project files + let boltConfigured = false; + if (boltProjectPath && boltProjectPath !== '.') { + const { existsSync } = await import("fs"); + const { join } = await import("path"); + + const inventoryYaml = join(boltProjectPath, "inventory.yaml"); + const inventoryYml = join(boltProjectPath, "inventory.yml"); + const boltProjectYaml = join(boltProjectPath, "bolt-project.yaml"); + const boltProjectYml = join(boltProjectPath, "bolt-project.yml"); + + const hasInventory = existsSync(inventoryYaml) || existsSync(inventoryYml); + const hasBoltProject = existsSync(boltProjectYaml) || existsSync(boltProjectYml); + + boltConfigured = hasInventory || hasBoltProject; + } + + console.warn("=== Bolt Integration Setup ==="); + console.warn(`Bolt configured: ${String(boltConfigured)}`); + console.warn(`Bolt project path: ${boltProjectPath || 'not set'}`); + + if (boltConfigured) { + console.warn("Registering Bolt integration..."); + try { + boltPlugin = new BoltPlugin(boltService); + const boltConfig: IntegrationConfig = { + enabled: true, + name: "bolt", + type: "both", + config: { + projectPath: config.boltProjectPath, + }, + priority: 5, // Lower priority than PuppetDB + }; + integrationManager.registerPlugin(boltPlugin, boltConfig); + console.warn("Bolt integration registered successfully"); + console.warn(`- Project Path: ${config.boltProjectPath}`); + } catch (error) { + console.warn( + `WARNING: Failed to initialize Bolt integration: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + boltPlugin = undefined; + } + } else { + console.warn( + "Bolt integration not configured - skipping registration", + ); + console.warn("Set BOLT_PROJECT_PATH to a valid project directory to enable Bolt integration"); + } // Initialize PuppetDB integration only if configured let puppetDBService: PuppetDBService | undefined; @@ -289,6 +345,9 @@ async function startServer(): Promise { console.warn("Integration manager initialized successfully"); console.warn("=== End Integration Plugin Initialization ==="); + // Make integration manager available globally for cross-service access + (global as any).integrationManager = integrationManager; + // Start health check scheduler for integrations if (integrationManager.getPluginCount() > 0) { integrationManager.startHealthCheckScheduler(); diff --git a/docs/PUPPETSERVER_SETUP.md b/docs/PUPPETSERVER_SETUP.md index fb1a15b..c33a4aa 100644 --- a/docs/PUPPETSERVER_SETUP.md +++ b/docs/PUPPETSERVER_SETUP.md @@ -6,7 +6,7 @@ This guide will help you configure the Puppetserver integration in Pabawi to man - A running Puppetserver instance (version 6.x or 7.x) - Network access to the Puppetserver API (default port 8140) -- Authentication credentials (either token or SSL certificates) +- Authentication credentials (token for Puppet Enterprise, or SSL certificates for all installations) ## Configuration Options @@ -29,14 +29,16 @@ PUPPETSERVER_PORT=8140 Choose one of the following authentication methods: -#### Option 1: Token Authentication (Recommended) +#### Option 1: Token Authentication (Puppet Enterprise Only) + +**Note: Token authentication is only available with Puppet Enterprise. Open Source Puppet installations must use certificate-based authentication.** ```bash -# API token for authentication +# API token for authentication (Puppet Enterprise only) PUPPETSERVER_TOKEN=your-api-token-here ``` -To generate a token: +To generate a token (Puppet Enterprise only): ```bash puppet access login --lifetime 1y @@ -82,10 +84,10 @@ PUPPETSERVER_CIRCUIT_BREAKER_RESET_TIMEOUT=30000 ## Complete Example Configuration -### Example 1: Token Authentication +### Example 1: Token Authentication (Puppet Enterprise Only) ```bash -# Puppetserver Integration +# Puppetserver Integration (Puppet Enterprise only) PUPPETSERVER_ENABLED=true PUPPETSERVER_SERVER_URL=https://puppet.example.com PUPPETSERVER_PORT=8140 @@ -218,7 +220,7 @@ Once configured, you can: ## Security Best Practices -1. **Use token authentication** when possible (easier to rotate) +1. **Use token authentication** when using Puppet Enterprise (easier to rotate than certificates) 2. **Store credentials securely** - never commit `.env` files 3. **Use SSL/TLS** for all connections 4. **Rotate tokens regularly** (set appropriate lifetime) diff --git a/docs/PUPPETSERVER_SETUP_SUMMARY.md b/docs/PUPPETSERVER_SETUP_SUMMARY.md index cf5ea03..c48b634 100644 --- a/docs/PUPPETSERVER_SETUP_SUMMARY.md +++ b/docs/PUPPETSERVER_SETUP_SUMMARY.md @@ -9,7 +9,7 @@ Complete setup guide including: - Prerequisites and requirements -- Two authentication methods (Token & SSL Certificate) +- Two authentication methods (Token for Puppet Enterprise & SSL Certificate for all installations) - All configuration options with detailed explanations - Step-by-step verification process - Troubleshooting guide for common issues @@ -81,8 +81,8 @@ PUPPETSERVER_CIRCUIT_BREAKER_RESET_TIMEOUT=30000 ### Authentication Options -- **Token Authentication** (Recommended): Easier to rotate, includes generation instructions -- **SSL Certificates**: More secure for production environments +- **Token Authentication** (Puppet Enterprise Only): Easier to rotate, includes generation instructions +- **SSL Certificates**: Required for Open Source Puppet, also available for Puppet Enterprise ### Interactive Elements @@ -107,7 +107,7 @@ PUPPETSERVER_CIRCUIT_BREAKER_RESET_TIMEOUT=30000 - `PUPPETSERVER_ENABLED`: Enable/disable the integration - `PUPPETSERVER_SERVER_URL`: Puppetserver API endpoint - `PUPPETSERVER_PORT`: API port (default: 8140) -- `PUPPETSERVER_TOKEN`: API authentication token +- `PUPPETSERVER_TOKEN`: API authentication token (Puppet Enterprise only) ### SSL Settings diff --git a/docs/api-endpoints-reference.md b/docs/api-endpoints-reference.md index 5fa2d15..f80094a 100644 --- a/docs/api-endpoints-reference.md +++ b/docs/api-endpoints-reference.md @@ -234,7 +234,7 @@ All endpoints return JSON responses with the following structure: | Header | Description | Applicable Endpoints | |--------|-------------|---------------------| | `X-Expert-Mode` | Enable expert mode | All endpoints | -| `X-Authentication-Token` | PuppetDB token | PuppetDB endpoints | +| `X-Authentication-Token` | PuppetDB token (PE only) | PuppetDB endpoints | | `X-Cache-Control` | Cache control | All endpoints | | `Content-Type` | Request content type | POST/PUT endpoints | | `Accept` | Response content type | All endpoints | diff --git a/docs/api.md b/docs/api.md index c255d60..6be9df3 100644 --- a/docs/api.md +++ b/docs/api.md @@ -42,8 +42,8 @@ http://localhost:3000/api Pabawi supports multiple authentication methods depending on the integration: - **Bolt**: No API-level authentication (authentication handled by Bolt for node connections) -- **PuppetDB**: Token-based authentication using RBAC tokens -- **Puppetserver**: Certificate-based authentication for CA operations +- **PuppetDB**: Token-based authentication using RBAC tokens (Puppet Enterprise only) or certificate-based authentication +- **Puppetserver**: Token-based authentication (Puppet Enterprise only) or certificate-based authentication for CA operations For detailed authentication setup and troubleshooting, see: diff --git a/docs/authentication.md b/docs/authentication.md index cd256ce..b81ca26 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -33,7 +33,9 @@ groups: ### Token-Based Authentication (PuppetDB) -PuppetDB supports token-based authentication using RBAC tokens from Puppet Enterprise or API tokens. +**Note: Token-based authentication is only available with Puppet Enterprise. Open Source Puppet and OpenVox require certificate-based authentication.** + +PuppetDB supports token-based authentication using RBAC tokens from Puppet Enterprise. **Configuration:** @@ -55,13 +57,15 @@ Or in your configuration file: } ``` -**Generating a PuppetDB Token (Puppet Enterprise):** +**Generating a PuppetDB Token (Puppet Enterprise Only):** ```bash puppet access login --lifetime 1y puppet access show ``` +**Note: The `puppet access` command is only available with Puppet Enterprise. Open Source Puppet installations must use certificate-based authentication.** + **Using the Token:** The token is automatically included in all PuppetDB API requests: diff --git a/docs/error-codes.md b/docs/error-codes.md index e110e73..6dcd267 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -72,7 +72,7 @@ When expert mode is enabled (via `X-Expert-Mode: true` header or `expertMode: tr | `PUPPETDB_NOT_CONFIGURED` | 503 | PuppetDB integration not configured | Missing configuration, integration disabled | | `PUPPETDB_NOT_INITIALIZED` | 503 | PuppetDB integration not initialized | Initialization failed, service not started | | `PUPPETDB_CONNECTION_ERROR` | 503 | Cannot connect to PuppetDB | PuppetDB offline, network issues, incorrect URL | -| `PUPPETDB_AUTH_ERROR` | 401 | Authentication failed | Invalid token, expired certificate, missing credentials | +| `PUPPETDB_AUTH_ERROR` | 401 | Authentication failed | Invalid token (PE only), expired certificate, missing credentials | | `PUPPETDB_QUERY_ERROR` | 400 | Invalid PQL query syntax | Malformed PQL query, unsupported query features | | `PUPPETDB_TIMEOUT` | 504 | PuppetDB request timeout | Query too complex, PuppetDB overloaded, timeout too short | | `NODE_NOT_FOUND` | 404 | Node not found in PuppetDB | Node never reported, deactivated node, typo in certname | @@ -101,7 +101,7 @@ When expert mode is enabled (via `X-Expert-Mode: true` header or `expertMode: tr | `INTEGRATION_NOT_CONFIGURED` | 503 | Integration not configured | Missing configuration, integration disabled | | `INTEGRATION_NOT_INITIALIZED` | 503 | Integration not initialized | Initialization failed, service not started | | `CONNECTION_ERROR` | 503 | Cannot connect to integration | Service offline, network issues, incorrect URL | -| `AUTH_ERROR` | 401 | Authentication failed | Invalid credentials, expired token/certificate | +| `AUTH_ERROR` | 401 | Authentication failed | Invalid credentials, expired token/certificate (tokens only available in PE) | | `TIMEOUT` | 504 | Request timeout | Service slow, timeout too short | ## Error Handling Best Practices diff --git a/docs/integrations-api.md b/docs/integrations-api.md index ff28f6e..1cdbf3f 100644 --- a/docs/integrations-api.md +++ b/docs/integrations-api.md @@ -19,7 +19,9 @@ This document describes the API endpoints for all Pabawi integrations including ### Token-Based Authentication -Some integrations (PuppetDB, Puppetserver) support token-based authentication: +**Note: Token-based authentication is only available with Puppet Enterprise. Open Source Puppet and OpenVox installations must use certificate-based authentication.** + +Some integrations (PuppetDB, Puppetserver) support token-based authentication when using Puppet Enterprise: ```http X-Authentication-Token: your-token-here diff --git a/docs/puppetdb-integration-setup.md b/docs/puppetdb-integration-setup.md index a93ad64..4dd661a 100644 --- a/docs/puppetdb-integration-setup.md +++ b/docs/puppetdb-integration-setup.md @@ -24,7 +24,7 @@ Before configuring PuppetDB integration, ensure you have: 1. **PuppetDB Server**: A running PuppetDB instance (version 6.0 or later recommended) 2. **Network Access**: Pabawi server can reach PuppetDB server (default port: 8081) -3. **Credentials**: Authentication token or SSL certificates for PuppetDB access +3. **Credentials**: Authentication token (Puppet Enterprise only) or SSL certificates for PuppetDB access 4. **Permissions**: Appropriate permissions to query PuppetDB data ### Verifying PuppetDB Availability @@ -331,9 +331,11 @@ PUPPETDB_SSL_REJECT_UNAUTHORIZED=true ## Authentication Setup -PuppetDB supports token-based authentication for API access. +**Important: Token-based authentication is only available with Puppet Enterprise. Open Source Puppet and OpenVox installations must use certificate-based authentication.** -### Token Authentication +PuppetDB supports token-based authentication for API access when using Puppet Enterprise. + +### Token Authentication (Puppet Enterprise Only) #### Obtaining a Token @@ -367,6 +369,8 @@ curl -X POST https://puppetdb.example.com:8081/pdb/admin/v1/token \ 4. Generate API token 5. Copy token for configuration +**Note: This method is only available with Puppet Enterprise installations.** + #### Configuring Token ```bash @@ -394,9 +398,9 @@ Pabawi requires read-only access to: - `/pdb/query/v4/catalogs` - `/pdb/query/v4/events` -### Combined SSL and Token Authentication +### Combined SSL and Token Authentication (Puppet Enterprise Only) -Most production deployments use both SSL and token authentication: +Most Puppet Enterprise production deployments use both SSL and token authentication: ```bash PUPPETDB_ENABLED=true @@ -896,7 +900,7 @@ Before deploying to production: - [ ] PuppetDB URL and port configured correctly - [ ] SSL/TLS enabled with proper certificates - [ ] Certificate validation enabled (`PUPPETDB_SSL_REJECT_UNAUTHORIZED=true`) -- [ ] Authentication token configured and tested +- [ ] Authentication token configured and tested (Puppet Enterprise only) - [ ] Token has minimum required permissions - [ ] `.env` file has restricted permissions (600) - [ ] Connection timeout appropriate for network diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 4a6ee22..61b44e7 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -30,11 +30,24 @@ -
+
-
+
+ + +
+
+

+ Made by Alessandro Franceschi (example42.com) and his AI assistants +

+
+
diff --git a/frontend/src/components/BoltSetupGuide.svelte b/frontend/src/components/BoltSetupGuide.svelte new file mode 100644 index 0000000..d642583 --- /dev/null +++ b/frontend/src/components/BoltSetupGuide.svelte @@ -0,0 +1,389 @@ + + +
+
+

Bolt Integration Setup

+

+ Configure Pabawi to use Bolt for remote command execution, task running, + and plan orchestration across your infrastructure. +

+
+ +
+
+

Prerequisites

+
    +
  • + + Bolt CLI installed (version 3.x or later) +
  • +
  • + + SSH or WinRM access to target nodes +
  • +
  • + + Bolt project directory with inventory and configuration +
  • +
+
+
+ +
+
+

Step 1: Choose Primary Transport

+ +
+ + + +
+ + {#if selectedTransport === "ssh"} +
+

SSH Configuration Requirements

+

Ensure SSH access is configured:

+
+
ssh-keygen -t rsa -b 4096
+
ssh-copy-id admin@target-node.example.com
+
ssh admin@target-node.example.com whoami
+
+
+ {:else} +
+

WinRM Configuration Requirements

+

Enable WinRM on Windows targets:

+
+
winrm quickconfig
+
winrm set winrm/config/service/auth @{'{'}Basic="true"{'}'}
+
winrm set winrm/config/service @{'{'}AllowUnencrypted="true"{'}'}
+
+
+ {/if} +
+
+ +
+
+

Step 2: Create Bolt Project Structure

+

Set up the required Bolt project files:

+ +
+
+ bolt-project.yaml + +
+
{boltProject}
+
+ +
+
+ + inventory.yaml - {selectedTransport === "ssh" ? "SSH" : "WinRM"} Example + + +
+
{selectedTransport === "ssh"
+            ? inventorySSH
+            : inventoryWinRM}
+
+
+
+ +
+
+

Step 3: Configure Environment Variables

+

Add these variables to your backend/.env file:

+ +
+
+ + {selectedTransport === "ssh" + ? "SSH Transport Config" + : "WinRM Transport Config"} + + +
+
{selectedTransport === "ssh"
+            ? sshConfig
+            : winrmConfig}
+
+ + + + {#if showAdvanced} +
+
+ Advanced Options + +
+
{advancedConfig}
+
+ +
+

Configuration Options:

+
    +
  • + EXECUTION_TIMEOUT: Maximum execution time in milliseconds + (default: 300000) +
  • +
  • + CONCURRENT_EXECUTION_LIMIT: Max parallel executions + (default: 10) +
  • +
  • + STREAMING_*: Real-time output streaming settings +
  • +
+
+ {/if} +
+
+ +
+
+

Step 4: Restart Backend Server

+

Apply the configuration by restarting the backend:

+
+
cd backend
+
npm run dev
+
+
+
+ +
+
+

Step 5: Verify Connection

+

Check the integration status:

+
    +
  1. Navigate to the Integrations page
  2. +
  3. Look for "Bolt" in the list
  4. +
  5. Status should show "healthy" with a green indicator
  6. +
+ +

Or test via API:

+
+ curl http://localhost:3000/api/inventory +
+
+
+ +
+
+

Features Available

+
+
+ +

Command Execution

+

Run ad-hoc commands across nodes

+
+
+ 📦 +

Task Running

+

Execute Puppet tasks and modules

+
+
+ 🎯 +

Plan Orchestration

+

Run complex multi-step plans

+
+
+ 📊 +

Inventory Management

+

Dynamic node discovery and targeting

+
+
+
+
+ +
+
+

Troubleshooting

+ +
+
+ + Bolt Configuration Errors + +
+

Error: "Bolt configuration files not found"

+
    +
  • Verify BOLT_PROJECT_PATH points to correct directory
  • +
  • + Check files exist: ls -la ./bolt-project/inventory.yaml +
  • +
  • Ensure bolt-project.yaml has color: false
  • +
+
+
+ +
+ + Connection Errors + +
+

Error: "Node unreachable"

+
    +
  • + Test SSH: ssh user@target-node.example.com whoami +
  • +
  • + Test WinRM: winrs -r:target-node.example.com whoami +
  • +
  • Check firewall rules and network connectivity
  • +
+
+
+ +
+ + Command Whitelist Errors + +
+

Error: "Command not allowed"

+
    +
  • + Add command to whitelist: COMMAND_WHITELIST=["ls","pwd","your-command"] +
  • +
  • + Or allow all: COMMAND_WHITELIST_ALLOW_ALL=true +
  • +
  • Restart backend after changes
  • +
+
+
+
+
+
+ +
+

+ For detailed documentation, see configuration.md +

+
+
diff --git a/frontend/src/components/CertificateManagement.svelte b/frontend/src/components/CertificateManagement.svelte index f96eec6..07f1e22 100644 --- a/frontend/src/components/CertificateManagement.svelte +++ b/frontend/src/components/CertificateManagement.svelte @@ -85,15 +85,16 @@ loading = true; error = null; const startTime = performance.now(); - const data = await get('/api/integrations/puppetserver/certificates'); + const response = await get<{certificates: Certificate[], source: string, count: number, filtered: boolean}>('/api/integrations/puppetserver/certificates'); const endTime = performance.now(); - certificates = data; + certificates = response.certificates; if (expertMode.enabled) { console.log('[CertificateManagement] Successfully loaded', certificates.length, 'certificates'); console.log('[CertificateManagement] Response time:', Math.round(endTime - startTime), 'ms'); - console.log('[CertificateManagement] Received data:', data); + console.log('[CertificateManagement] Received response:', response); + console.log('[CertificateManagement] Certificates:', certificates); } } catch (err) { console.error('[CertificateManagement] Error loading certificates:', err); diff --git a/frontend/src/components/IntegrationStatus.svelte b/frontend/src/components/IntegrationStatus.svelte index d2ba5e7..9787511 100644 --- a/frontend/src/components/IntegrationStatus.svelte +++ b/frontend/src/components/IntegrationStatus.svelte @@ -49,6 +49,8 @@ return `/integrations/${name}/setup`; } + + // Format last check time function formatLastCheck(timestamp: string): string { try { @@ -272,6 +274,8 @@
{/if} + + {#if integration.details && integration.status === 'error'}
Configure the integration using environment variables or config file
  • Check the setup instructions for required parameters
  • {:else if integration.status === 'error' || integration.status === 'disconnected'} +
  • Verify if you have the command available
  • Verify the service is running and accessible
  • Check network connectivity and firewall rules
  • Verify authentication credentials are correct
  • diff --git a/frontend/src/components/ManagedResourcesViewer.svelte b/frontend/src/components/ManagedResourcesViewer.svelte index cb3bf6a..9ae9fe8 100644 --- a/frontend/src/components/ManagedResourcesViewer.svelte +++ b/frontend/src/components/ManagedResourcesViewer.svelte @@ -272,16 +272,20 @@ {#if selectedResource}