add rate limiting protection

This commit is contained in:
Jeff Caldwell
2025-08-01 19:35:14 -04:00
parent f62993775e
commit 567bd29417
7 changed files with 163 additions and 11 deletions

2
.env
View File

@@ -1,5 +1,5 @@
# Authentication test configuration
ENABLE_AUTH=true
ENABLE_AUTH=false
AUTH_USERNAME=admin
AUTH_PASSWORD=admin
SESSION_SECRET=test-secret-key-for-development

View File

@@ -12,6 +12,12 @@ NODE_ENV=development # Environment mode (development/production)
CERT_DIR= # Custom certificate storage directory (optional)
DEFAULT_THEME=dark # Default theme mode (dark/light)
# Rate Limiting Configuration
CLI_RATE_LIMIT_WINDOW=900000 # CLI rate limit window in ms (default: 15 minutes)
CLI_RATE_LIMIT_MAX=10 # Max CLI operations per window (default: 10)
API_RATE_LIMIT_WINDOW=900000 # API rate limit window in ms (default: 15 minutes)
API_RATE_LIMIT_MAX=100 # Max API requests per window (default: 100)
# Authentication Configuration
ENABLE_AUTH=false # Enable user authentication (true/false)
AUTH_USERNAME=admin # Username for authentication (when ENABLE_AUTH=true)

View File

@@ -5,7 +5,28 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.0] - 2025-08-01
## [1.4.1] - 2025-08-01
### Added
- **Rate Limiting Protection**: Comprehensive rate limiting to prevent CLI command abuse
- Separate rate limiters for CLI operations (certificate generation, CA management) and API requests
- Configurable rate limits with environment variables (CLI: 10 ops/15min, API: 100 req/15min)
- Per-user and per-IP rate limiting for authenticated and anonymous users
- Protection against automated attacks and resource exhaustion
- **Rate Limiting Testing**: Comprehensive testing procedures and automated test script
- **Environment Configuration**: Added rate limiting configuration options to .env.example
### Security
- **Rate Limiting Protection**: Comprehensive protection against CLI command abuse and automated attacks
- **Resource Protection**: Prevents excessive CLI operations that could impact server performance
- **Multi-layer Security**: Combined IP-based and user-based rate limiting for enhanced protection
### Technical
- Added `express-rate-limit@^7.4.0` dependency for robust rate limiting functionality
- Enhanced server middleware with configurable rate limiting for different endpoint types
- Automated test script for validating rate limiting functionality
## [1.4.0]
### Added
- **OpenID Connect (OIDC) SSO Authentication**: Full OpenID Connect integration for single sign-on support
@@ -175,7 +196,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **v1.2.0**: Complete Docker containerization support
- **v1.3.0**: PFX generation, improved UI/UX, and enhanced certificate management
- **v1.4.0**: OpenID Connect SSO authentication and enhanced Root CA management
- **Current**: Full-featured mkcert Web UI with comprehensive certificate format support and enterprise SSO
- **v1.4.1**: Rate limiting protection and security enhancements
- **Current**: Full-featured mkcert Web UI with comprehensive certificate format support, enterprise SSO, and rate limiting protection
## Contributing

View File

@@ -8,6 +8,7 @@ A modern web interface for managing SSL certificates using the mkcert CLI tool.
- **📋 Multiple Formats**: Generate PEM, CRT, and PFX (PKCS#12) certificates on-demand
- **🔒 Flexible Authentication**: Basic authentication and enterprise SSO with OpenID Connect (OIDC)
- **🏢 Enterprise SSO**: OpenID Connect integration for Azure AD, Google, and other OIDC providers
- **🛡️ Rate Limiting**: Built-in protection against CLI command abuse and automated attacks
- **🌐 HTTPS Support**: Auto-generated SSL certificates for secure web interface
- **📋 Certificate Management**: View, download, archive, and restore certificates
- **🎨 Modern UI**: Dark/light themes with responsive design and mobile support
@@ -118,6 +119,12 @@ FORCE_HTTPS=true # Redirect HTTP to HTTPS
# Certificate Settings
CERTIFICATE_FORMAT=pem # Default format: 'pem' or 'crt'
# Rate Limiting Configuration
CLI_RATE_LIMIT_WINDOW=900000 # CLI operations window in ms (default: 15 minutes)
CLI_RATE_LIMIT_MAX=10 # Max CLI operations per window (default: 10)
API_RATE_LIMIT_WINDOW=900000 # API requests window in ms (default: 15 minutes)
API_RATE_LIMIT_MAX=100 # Max API requests per window (default: 100)
```
### Authentication Setup
@@ -347,6 +354,11 @@ mkcertWeb/
- **Development Focus**: Designed for local development environments
- **Flexible Authentication**: Basic authentication and enterprise SSO with OpenID Connect
- **Enterprise SSO**: Secure OIDC integration with proper token validation and session management
- **Rate Limiting Protection**: Built-in protection against CLI command abuse and automated attacks
- **CLI Operations**: Limited to 10 operations per 15-minute window (certificate generation, CA management)
- **API Requests**: Limited to 100 requests per 15-minute window (general API endpoints)
- **Per-User Limiting**: Rate limits applied per IP address and authenticated user
- **Configurable Limits**: All rate limits can be adjusted via environment variables
- **Regular User Execution**: Runs without root privileges (except for `mkcert -install`)
- **Read-Only Protection**: Root directory certificates cannot be deleted
- **Session Security**: HTTP-only cookies with CSRF protection and secure OIDC flows

View File

@@ -729,7 +729,71 @@ wget --load-cookies=/tmp/auth-cookies.txt \
# Should either reject or sanitize the input
```
### 3. File Access Security Testing
### 3. Rate Limiting Security Testing
```bash
# Test CLI rate limiting for certificate generation
echo "Testing CLI rate limiting..."
# Login first to get valid session
wget --post-data='{"username":"admin","password":"admin"}' \
--header='Content-Type: application/json' \
--save-cookies=/tmp/rate-limit-cookies.txt \
http://localhost:3000/api/auth/login \
-O /tmp/temp.json
# Test rapid certificate generation (should hit rate limit)
echo "Attempting rapid certificate generation to test rate limiting..."
for i in {1..12}; do
echo "Request $i:"
wget --load-cookies=/tmp/rate-limit-cookies.txt \
--post-data="{\"domains\":[\"test$i.local\"],\"format\":\"pem\"}" \
--header='Content-Type: application/json' \
http://localhost:3000/api/generate \
-O /tmp/rate-limit-test-$i.json 2>&1
if grep -q "429\|Too many" /tmp/rate-limit-test-$i.json; then
echo "✓ Rate limit triggered at request $i"
break
elif [ $i -gt 10 ]; then
echo "⚠ Rate limit may not be working (completed $i requests)"
fi
sleep 1
done
# Test API rate limiting for general endpoints
echo "Testing API rate limiting..."
for i in {1..25}; do
wget --load-cookies=/tmp/rate-limit-cookies.txt \
-qO- http://localhost:3000/api/certificates > /tmp/api-rate-$i.json 2>&1
if grep -q "429\|Too many" /tmp/api-rate-$i.json; then
echo "✓ API rate limit working (triggered at request $i)"
break
fi
if [ $((i % 10)) -eq 0 ]; then
echo "Completed $i API requests..."
fi
done
# Test rate limit headers
echo "Testing rate limit headers..."
wget --load-cookies=/tmp/rate-limit-cookies.txt \
--server-response \
http://localhost:3000/api/status \
-O /tmp/rate-headers.txt 2>&1
grep -E "X-RateLimit|RateLimit" /tmp/rate-headers.txt && echo "✓ Rate limit headers present"
# Test rate limiting with different IPs (if possible)
# Note: This is limited in single-machine testing
echo "✓ Rate limiting tests completed"
# Clean up rate limiting test files
rm -f /tmp/rate-limit-*.json /tmp/api-rate-*.json /tmp/rate-headers.txt
```
### 4. File Access Security Testing
```bash
# Try to access files outside certificate directory (should fail)
wget --load-cookies=/tmp/auth-cookies.txt \
@@ -745,7 +809,7 @@ wget http://localhost:3000/api/download/rootca \
grep -q "401" /tmp/unauth-download.txt && echo "✓ File download requires authentication"
```
### 4. Session Management Security Testing
### 5. Session Management Security Testing
```bash
# Test concurrent sessions
# Login from multiple "clients"

View File

@@ -1,6 +1,6 @@
{
"name": "mkcert-web-ui",
"version": "1.4.0",
"version": "1.4.1",
"description": "Web UI middleware for managing mkcert CLI and certificate files",
"main": "server.js",
"scripts": {
@@ -35,6 +35,7 @@
"cors": "^2.8.5",
"dotenv": "^16.6.1",
"express": "^4.19.2",
"express-rate-limit": "^7.4.0",
"express-session": "^1.17.3",
"fs-extra": "^11.2.0",
"passport": "^0.7.0",

View File

@@ -14,6 +14,7 @@ const session = require('express-session');
const bcrypt = require('bcryptjs');
const passport = require('passport');
const OpenIDConnectStrategy = require('passport-openidconnect');
const rateLimit = require('express-rate-limit');
const app = express();
const PORT = process.env.PORT || 3000;
@@ -36,6 +37,46 @@ const OIDC_CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET;
const OIDC_CALLBACK_URL = process.env.OIDC_CALLBACK_URL || `http://localhost:${PORT}/auth/oidc/callback`;
const OIDC_SCOPE = process.env.OIDC_SCOPE || 'openid profile email';
// Rate limiting configuration
const CLI_RATE_LIMIT_WINDOW = parseInt(process.env.CLI_RATE_LIMIT_WINDOW) || 15 * 60 * 1000; // 15 minutes
const CLI_RATE_LIMIT_MAX = parseInt(process.env.CLI_RATE_LIMIT_MAX) || 10; // 10 requests per window
const API_RATE_LIMIT_WINDOW = parseInt(process.env.API_RATE_LIMIT_WINDOW) || 15 * 60 * 1000; // 15 minutes
const API_RATE_LIMIT_MAX = parseInt(process.env.API_RATE_LIMIT_MAX) || 100; // 100 requests per window
// Create rate limiters
const cliRateLimiter = rateLimit({
windowMs: CLI_RATE_LIMIT_WINDOW,
max: CLI_RATE_LIMIT_MAX,
message: {
error: 'Too many CLI operations, please try again later.',
retryAfter: Math.ceil(CLI_RATE_LIMIT_WINDOW / 1000)
},
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => {
// Rate limit by IP address and user (if authenticated)
const ip = req.ip || req.connection.remoteAddress;
const user = req.user?.username || req.session?.username || 'anonymous';
return `cli:${ip}:${user}`;
}
});
const apiRateLimiter = rateLimit({
windowMs: API_RATE_LIMIT_WINDOW,
max: API_RATE_LIMIT_MAX,
message: {
error: 'Too many API requests, please try again later.',
retryAfter: Math.ceil(API_RATE_LIMIT_WINDOW / 1000)
},
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => {
const ip = req.ip || req.connection.remoteAddress;
const user = req.user?.username || req.session?.username || 'anonymous';
return `api:${ip}:${user}`;
}
});
// Middleware
app.use(cors());
app.use(bodyParser.json());
@@ -295,8 +336,14 @@ const executeCommand = (command) => {
// Routes
// Apply general API rate limiting to all API routes (except auth endpoints)
app.use('/api/certificates', apiRateLimiter);
app.use('/api/download', apiRateLimiter);
app.use('/api/rootca', apiRateLimiter);
app.use('/api/config', apiRateLimiter);
// Get mkcert status and CA info
app.get('/api/status', requireAuth, async (req, res) => {
app.get('/api/status', requireAuth, cliRateLimiter, async (req, res) => {
try {
const result = await executeCommand('mkcert -CAROOT');
const caRoot = result.stdout.trim();
@@ -360,7 +407,7 @@ app.get('/api/status', requireAuth, async (req, res) => {
});
// Install CA (mkcert -install)
app.post('/api/install-ca', requireAuth, async (req, res) => {
app.post('/api/install-ca', requireAuth, cliRateLimiter, async (req, res) => {
try {
const result = await executeCommand('mkcert -install');
res.json({
@@ -378,7 +425,7 @@ app.post('/api/install-ca', requireAuth, async (req, res) => {
});
// Generate new Root CA (mkcert -install creates a new CA if one doesn't exist)
app.post('/api/generate-ca', requireAuth, async (req, res) => {
app.post('/api/generate-ca', requireAuth, cliRateLimiter, async (req, res) => {
try {
// First check if mkcert is available
try {
@@ -598,7 +645,7 @@ app.get('/api/rootca/info', requireAuth, async (req, res) => {
});
// Generate certificate
app.post('/api/generate', requireAuth, async (req, res) => {
app.post('/api/generate', requireAuth, cliRateLimiter, async (req, res) => {
try {
const { domains, format = 'pem' } = req.body;
@@ -1019,7 +1066,7 @@ app.get('/api/download/bundle/:certname', requireAuth, (req, res) => {
});
// Generate PFX file from certificate and key
app.post('/api/generate/pfx/*', requireAuth, async (req, res) => {
app.post('/api/generate/pfx/*', requireAuth, cliRateLimiter, async (req, res) => {
try {
// Parse the wildcard path to extract folder and certname
const fullPath = req.params[0]; // Get the wildcard part