This commit is contained in:
CaffeineFueled 2025-04-15 21:16:46 +02:00
commit 09a564f792
7 changed files with 587 additions and 0 deletions

227
CONTAINER_INSTRUCTIONS.md Normal file
View file

@ -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
```

39
Dockerfile Normal file
View file

@ -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"]

162
README.md Normal file
View file

@ -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
```

85
main.py Normal file
View file

@ -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)

4
requirements.txt Normal file
View file

@ -0,0 +1,4 @@
fastapi==0.104.1
uvicorn==0.23.2
jinja2==3.1.2
pydantic==2.4.2

69
run_container.sh Executable file
View file

@ -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

1
source/users.json Normal file
View file

@ -0,0 +1 @@
{"users": [{"name": "John", "email": "john@example.com"}, {"name": "Jane", "email": "jane@example.com"}]}