From 47342a04c7abf4d7c4f3d52af1b18dfa91f97e18 Mon Sep 17 00:00:00 2001 From: livehybrid Date: Tue, 11 Mar 2025 09:54:55 +0000 Subject: [PATCH] feat: Enhanced SSL debugging and updated documentation --- README.md | 199 +++++++++++++++++++------------------------------- splunk_mcp.py | 105 ++++++++++++++++++++------ 2 files changed, 160 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index fa7aa3d..df9c74b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ A FastMCP-based tool for interacting with Splunk Enterprise/Cloud through natura - **KV Store Operations**: Create, list, and manage KV store collections - **Async Support**: Built with async/await patterns for better performance - **Detailed Logging**: Comprehensive logging with emoji indicators for better visibility +- **SSL Configuration**: Flexible SSL verification options for different security requirements +- **Enhanced Debugging**: Detailed connection and error logging for troubleshooting - **Comprehensive Testing**: Unit tests covering all major functionality ## Prerequisites @@ -46,96 +48,132 @@ SPLUNK_PORT=8089 SPLUNK_USERNAME=your_username SPLUNK_PASSWORD=your_password SPLUNK_SCHEME=https +VERIFY_SSL=true FASTMCP_LOG_LEVEL=INFO ``` ### Option 2: Docker Installation -1. Clone the repository: +1. Pull the latest image: ```bash -git clone -cd splunk-mcp +docker pull livehybrid/splunk-mcp:latest ``` -2. Copy the example environment file and configure your settings: -```bash -cp .env.example .env -``` +2. Create your `.env` file as above or use environment variables directly. -3. Update the `.env` file with your Splunk credentials (same as above). - -4. Build and run using Docker Compose: +3. Run using Docker Compose: ```bash docker-compose up -d ``` Or using Docker directly: ```bash -# Build the image -docker build -t splunk-mcp . - -# Run the container -docker run -d \ - -p 3000:3000 \ +docker run -i \ --env-file .env \ - --name splunk-mcp \ - splunk-mcp + livehybrid/splunk-mcp ``` ## Usage ### Local Usage -1. Start the MCP server: +The tool can run in two modes: + +1. STDIO mode (default) - for command-line integration: ```bash poetry run python splunk_mcp.py ``` +2. SSE mode - for web server integration: +```bash +poetry run python splunk_mcp.py sse +``` + ### Docker Usage If using Docker Compose: ```bash -# Start the service +# Start the service in STDIO mode (default) docker-compose up -d -# View logs -docker-compose logs -f - -# Stop the service -docker-compose down +# Start in SSE mode +docker-compose run --rm splunk-mcp python splunk_mcp.py sse ``` If using Docker directly: ```bash -# Start the container -docker start splunk-mcp +# Start in STDIO mode (default) +docker run -i \ + --env-file .env \ + livehybrid/splunk-mcp -# View logs -docker logs -f splunk-mcp - -# Stop the container -docker stop splunk-mcp +# Start in SSE mode +docker run -d \ + -p 3000:3000 \ + --env-file .env \ + livehybrid/splunk-mcp python splunk_mcp.py sse ``` -The server will start and listen for connections on port 3000 in both local and Docker installations. +### Environment Variables -### Docker Environment Variables - -When running with Docker, you can configure the following environment variables: +Configure the following environment variables: - `SPLUNK_HOST`: Your Splunk host address - `SPLUNK_PORT`: Splunk management port (default: 8089) - `SPLUNK_USERNAME`: Your Splunk username - `SPLUNK_PASSWORD`: Your Splunk password - `SPLUNK_SCHEME`: Connection scheme (default: https) +- `VERIFY_SSL`: Enable/disable SSL verification (default: true) - `FASTMCP_LOG_LEVEL`: Logging level (default: INFO) -These can be set either in the `.env` file or passed directly to Docker using the `-e` flag. +### SSL Configuration + +The tool provides flexible SSL verification options: + +1. **Default (Secure) Mode**: +```env +VERIFY_SSL=true +``` +- Full SSL certificate verification +- Hostname verification enabled +- Recommended for production environments + +2. **Relaxed Mode**: +```env +VERIFY_SSL=false +``` +- SSL certificate verification disabled +- Hostname verification disabled +- Useful for testing or self-signed certificates + +### Troubleshooting + +#### Connection Issues + +1. **Basic Connectivity**: +- The tool now performs a basic TCP connectivity test +- Check if port 8089 is accessible +- Verify network routing and firewalls + +2. **SSL Issues**: +- If seeing SSL errors, try setting `VERIFY_SSL=false` +- Check certificate validity and trust chain +- Verify hostname matches certificate + +3. **Authentication Issues**: +- Verify Splunk credentials +- Check user permissions +- Ensure account is not locked + +4. **Debugging**: +- Set `FASTMCP_LOG_LEVEL=DEBUG` for detailed logs +- Check connection logs for specific error messages +- Review SSL configuration messages ### Available Tools 1. **search_splunk** - Execute Splunk searches with customizable time ranges - - Example: Search for hosts sending data in the last hour + - Example: Search for events in the last hour ```python search_query="index=* | stats count by host" ``` @@ -153,98 +191,15 @@ These can be set either in the `.env` file or passed directly to Docker using th - create_kvstore_collection: Create new collections - delete_kvstore_collection: Remove existing collections -## Example Queries - -1. Search for temperature data: -```python -search_query="index=main sourcetype=httpevent *temperature* | stats avg(value) by location" -``` - -2. List all indexes: -```python -await list_indexes() -``` - -3. View user information: -```python -await list_users() -``` - ## Development -### Project Structure - -- `splunk_mcp.py`: Main implementation file -- `pyproject.toml`: Poetry project configuration -- `.env`: Environment configuration -- `README.md`: Documentation -- `tests/`: Unit tests directory - - `test_splunk_mcp.py`: Test suite for Splunk MCP functionality - ### Running Tests -The project uses pytest for testing. All tests are written to work without requiring an actual Splunk connection, using mocks to simulate Splunk's behavior. - -1. Run all tests: ```bash poetry run pytest ``` -2. Run tests with coverage: -```bash -poetry run pytest --cov=splunk_mcp tests/ -``` - -3. Run specific test file: -```bash -poetry run pytest tests/test_splunk_mcp.py -``` - -4. Run tests with verbose output: -```bash -poetry run pytest -v -``` - -The test suite includes: -- Unit tests for all Splunk operations (search, index listing, user management) -- KV store operation tests -- Connection handling tests -- Error case testing - -### Adding New Tests - -When adding new features: -1. Create corresponding test cases in `tests/test_splunk_mcp.py` -2. Use the provided mock fixtures for Splunk service simulation -3. Add appropriate assertions to verify functionality -4. Ensure both success and error cases are covered - -## Troubleshooting - -Common issues and solutions: - -1. Connection Issues - - Verify Splunk credentials in `.env` - - Check network connectivity - - Ensure Splunk management port (8089) is accessible - - If using Docker, ensure the container has network access to your Splunk instance - -2. Docker Issues - - Check container logs: `docker logs splunk-mcp` - - Verify environment variables are properly set - - Ensure port 3000 is not in use by another service - - Check container status: `docker ps -a` - -2. Permission Issues - - Verify user has appropriate Splunk roles - - Check app/collection access permissions - -3. Search Issues - - Validate search syntax - - Check time ranges - - Verify index access permissions - -## Contributing +### Contributing 1. Fork the repository 2. Create a feature branch diff --git a/splunk_mcp.py b/splunk_mcp.py index af5d4c6..3458964 100644 --- a/splunk_mcp.py +++ b/splunk_mcp.py @@ -5,6 +5,10 @@ from decouple import config import splunklib.client as client from splunklib import results from datetime import datetime, timedelta +import sys +import ssl +import socket +import traceback # Configure logging logging.basicConfig( @@ -17,24 +21,62 @@ mcp = FastMCP("splunk") # Splunk connection configuration SPLUNK_HOST = config("SPLUNK_HOST", default="localhost") -SPLUNK_PORT = config("SPLUNK_PORT", default=8089) +SPLUNK_PORT = config("SPLUNK_PORT", default=8089, cast=int) SPLUNK_USERNAME = config("SPLUNK_USERNAME", default="admin") SPLUNK_PASSWORD = config("SPLUNK_PASSWORD") SPLUNK_SCHEME = config("SPLUNK_SCHEME", default="https") +VERIFY_SSL = config("VERIFY_SSL", default="true", cast=bool) def get_splunk_connection(): """Helper function to establish Splunk connection""" try: - service = client.connect( - host=SPLUNK_HOST, - port=SPLUNK_PORT, - username=SPLUNK_USERNAME, - password=SPLUNK_PASSWORD, - scheme=SPLUNK_SCHEME - ) - return service + logger.info(f"🔌 Attempting to connect to Splunk at {SPLUNK_SCHEME}://{SPLUNK_HOST}:{SPLUNK_PORT}") + logger.info(f"SSL Verification is {'enabled' if VERIFY_SSL else 'disabled'}") + + # Test basic connectivity first + try: + sock = socket.create_connection((SPLUNK_HOST, SPLUNK_PORT), timeout=10) + sock.close() + logger.info("✅ Basic TCP connection test successful") + except Exception as e: + logger.error(f"❌ Failed to establish basic TCP connection: {str(e)}") + raise + + # Configure SSL context with detailed logging + if not VERIFY_SSL: + logger.info("🔒 Creating custom SSL context with verification disabled") + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + logger.info("✅ SSL context configured with verification disabled") + else: + logger.info("🔒 Using default SSL context with verification enabled") + ssl_context = None + + # Attempt Splunk connection with detailed logging + try: + service = client.connect( + host=SPLUNK_HOST, + port=SPLUNK_PORT, + username=SPLUNK_USERNAME, + password=SPLUNK_PASSWORD, + scheme=SPLUNK_SCHEME, + ssl_context=ssl_context + ) + logger.info("✅ Successfully established Splunk connection") + return service + except ssl.SSLError as e: + logger.error(f"❌ SSL Error during connection: {str(e)}") + logger.error(f"SSL Error details: {traceback.format_exc()}") + raise + except Exception as e: + logger.error(f"❌ Error during Splunk connection: {str(e)}") + logger.error(f"Error details: {traceback.format_exc()}") + raise + except Exception as e: - logger.error(f"Failed to connect to Splunk: {str(e)}") + logger.error(f"❌ Failed to connect to Splunk: {str(e)}") + logger.error(f"Full error details: {traceback.format_exc()}") raise @mcp.tool() @@ -95,7 +137,7 @@ async def search_splunk( raise @mcp.tool() -async def list_indexes() -> List[Dict[str, Any]]: # Made async +async def list_indexes() -> List[Dict[str, Any]]: """ List all available Splunk indexes @@ -108,15 +150,27 @@ async def list_indexes() -> List[Dict[str, Any]]: # Made async indexes = [] for index in service.indexes: - index_info = { - "name": index.name, - "total_event_count": index["totalEventCount"], - "current_size": index["currentDBSizeMB"], - "max_size": index["maxTotalDataSizeMB"], - "earliest_time": index["earliestTime"], - "latest_time": index["latestTime"] - } - indexes.append(index_info) + try: + index_info = { + "name": index.name, + "total_event_count": index.get("totalEventCount", "0"), + "current_size": index.get("currentDBSizeMB", "0"), + "max_size": index.get("maxTotalDataSizeMB", "0"), + "earliest_time": index.get("earliestTime", "0"), + "latest_time": index.get("latestTime", "0") + } + indexes.append(index_info) + except Exception as e: + logger.warning(f"⚠️ Error accessing metadata for index {index.name}: {str(e)}") + # Add basic information if metadata access fails + indexes.append({ + "name": index.name, + "total_event_count": "0", + "current_size": "0", + "max_size": "0", + "earliest_time": "0", + "latest_time": "0" + }) logger.info(f"✅ Found {len(indexes)} indexes") return indexes @@ -261,5 +315,12 @@ async def delete_kvstore_collection( # Made async raise if __name__ == "__main__": - logger.info("🚀 Starting Splunk MCP server") - mcp.run(transport="sse") # Added SSE transport \ No newline at end of file + # Get transport mode from command line argument, default to stdio + transport_mode = "stdio" + if len(sys.argv) > 1 and sys.argv[1].lower() == "sse": + transport_mode = "sse" + logger.info("🚀 Starting Splunk MCP server in SSE mode") + else: + logger.info("🚀 Starting Splunk MCP server in STDIO mode") + + mcp.run(transport=transport_mode) \ No newline at end of file