430 lines
14 KiB
Markdown
430 lines
14 KiB
Markdown
# Authentication Service with Active Directory Integration
|
|
|
|
This authentication service provides JWT-based authentication with Active Directory integration and Traefik ForwardAuth support for Kubernetes environments.
|
|
|
|
## Features
|
|
|
|
- 🔐 **Active Directory Authentication**: Validates credentials against your AD server
|
|
- 🎫 **JWT Tokens**: Secure token-based authentication with configurable expiration
|
|
- 🍪 **Cookie & Local Storage**: Tokens stored securely in HTTP-only cookies and locally
|
|
- 🚀 **Traefik Integration**: ForwardAuth middleware for seamless Kubernetes access control
|
|
- 📱 **Responsive UI**: Clean, modern login interface
|
|
- 🔒 **Security Headers**: Proper CORS, security headers, and token validation
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
│ User Browser │───▶│ Auth Service │───▶│ Active Directory│
|
|
│ │ │ │ │ │
|
|
│ 1. Login Form │ │ 2. Validate AD │ │ 3. LDAP Auth │
|
|
│ 4. Store Token │◀───│ Create JWT │ │ │
|
|
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
│ │
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────┐ ┌──────────────────┐
|
|
│ Protected API │◀───│ Traefik Forward │
|
|
│ │ │ Auth Middleware │
|
|
│ 5. Access with │ │ 6. Validate JWT │
|
|
│ JWT Token │ │ │
|
|
└─────────────────┘ └──────────────────┘
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
### 1. Build and Deploy
|
|
|
|
```bash
|
|
# Build the authentication service
|
|
cd auth-service
|
|
docker build -t your-registry/auth-service:1.0.0 .
|
|
docker push your-registry/auth-service:1.0.0
|
|
|
|
# Update values in values-example.yaml
|
|
cp values-example.yaml values.yaml
|
|
# Edit values.yaml with your AD configuration
|
|
|
|
# Deploy to Kubernetes
|
|
kubectl apply -f kubernetes-auth.yaml
|
|
```
|
|
|
|
### 2. Configure Active Directory
|
|
|
|
Update the `values.yaml` file with your AD configuration:
|
|
|
|
```yaml
|
|
authService:
|
|
activeDirectory:
|
|
server: "ldap://your-ad-server.yourdomain.com"
|
|
baseDN: "DC=yourdomain,DC=com"
|
|
userSearchBase: "CN=Users,DC=yourdomain,DC=com"
|
|
bindUser: "CN=ServiceAccount,CN=Users,DC=yourdomain,DC=com"
|
|
bindPassword: "your-service-account-password"
|
|
```
|
|
|
|
### 3. Configure Traefik ForwardAuth
|
|
|
|
The service automatically creates a ForwardAuth middleware that:
|
|
|
|
```yaml
|
|
apiVersion: traefik.io/v1alpha1
|
|
kind: Middleware
|
|
metadata:
|
|
name: auth-forward
|
|
spec:
|
|
forwardAuth:
|
|
address: http://auth-service:8080/auth/verify
|
|
authResponseHeaders:
|
|
- "X-Auth-User"
|
|
- "X-Auth-Email"
|
|
- "X-Auth-Groups"
|
|
- "X-Auth-Display-Name"
|
|
```
|
|
|
|
### 4. Protect Your Services
|
|
|
|
Add the ForwardAuth middleware to any IngressRoute:
|
|
|
|
```yaml
|
|
apiVersion: traefik.io/v1alpha1
|
|
kind: IngressRoute
|
|
metadata:
|
|
name: protected-service
|
|
spec:
|
|
routes:
|
|
- match: Host(`api.yourdomain.com`)
|
|
kind: Rule
|
|
services:
|
|
- name: your-api-service
|
|
port: 8000
|
|
middlewares:
|
|
- name: auth-forward # This protects the entire service
|
|
```
|
|
|
|
## How It Works
|
|
|
|
### Authentication Flow
|
|
|
|
1. **User visits protected resource** → Traefik ForwardAuth redirects to login
|
|
2. **User enters AD credentials** → Service validates against Active Directory
|
|
3. **JWT token created** → Stored in HTTP-only cookie + localStorage
|
|
4. **Subsequent requests** → Traefik validates JWT via ForwardAuth
|
|
5. **Access granted** → User headers passed to backend service
|
|
|
|
### Token Storage
|
|
|
|
The system uses a dual-storage approach:
|
|
|
|
- **HTTP-only Cookie**: Secure, automatic transmission, protected from XSS
|
|
- **localStorage**: Available to JavaScript for SPA applications
|
|
|
|
### Security Features
|
|
|
|
- ✅ **LDAP over TLS** support for secure AD communication
|
|
- ✅ **JWT token expiration** with configurable timeouts
|
|
- ✅ **HTTP-only cookies** prevent XSS token theft
|
|
- ✅ **Secure headers** for production deployment
|
|
- ✅ **CORS protection** with configurable origins
|
|
|
|
## API Endpoints
|
|
|
|
### Authentication Endpoints
|
|
|
|
| Endpoint | Method | Description |
|
|
|----------|--------|-------------|
|
|
| `/` | GET | Login page (HTML) |
|
|
| `/dashboard` | GET | Dashboard page (HTML) |
|
|
| `/auth/login` | POST | Authenticate user |
|
|
| `/auth/verify` | POST | Verify JWT token (ForwardAuth) |
|
|
| `/auth/logout` | GET | Logout user |
|
|
| `/auth/user` | GET | Get current user info |
|
|
| `/health` | GET | Health check |
|
|
|
|
### ForwardAuth Integration
|
|
|
|
When Traefik calls `/auth/verify`, the service:
|
|
|
|
1. **Checks for token** in Authorization header or cookies
|
|
2. **Validates JWT** signature and expiration
|
|
3. **Returns user headers** for backend services:
|
|
- `X-Auth-User`: Username
|
|
- `X-Auth-Email`: User email
|
|
- `X-Auth-Groups`: AD group memberships
|
|
- `X-Auth-Display-Name`: User's display name
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Description | Default |
|
|
|----------|-------------|---------|
|
|
| `JWT_SECRET` | Secret key for JWT signing | (required) |
|
|
| `TOKEN_EXPIRE_HOURS` | Token expiration in hours | 8 |
|
|
| `AD_SERVER` | LDAP server URL | (required) |
|
|
| `AD_BASE_DN` | Base DN for AD | (required) |
|
|
| `AD_USER_SEARCH_BASE` | User search base | (required) |
|
|
| `AD_BIND_USER` | Service account for LDAP | (optional) |
|
|
| `AD_BIND_PASSWORD` | Service account password | (optional) |
|
|
|
|
### Kubernetes Secrets
|
|
|
|
Create the required secrets:
|
|
|
|
```bash
|
|
kubectl create secret generic auth-secrets \
|
|
--from-literal=jwt-secret="your-super-secret-key" \
|
|
--from-literal=ad-bind-user="CN=ServiceAccount,CN=Users,DC=yourdomain,DC=com" \
|
|
--from-literal=ad-bind-password="your-service-password"
|
|
```
|
|
|
|
## Advanced Usage
|
|
|
|
### Custom Group-Based Access
|
|
|
|
The service passes AD group memberships in the `X-Auth-Groups` header. You can use this in your backend services:
|
|
|
|
```python
|
|
# In your FastAPI backend
|
|
from fastapi import Header
|
|
|
|
def check_admin_access(x_auth_groups: str = Header(None)):
|
|
groups = x_auth_groups.split(',') if x_auth_groups else []
|
|
if 'CN=Admins,CN=Groups,DC=yourdomain,DC=com' not in groups:
|
|
raise HTTPException(status_code=403, detail="Admin access required")
|
|
```
|
|
|
|
### Multiple Protection Levels
|
|
|
|
You can create different ForwardAuth middlewares for different access levels:
|
|
|
|
```yaml
|
|
# Admin-only middleware
|
|
apiVersion: traefik.io/v1alpha1
|
|
kind: Middleware
|
|
metadata:
|
|
name: admin-auth
|
|
spec:
|
|
forwardAuth:
|
|
address: http://auth-service:8080/auth/verify-admin
|
|
authResponseHeaders:
|
|
- "X-Auth-User"
|
|
- "X-Auth-Groups"
|
|
```
|
|
|
|
### Token Refresh
|
|
|
|
The service automatically handles token refresh. Configure shorter expiration times and implement refresh logic in your frontend:
|
|
|
|
```javascript
|
|
// Check token expiration
|
|
const token = localStorage.getItem('auth_token');
|
|
const payload = JSON.parse(atob(token.split('.')[1]));
|
|
const expiry = new Date(payload.exp * 1000);
|
|
|
|
if (expiry < new Date()) {
|
|
// Redirect to login for refresh
|
|
window.location.href = '/auth/login';
|
|
}
|
|
```
|
|
|
|
## Monitoring
|
|
|
|
### Health Checks
|
|
|
|
The service includes health check endpoints:
|
|
|
|
```bash
|
|
curl http://auth-service:8080/health
|
|
```
|
|
|
|
### Logging
|
|
|
|
The service logs authentication attempts and failures:
|
|
|
|
```
|
|
INFO: Successfully authenticated user: john.doe
|
|
ERROR: Authentication failed for user: invalid.user
|
|
ERROR: LDAP connection failed: timeout
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
1. **Use HTTPS**: Always deploy with TLS certificates
|
|
2. **Secure JWT Secret**: Use a strong, unique JWT secret
|
|
3. **Network Security**: Restrict access to AD servers
|
|
4. **Token Expiration**: Use reasonable token expiration times
|
|
5. **Service Account**: Use a dedicated AD service account with minimal permissions
|
|
6. **Audit Logs**: Monitor authentication logs for suspicious activity
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
1. **LDAP Connection Failed**
|
|
- Check AD server connectivity
|
|
- Verify LDAP URL format
|
|
- Check firewall rules
|
|
|
|
2. **Authentication Failed**
|
|
- Verify AD credentials
|
|
- Check user search base DN
|
|
- Confirm user exists in specified OU
|
|
|
|
3. **ForwardAuth Not Working**
|
|
- Verify Traefik middleware configuration
|
|
- Check service connectivity
|
|
- Review Traefik logs
|
|
|
|
4. **Token Issues**
|
|
- Verify JWT secret consistency
|
|
- Check token expiration
|
|
- Validate cookie settings
|
|
|
|
### Debug Mode
|
|
|
|
Enable debug logging by setting log level:
|
|
|
|
```yaml
|
|
env:
|
|
- name: LOG_LEVEL
|
|
value: "DEBUG"
|
|
```
|
|
|
|
This will provide detailed authentication flow logs.
|
|
|
|
# 🔧 Drone Configuration Factorization Options
|
|
|
|
## 1. Jsonnet Configuration (Recommended)
|
|
Drone supports Jsonnet for dynamic configuration generation, which allows you to include files and create reusable components.
|
|
|
|
## 2. YAML Anchors & References (Limited)
|
|
YAML supports anchors and references for basic factorization:
|
|
|
|
## 3. External Configuration Files (Starlark)
|
|
Drone also supports Starlark (Python-like) for advanced configuration:
|
|
|
|
|
|
🎯 Recommended: Convert to Jsonnet
|
|
Let me create a script to help you convert your current .drone.yml to the modular Jsonnet approach:
|
|
|
|
convert-to-jsonnet.sh
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
# Convert existing .drone.yml to modular Jsonnet structure
|
|
# This script helps migrate from YAML to factorized Jsonnet configuration
|
|
|
|
echo "🔄 Converting Drone configuration to modular Jsonnet..."
|
|
|
|
# Generate the final configuration from Jsonnet
|
|
echo "📝 Generating .drone.yml from Jsonnet..."
|
|
if command -v jsonnet >/dev/null 2>&1; then
|
|
jsonnet .drone.jsonnet > .drone.yml.generated
|
|
echo "✅ Generated .drone.yml.generated from Jsonnet"
|
|
echo ""
|
|
echo "📋 To use the new configuration:"
|
|
echo "1. Review: cat .drone.yml.generated"
|
|
echo "2. Test: python3 -c \"import yaml; yaml.safe_load(open('.drone.yml.generated'))\""
|
|
echo "3. Replace: mv .drone.yml.generated .drone.yml"
|
|
echo "4. Commit: git add .drone.jsonnet common.libsonnet build-steps.libsonnet .drone.yml"
|
|
else
|
|
echo "⚠️ jsonnet not installed. Installing..."
|
|
|
|
# Try to install jsonnet
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
sudo apt-get update && sudo apt-get install -y jsonnet
|
|
elif command -v apk >/dev/null 2>&1; then
|
|
apk add --no-cache jsonnet
|
|
elif command -v yum >/dev/null 2>&1; then
|
|
sudo yum install -y jsonnet
|
|
else
|
|
echo "❌ Cannot install jsonnet automatically"
|
|
echo "📖 Please install jsonnet manually:"
|
|
echo " - Ubuntu/Debian: sudo apt-get install jsonnet"
|
|
echo " - Alpine: apk add jsonnet"
|
|
echo " - CentOS/RHEL: sudo yum install jsonnet"
|
|
echo " - Or download from: https://github.com/google/jsonnet"
|
|
exit 1
|
|
fi
|
|
|
|
# Try generating again
|
|
if command -v jsonnet >/dev/null 2>&1; then
|
|
jsonnet .drone.jsonnet > .drone.yml.generated
|
|
echo "✅ Generated .drone.yml.generated from Jsonnet"
|
|
else
|
|
echo "❌ Failed to install jsonnet"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo "🎯 Benefits of Jsonnet configuration:"
|
|
echo " ✅ Reusable components (common.libsonnet, build-steps.libsonnet)"
|
|
echo " ✅ Variables and functions"
|
|
echo " ✅ Conditional logic"
|
|
echo " ✅ Better maintainability"
|
|
echo " ✅ DRY principle"
|
|
echo ""
|
|
echo "📚 Files created:"
|
|
echo " - .drone.jsonnet (main configuration)"
|
|
echo " - common.libsonnet (shared steps and config)"
|
|
echo " - build-steps.libsonnet (build-specific logic)"
|
|
echo " - .drone.yml.generated (generated YAML)"
|
|
|
|
# Validate the generated YAML
|
|
if [ -f ".drone.yml.generated" ]; then
|
|
echo ""
|
|
echo "🔍 Validating generated YAML..."
|
|
if python3 -c "import yaml; yaml.safe_load(open('.drone.yml.generated'))" 2>/dev/null; then
|
|
echo "✅ Generated YAML is valid"
|
|
else
|
|
echo "❌ Generated YAML has syntax errors"
|
|
exit 1
|
|
fi
|
|
fi
|
|
```
|
|
|
|
# 📋 Summary of Factorization Options
|
|
|
|
| Method | Pros | Cons | Best For |
|
|
| ------ | ---- | ---- | -------- |
|
|
| Jsonnet | ✅ Full programming language<br>✅ Variables, functions, imports<br>✅ Conditional logic | ❌ Learning curve<br>❌ Requires jsonnet tool | Complex configurations |
|
|
| YAML Anchors | ✅ Native YAML<br>✅ Simple syntax<br>✅ No extra tools | ❌ Limited functionality<br>❌ No conditionals | Simple repetition |
|
|
| Starlark | ✅ Python-like syntax<br>✅ Advanced logic<br>✅ Built into Drone | ❌ Drone-specific<br>❌ Less common | Advanced pipelines |
|
|
|
|
# 🎯 Recommendation for Your Project
|
|
For your external Buildah build system, I recommend Jsonnet because:
|
|
|
|
- **Modularity**: Separate build logic from common steps
|
|
- **Reusability**: Common kubectl setup, environment config
|
|
- **Maintainability**: Easier to update build steps independently
|
|
- **Flexibility**: Can create variants (dev, staging, prod) easily
|
|
|
|
## 🚀 Quick Start with Jsonnet
|
|
|
|
```console
|
|
# Install jsonnet (if needed)
|
|
sudo apt-get install jsonnet
|
|
|
|
# Convert to modular structure
|
|
./convert-to-jsonnet.sh
|
|
|
|
# Review generated configuration
|
|
cat .drone.yml.generated
|
|
|
|
# Test and deploy
|
|
mv .drone.yml.generated .drone.yml
|
|
git add .drone.jsonnet common.libsonnet build-steps.libsonnet .drone.yml
|
|
git commit -m "Convert to modular Jsonnet configuration"
|
|
git push
|
|
```
|
|
|
|
The modular approach will make it much easier to:
|
|
|
|
- 🔧 Update build steps without touching common logic
|
|
- 🎯 Create environment-specific configurations
|
|
- 🧪 Test individual components
|
|
- 📦 Share configuration across projects
|