From 09a564f792a6ca35723c615ca81188b9b6df8b50 Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Tue, 15 Apr 2025 21:16:46 +0200 Subject: [PATCH] init --- CONTAINER_INSTRUCTIONS.md | 227 ++++++++++++++++++++++++++++++++++++++ Dockerfile | 39 +++++++ README.md | 162 +++++++++++++++++++++++++++ main.py | 85 ++++++++++++++ requirements.txt | 4 + run_container.sh | 69 ++++++++++++ source/users.json | 1 + 7 files changed, 587 insertions(+) create mode 100644 CONTAINER_INSTRUCTIONS.md create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 main.py create mode 100644 requirements.txt create mode 100755 run_container.sh create mode 100644 source/users.json diff --git a/CONTAINER_INSTRUCTIONS.md b/CONTAINER_INSTRUCTIONS.md new file mode 100644 index 0000000..80a194f --- /dev/null +++ b/CONTAINER_INSTRUCTIONS.md @@ -0,0 +1,227 @@ +# Container Instructions for VPN Session Viewer + +This guide explains how to run the VPN Session Viewer application in a secure rootless container with persistent log storage using Podman or Docker. + +## Prerequisites + +- [Podman](https://podman.io/getting-started/installation) (version 3.0 or higher) or [Docker](https://docs.docker.com/get-docker/) (version 20.10 or higher) + +## Security Features + +This deployment includes the following security features: + +1. **Rootless container**: The application runs as a non-root user (UID 1000) +2. **Dropped capabilities**: All Linux capabilities are dropped +3. **No privilege escalation**: The container cannot gain additional privileges +4. **Minimal base image**: Uses a slim Python image to reduce attack surface +5. **Non-privileged ports**: Uses port 8000 instead of privileged ports (<1024) +6. **Persistent volume**: VPN logs are stored in a volume for persistence + +## Quick Start with Provided Script + +The easiest way to run the container is using the included script: + +```bash +./run_container.sh +``` + +This script will automatically: +1. Detect whether to use Podman or Docker +2. Build the container image +3. Create a logs directory if it doesn't exist +4. Run the container with all necessary security settings + +## Manual Setup with Podman + +### Building the Container + +```bash +podman build -t vpn-session-viewer:latest . +``` + +### Creating the Logs Directory + +```bash +mkdir -p ./logs +``` + +### Running the Container + +```bash +podman run --name vpn-session-viewer \ + -p 8000:8000 \ + -v ./logs:/home/appuser/app/logs:Z \ + --security-opt no-new-privileges:true \ + --cap-drop ALL \ + --user 1000:1000 \ + -d vpn-session-viewer:latest +``` + +### Checking Container Status + +```bash +podman ps +``` + +### Accessing the Application + +Open your browser to: +``` +http://localhost:8000 +``` + +## Manual Setup with Docker + +### Building the Container + +```bash +docker build -t vpn-session-viewer:latest . +``` + +### Creating the Logs Directory + +```bash +mkdir -p ./logs +``` + +### Running the Container + +```bash +docker run --name vpn-session-viewer \ + -p 8000:8000 \ + -v ./logs:/home/appuser/app/logs \ + --security-opt no-new-privileges:true \ + --cap-drop ALL \ + --user 1000:1000 \ + -d vpn-session-viewer:latest +``` + +### Checking Container Status + +```bash +docker ps +``` + +### Accessing the Application + +Open your browser to: +``` +http://localhost:8000 +``` + +## Working with VPN Logs + +### Log File Format + +Log files should follow this naming convention: +``` +{gateway-name}_{ISO-timestamp}.logs +``` + +Example: `firewall-1_2025-04-10T17:04:51Z.logs` + +### Adding Log Files + +Simply place your VPN log files in the `./logs` directory on your host machine. The container will automatically access them. + +## Maintenance + +### View Logs + +**Podman:** +```bash +podman logs vpn-session-viewer +``` + +**Docker:** +```bash +docker logs vpn-session-viewer +``` + +### Restart the Application + +**Podman:** +```bash +podman restart vpn-session-viewer +``` + +**Docker:** +```bash +docker restart vpn-session-viewer +``` + +### Stop the Application + +**Podman:** +```bash +podman stop vpn-session-viewer +``` + +**Docker:** +```bash +docker stop vpn-session-viewer +``` + +### Remove the Container + +**Podman:** +```bash +podman rm vpn-session-viewer +``` + +**Docker:** +```bash +docker rm vpn-session-viewer +``` + +## Troubleshooting + +### Check Container Status + +**Podman:** +```bash +podman ps -a +``` + +**Docker:** +```bash +docker ps -a +``` + +### Inspect the Container + +**Podman:** +```bash +podman inspect vpn-session-viewer +``` + +**Docker:** +```bash +docker inspect vpn-session-viewer +``` + +### Access Container Shell + +**Podman:** +```bash +podman exec -it vpn-session-viewer bash +``` + +**Docker:** +```bash +docker exec -it vpn-session-viewer bash +``` + +### Check Files in Container + +To verify logs are correctly mounted: + +**Podman:** +```bash +podman exec -it vpn-session-viewer ls -la /home/appuser/app/logs +``` + +**Docker:** +```bash +docker exec -it vpn-session-viewer ls -la /home/appuser/app/logs +``` \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f89f9d8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# Use Python 3.11 slim image for a smaller footprint +FROM python:3.11-slim + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + HOME=/home/appuser \ + APP_HOME=/home/appuser/app + +# Create non-root user and setup directories +RUN groupadd -g 1000 appgroup && \ + useradd -m -u 1000 -g appgroup -s /bin/bash -d ${HOME} appuser && \ + mkdir -p ${APP_HOME} && \ + mkdir -p ${APP_HOME}/source && \ + chown -R appuser:appgroup ${HOME} + +# Set the working directory +WORKDIR ${APP_HOME} + +# Install dependencies +COPY --chown=appuser:appgroup requirements.txt ${APP_HOME}/ +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY --chown=appuser:appgroup main.py ${APP_HOME}/ + +# Create a volume for source files +VOLUME ["${APP_HOME}/source"] + +# Switch to non-root user +USER appuser + +# Expose port +EXPOSE 8000 + +# Command to run the application +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d5a4390 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +# Static2API + +A simple FastAPI application that automatically exposes CSV and JSON files as API endpoints. + +## Features + +- Automatically creates API endpoints for CSV and JSON files in the `source` directory +- Files are accessible via `/api/{filename}` endpoints +- New files are detected automatically without restarting the server +- Runs in a secure container with Docker or Podman + +## Quick Start + +### Option 1: Run with Python + +```bash +# Install dependencies +pip install -r requirements.txt + +# Run the application +uvicorn main:app --reload +``` + +The API will be available at: http://localhost:8000 + +### Option 2: Run with Docker/Podman + +Use the provided script to run the application in a container: + +```bash +# Make the script executable +chmod +x run_container.sh + +# Run the container +./run_container.sh +``` + +The API will be available at: http://localhost:8000 + +## API Endpoints + +- `GET /` - Welcome message +- `GET /api/{filename}` - Access data from files in the source directory + +Example: +- `GET /api/contacts` - Returns data from `source/contacts.csv` +- `GET /api/users` - Returns data from `source/users.json` + +## Sample Responses + +### CSV Example (from contacts.csv) + +```bash +curl http://localhost:8000/api/contacts +``` + +Response: +```json +[ + { + "location": "dortmund", + "contact": "achim" + }, + { + "location": "madrid", + "contact": "santos" + } +] +``` + +### JSON Example (from users.json) + +```bash +curl http://localhost:8000/api/users +``` + +Response: +```json +{ + "users": [ + { + "name": "John", + "email": "john@example.com" + }, + { + "name": "Jane", + "email": "jane@example.com" + } + ] +} +``` + +## Adding New Files + +1. Place CSV or JSON files in the `source` directory +2. Files will be automatically accessible via `/api/{filename}` +3. No restart required + +## File Format Support + +- CSV files: Must have a header row with column names +- JSON files: Must contain valid JSON data + +## Manual Container Setup + +### Docker + +```bash +# Build the image +docker build -t static2api:latest . + +# Run the container +docker run --name static2api \ + -p 8000:8000 \ + -v "./source":/home/appuser/app/source \ + --security-opt no-new-privileges:true \ + --cap-drop ALL \ + --user 1000:1000 \ + -d static2api:latest +``` + +### Podman + +```bash +# Build the image +podman build -t static2api:latest . + +# Run the container +podman run --name static2api \ + -p 8000:8000 \ + -v "./source":/home/appuser/app/source:Z \ + --security-opt no-new-privileges:true \ + --cap-drop ALL \ + --user 1000:1000 \ + -d static2api:latest +``` + +## Container Management + +### View Logs + +```bash +docker logs static2api +# or +podman logs static2api +``` + +### Stop Container + +```bash +docker stop static2api +# or +podman stop static2api +``` + +### Remove Container + +```bash +docker rm static2api +# or +podman rm static2api +``` \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..bc2dd32 --- /dev/null +++ b/main.py @@ -0,0 +1,85 @@ +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +import os +import csv +import json +from pathlib import Path + +app = FastAPI(title="Static2API") + +@app.get("/") +async def root(): + return {"message": "Welcome to Static2API"} + +def load_csv(file_path): + data = [] + with open(file_path, 'r') as f: + reader = csv.DictReader(f) + for row in reader: + data.append(dict(row)) + return data + +def load_json(file_path): + with open(file_path, 'r') as f: + return json.load(f) + +@app.on_event("startup") +async def startup_event(): + # Register API endpoints for all files in the source directory + register_api_endpoints() + +def register_api_endpoints(): + source_dir = Path("source") + if not source_dir.exists(): + return + + for file_path in source_dir.glob("*"): + if file_path.is_file(): + endpoint_name = file_path.stem + route = f"/api/{endpoint_name}" + + # Skip if route already exists + if any(route == route_info.path for route_info in app.routes): + continue + + if file_path.suffix.lower() == '.csv': + # Create a closure to capture the current file_path + async def get_csv_data(request: Request, file_path=file_path): + try: + data = load_csv(file_path) + return data + except Exception as e: + return JSONResponse( + status_code=500, + content={"error": f"Failed to load CSV: {str(e)}"} + ) + + # Add the route + app.add_api_route(route, get_csv_data, methods=["GET"]) + + elif file_path.suffix.lower() == '.json': + # Create a closure to capture the current file_path + async def get_json_data(request: Request, file_path=file_path): + try: + data = load_json(file_path) + return data + except Exception as e: + return JSONResponse( + status_code=500, + content={"error": f"Failed to load JSON: {str(e)}"} + ) + + # Add the route + app.add_api_route(route, get_json_data, methods=["GET"]) + +@app.middleware("http") +async def check_for_new_files(request: Request, call_next): + # Re-register endpoints before processing each request + # This ensures new files added while the server is running get endpoints + register_api_endpoints() + response = await call_next(request) + return response + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6f91dc3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.104.1 +uvicorn==0.23.2 +jinja2==3.1.2 +pydantic==2.4.2 \ No newline at end of file diff --git a/run_container.sh b/run_container.sh new file mode 100755 index 0000000..5a11df3 --- /dev/null +++ b/run_container.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Set container name +CONTAINER_NAME="static2api" + +# Determine if we use podman or docker +if command_exists podman; then + CONTAINER_CMD="podman" + VOLUME_FLAG=":Z" + echo "Using Podman for container management." +elif command_exists docker; then + CONTAINER_CMD="docker" + VOLUME_FLAG="" + echo "Using Docker for container management." +else + echo "Error: Neither Podman nor Docker found. Please install one of them first." + exit 1 +fi + +# Stop and remove container if it exists +echo "Checking for existing container..." +if $CONTAINER_CMD ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then + echo "Stopping and removing existing ${CONTAINER_NAME} container..." + $CONTAINER_CMD stop ${CONTAINER_NAME} + $CONTAINER_CMD rm ${CONTAINER_NAME} +fi + +# Build the container image +echo "Building container image..." +$CONTAINER_CMD build -t ${CONTAINER_NAME}:latest . + +# Set up source directory to mount +SOURCE_DIR="./source" +if [ ! -d "$SOURCE_DIR" ]; then + echo "Creating source directory..." + mkdir -p "$SOURCE_DIR" +fi + +# Run the container +echo "Starting container..." +$CONTAINER_CMD run --name ${CONTAINER_NAME} \ + -p 8000:8000 \ + -v "$SOURCE_DIR":/home/appuser/app/source${VOLUME_FLAG} \ + --security-opt no-new-privileges:true \ + --cap-drop ALL \ + --user 1000:1000 \ + -d ${CONTAINER_NAME}:latest + +# Check if container started successfully +if [ $? -eq 0 ]; then + echo "Container started successfully!" + echo "Static2API is available at: http://localhost:8000" + echo "" + echo "Container logs:" + $CONTAINER_CMD logs ${CONTAINER_NAME} + + echo "" + echo "Note: CSV/JSON files should be placed in the ./source directory." + echo " They will be available at: http://localhost:8000/api/{filename}" + echo " Example: ./source/contacts.csv -> http://localhost:8000/api/contacts" +else + echo "Failed to start container." + exit 1 +fi \ No newline at end of file diff --git a/source/users.json b/source/users.json new file mode 100644 index 0000000..42b0693 --- /dev/null +++ b/source/users.json @@ -0,0 +1 @@ +{"users": [{"name": "John", "email": "john@example.com"}, {"name": "Jane", "email": "jane@example.com"}]}