mirror of
https://github.com/jeffcaldwellca/mkcertWeb.git
synced 2025-12-30 09:19:52 -06:00
add rate limiting protection
This commit is contained in:
2
.env
2
.env
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -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
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -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
|
||||
|
||||
68
TESTING.md
68
TESTING.md
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
57
server.js
57
server.js
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user