init
This commit is contained in:
commit
09a564f792
7 changed files with 587 additions and 0 deletions
227
CONTAINER_INSTRUCTIONS.md
Normal file
227
CONTAINER_INSTRUCTIONS.md
Normal 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
39
Dockerfile
Normal 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
162
README.md
Normal 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
85
main.py
Normal 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
4
requirements.txt
Normal 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
69
run_container.sh
Executable 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
1
source/users.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"users": [{"name": "John", "email": "john@example.com"}, {"name": "Jane", "email": "jane@example.com"}]}
|
Loading…
Add table
Add a link
Reference in a new issue