Security

HTTPS Setup and HSTS

HTTPS (HTTP Secure) is essential for protecting data in transit and ensuring the integrity and confidentiality of communications between clients and servers. This chapter covers implementing HTTPS in Django applications and configuring HTTP Strict Transport Security (HSTS) for enhanced security.

HTTPS Setup and HSTS

HTTPS (HTTP Secure) is essential for protecting data in transit and ensuring the integrity and confidentiality of communications between clients and servers. This chapter covers implementing HTTPS in Django applications and configuring HTTP Strict Transport Security (HSTS) for enhanced security.

Understanding HTTPS and TLS

Why HTTPS is Essential

# Without HTTPS, sensitive data is transmitted in plain text:

# HTTP Request (VISIBLE TO ANYONE ON THE NETWORK):
"""
POST /login/ HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded

username=admin&password=secretpassword123&csrfmiddlewaretoken=abc123
"""

# HTTPS Request (ENCRYPTED):
"""
POST /login/ HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
[ENCRYPTED DATA - UNREADABLE TO EAVESDROPPERS]
"""

# Security benefits of HTTPS:
# 1. Encryption - Data is encrypted in transit
# 2. Authentication - Verifies server identity
# 3. Integrity - Prevents data tampering
# 4. Trust - Required for modern web features

TLS/SSL Certificates

# Certificate types and their use cases:

# 1. Domain Validated (DV) Certificates
# - Validates domain ownership only
# - Suitable for most websites
# - Quick issuance (minutes to hours)

# 2. Organization Validated (OV) Certificates  
# - Validates organization identity
# - Shows organization name in certificate
# - More trust indicators

# 3. Extended Validation (EV) Certificates
# - Highest level of validation
# - Shows organization name in browser
# - Most expensive but highest trust

# 4. Wildcard Certificates
# - Covers all subdomains (*.example.com)
# - Useful for multiple subdomains
# - Single certificate management

# 5. Multi-Domain (SAN) Certificates
# - Covers multiple different domains
# - Cost-effective for multiple sites
# - Single certificate for multiple domains

Django HTTPS Configuration

Basic HTTPS Settings

# settings.py - Basic HTTPS configuration
import os

# Force HTTPS in production
SECURE_SSL_REDIRECT = True  # Redirect HTTP to HTTPS

# Secure cookie settings
SESSION_COOKIE_SECURE = True  # Send session cookies over HTTPS only
CSRF_COOKIE_SECURE = True    # Send CSRF cookies over HTTPS only

# Secure headers
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True

# HSTS (HTTP Strict Transport Security)
SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# Proxy configuration (if behind load balancer/proxy)
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Referrer policy
SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin'

Environment-Specific Configuration

# settings/base.py - Base security settings
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin'

# settings/development.py - Development settings
from .base import *

# Allow HTTP in development
SECURE_SSL_REDIRECT = False
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
SECURE_HSTS_SECONDS = 0  # Disable HSTS in development

# settings/production.py - Production settings
from .base import *

# Enforce HTTPS in production
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# HSTS configuration
SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# Proxy configuration for production
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Additional production security
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']

Custom HTTPS Middleware

# middleware.py - Custom HTTPS enforcement middleware
from django.http import HttpResponsePermanentRedirect
from django.conf import settings
from django.urls import reverse
import logging

logger = logging.getLogger('security')

class EnhancedHTTPSMiddleware:
    """Enhanced HTTPS enforcement with additional security features"""
    
    def __init__(self, get_response):
        self.get_response = get_response
        self.exempt_paths = getattr(settings, 'HTTPS_EXEMPT_PATHS', [])
        self.force_https_paths = getattr(settings, 'FORCE_HTTPS_PATHS', [])
    
    def __call__(self, request):
        # Check if HTTPS should be enforced
        if self.should_enforce_https(request):
            if not request.is_secure():
                return self.redirect_to_https(request)
        
        response = self.get_response(request)
        
        # Add security headers for HTTPS responses
        if request.is_secure():
            self.add_security_headers(request, response)
        
        return response
    
    def should_enforce_https(self, request):
        """Determine if HTTPS should be enforced for this request"""
        
        # Always enforce for sensitive paths
        if any(request.path.startswith(path) for path in self.force_https_paths):
            return True
        
        # Skip enforcement for exempt paths
        if any(request.path.startswith(path) for path in self.exempt_paths):
            return False
        
        # Check global setting
        return getattr(settings, 'SECURE_SSL_REDIRECT', False)
    
    def redirect_to_https(self, request):
        """Redirect HTTP request to HTTPS"""
        
        # Log HTTP access attempt
        logger.info(f"HTTP request redirected to HTTPS: {request.path}", extra={
            'ip_address': self.get_client_ip(request),
            'user_agent': request.META.get('HTTP_USER_AGENT', ''),
            'path': request.path,
        })
        
        # Build HTTPS URL
        https_url = f"https://{request.get_host()}{request.get_full_path()}"
        
        return HttpResponsePermanentRedirect(https_url)
    
    def add_security_headers(self, request, response):
        """Add security headers to HTTPS responses"""
        
        # HSTS header
        if getattr(settings, 'SECURE_HSTS_SECONDS', 0):
            hsts_header = f"max-age={settings.SECURE_HSTS_SECONDS}"
            
            if getattr(settings, 'SECURE_HSTS_INCLUDE_SUBDOMAINS', False):
                hsts_header += "; includeSubDomains"
            
            if getattr(settings, 'SECURE_HSTS_PRELOAD', False):
                hsts_header += "; preload"
            
            response['Strict-Transport-Security'] = hsts_header
        
        # Additional security headers
        response['X-Content-Type-Options'] = 'nosniff'
        response['X-Frame-Options'] = 'DENY'
        response['X-XSS-Protection'] = '1; mode=block'
        
        # Referrer policy
        if hasattr(settings, 'SECURE_REFERRER_POLICY'):
            response['Referrer-Policy'] = settings.SECURE_REFERRER_POLICY
    
    def get_client_ip(self, request):
        """Get client IP address"""
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            ip = x_forwarded_for.split(',')[0]
        else:
            ip = request.META.get('REMOTE_ADDR')
        return ip

# settings.py - Configuration for custom middleware
MIDDLEWARE = [
    'myapp.middleware.EnhancedHTTPSMiddleware',
    # ... other middleware
]

# Custom HTTPS settings
HTTPS_EXEMPT_PATHS = [
    '/health/',  # Health check endpoints
    '/status/',  # Status endpoints
]

FORCE_HTTPS_PATHS = [
    '/login/',
    '/admin/',
    '/api/auth/',
    '/payment/',
]

SSL/TLS Certificate Management

Let's Encrypt Integration

# Certificate management with Let's Encrypt
# Using certbot for automatic certificate management

# Install certbot
# pip install certbot certbot-nginx  # or certbot-apache

# Automatic certificate generation and renewal
"""
# Generate certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Test renewal
sudo certbot renew --dry-run

# Automatic renewal (add to crontab)
0 12 * * * /usr/bin/certbot renew --quiet
"""

# Django settings for Let's Encrypt
# settings.py
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']

# ACME challenge handling (for certificate validation)
ACME_CHALLENGE_PATH = '/.well-known/acme-challenge/'

# URL configuration for ACME challenges
# urls.py
from django.urls import path
from django.views.static import serve
from django.conf import settings
import os

urlpatterns = [
    # ... your URL patterns
]

# Serve ACME challenge files
if not settings.DEBUG:
    urlpatterns += [
        path('.well-known/acme-challenge/<path:path>', 
             serve, 
             {'document_root': '/var/www/html/.well-known/acme-challenge/'}),
    ]

Certificate Monitoring

# Certificate monitoring and alerting
import ssl
import socket
from datetime import datetime, timedelta
from django.core.management.base import BaseCommand
from django.core.mail import send_mail

class Command(BaseCommand):
    """Monitor SSL certificate expiration"""
    
    help = 'Check SSL certificate expiration and send alerts'
    
    def add_arguments(self, parser):
        parser.add_argument('--domain', type=str, help='Domain to check')
        parser.add_argument('--port', type=int, default=443, help='Port to check')
        parser.add_argument('--days', type=int, default=30, help='Days before expiration to alert')
    
    def handle(self, *args, **options):
        domain = options['domain'] or 'yourdomain.com'
        port = options['port']
        alert_days = options['days']
        
        try:
            # Get certificate information
            cert_info = self.get_certificate_info(domain, port)
            
            # Check expiration
            expires_in_days = self.check_expiration(cert_info)
            
            if expires_in_days <= alert_days:
                self.send_expiration_alert(domain, expires_in_days, cert_info)
                self.stdout.write(
                    self.style.WARNING(
                        f'Certificate for {domain} expires in {expires_in_days} days'
                    )
                )
            else:
                self.stdout.write(
                    self.style.SUCCESS(
                        f'Certificate for {domain} is valid for {expires_in_days} days'
                    )
                )
        
        except Exception as e:
            self.stdout.write(
                self.style.ERROR(f'Error checking certificate for {domain}: {e}')
            )
    
    def get_certificate_info(self, domain, port):
        """Get SSL certificate information"""
        
        context = ssl.create_default_context()
        
        with socket.create_connection((domain, port), timeout=10) as sock:
            with context.wrap_socket(sock, server_hostname=domain) as ssock:
                cert = ssock.getpeercert()
        
        return cert
    
    def check_expiration(self, cert_info):
        """Check certificate expiration"""
        
        # Parse expiration date
        not_after = cert_info['notAfter']
        expiry_date = datetime.strptime(not_after, '%b %d %H:%M:%S %Y %Z')
        
        # Calculate days until expiration
        days_until_expiry = (expiry_date - datetime.now()).days
        
        return days_until_expiry
    
    def send_expiration_alert(self, domain, days_remaining, cert_info):
        """Send certificate expiration alert"""
        
        subject = f'SSL Certificate Expiring Soon: {domain}'
        
        message = f"""
        The SSL certificate for {domain} will expire in {days_remaining} days.
        
        Certificate Details:
        - Subject: {cert_info.get('subject', 'Unknown')}
        - Issuer: {cert_info.get('issuer', 'Unknown')}
        - Expires: {cert_info.get('notAfter', 'Unknown')}
        
        Please renew the certificate before it expires to avoid service disruption.
        """
        
        send_mail(
            subject,
            message,
            'ssl-monitor@yourdomain.com',
            ['admin@yourdomain.com'],
            fail_silently=False,
        )

# Run certificate monitoring
# python manage.py check_ssl_certificate --domain yourdomain.com --days 30

HTTP Strict Transport Security (HSTS)

HSTS Configuration

# settings.py - HSTS configuration
# HSTS tells browsers to only connect via HTTPS

# Basic HSTS configuration
SECURE_HSTS_SECONDS = 31536000  # 1 year (recommended minimum)

# Include subdomains in HSTS policy
SECURE_HSTS_INCLUDE_SUBDOMAINS = True

# Enable HSTS preloading (submit to browser preload lists)
SECURE_HSTS_PRELOAD = True

# Progressive HSTS deployment
# Start with shorter duration, then increase
if DEBUG:
    SECURE_HSTS_SECONDS = 0  # Disable in development
else:
    # Production HSTS progression:
    # Week 1: 300 (5 minutes) - for testing
    # Week 2: 86400 (1 day) - short term
    # Week 3: 604800 (1 week) - medium term
    # Week 4+: 31536000 (1 year) - long term
    SECURE_HSTS_SECONDS = 31536000

Custom HSTS Implementation

# middleware.py - Custom HSTS middleware with advanced features
class AdvancedHSTSMiddleware:
    """Advanced HSTS middleware with conditional policies"""
    
    def __init__(self, get_response):
        self.get_response = get_response
        
        # HSTS configuration
        self.hsts_config = {
            'default': {
                'max_age': 31536000,  # 1 year
                'include_subdomains': True,
                'preload': True
            },
            'api': {
                'max_age': 63072000,  # 2 years (stricter for API)
                'include_subdomains': True,
                'preload': True
            },
            'admin': {
                'max_age': 63072000,  # 2 years (stricter for admin)
                'include_subdomains': True,
                'preload': True
            }
        }
    
    def __call__(self, request):
        response = self.get_response(request)
        
        # Only add HSTS header for HTTPS requests
        if request.is_secure():
            hsts_header = self.build_hsts_header(request)
            if hsts_header:
                response['Strict-Transport-Security'] = hsts_header
        
        return response
    
    def build_hsts_header(self, request):
        """Build HSTS header based on request path"""
        
        # Determine HSTS policy based on path
        if request.path.startswith('/api/'):
            config = self.hsts_config['api']
        elif request.path.startswith('/admin/'):
            config = self.hsts_config['admin']
        else:
            config = self.hsts_config['default']
        
        # Build header
        header_parts = [f"max-age={config['max_age']}"]
        
        if config.get('include_subdomains'):
            header_parts.append('includeSubDomains')
        
        if config.get('preload'):
            header_parts.append('preload')
        
        return '; '.join(header_parts)

# HSTS preload list submission
"""
To submit your domain to HSTS preload lists:

1. Ensure HSTS is properly configured with preload directive
2. Visit https://hstspreload.org/
3. Submit your domain
4. Wait for inclusion in browser preload lists

Requirements for preload:
- Serve a valid certificate
- Redirect all HTTP traffic to HTTPS
- Serve HSTS header with:
  - max-age of at least 31536000 (1 year)
  - includeSubDomains directive
  - preload directive
"""

HSTS Monitoring and Reporting

# HSTS monitoring and compliance checking
import requests
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    """Check HSTS compliance and configuration"""
    
    help = 'Check HSTS header configuration and compliance'
    
    def add_arguments(self, parser):
        parser.add_argument('--url', type=str, help='URL to check')
    
    def handle(self, *args, **options):
        url = options['url'] or 'https://yourdomain.com'
        
        try:
            # Make HTTPS request
            response = requests.get(url, timeout=10)
            
            # Check HSTS header
            hsts_header = response.headers.get('Strict-Transport-Security')
            
            if hsts_header:
                self.analyze_hsts_header(hsts_header)
            else:
                self.stdout.write(
                    self.style.ERROR('HSTS header not found')
                )
            
            # Check other security headers
            self.check_security_headers(response.headers)
            
        except Exception as e:
            self.stdout.write(
                self.style.ERROR(f'Error checking HSTS: {e}')
            )
    
    def analyze_hsts_header(self, hsts_header):
        """Analyze HSTS header configuration"""
        
        self.stdout.write(f'HSTS Header: {hsts_header}')
        
        # Parse header components
        components = [comp.strip() for comp in hsts_header.split(';')]
        
        max_age = None
        include_subdomains = False
        preload = False
        
        for component in components:
            if component.startswith('max-age='):
                max_age = int(component.split('=')[1])
            elif component == 'includeSubDomains':
                include_subdomains = True
            elif component == 'preload':
                preload = True
        
        # Analyze configuration
        if max_age:
            if max_age >= 31536000:  # 1 year
                self.stdout.write(
                    self.style.SUCCESS(f'✓ Max-age is sufficient: {max_age} seconds')
                )
            else:
                self.stdout.write(
                    self.style.WARNING(f'⚠ Max-age is low: {max_age} seconds (recommend 31536000+)')
                )
        
        if include_subdomains:
            self.stdout.write(
                self.style.SUCCESS('✓ includeSubDomains directive present')
            )
        else:
            self.stdout.write(
                self.style.WARNING('⚠ includeSubDomains directive missing')
            )
        
        if preload:
            self.stdout.write(
                self.style.SUCCESS('✓ preload directive present')
            )
        else:
            self.stdout.write(
                self.style.WARNING('⚠ preload directive missing')
            )
    
    def check_security_headers(self, headers):
        """Check other security headers"""
        
        security_headers = {
            'X-Content-Type-Options': 'nosniff',
            'X-Frame-Options': ['DENY', 'SAMEORIGIN'],
            'X-XSS-Protection': '1; mode=block',
            'Referrer-Policy': 'strict-origin-when-cross-origin'
        }
        
        for header, expected in security_headers.items():
            value = headers.get(header)
            
            if value:
                if isinstance(expected, list):
                    if value in expected:
                        self.stdout.write(
                            self.style.SUCCESS(f'✓ {header}: {value}')
                        )
                    else:
                        self.stdout.write(
                            self.style.WARNING(f'⚠ {header}: {value} (expected one of {expected})')
                        )
                else:
                    if value == expected:
                        self.stdout.write(
                            self.style.SUCCESS(f'✓ {header}: {value}')
                        )
                    else:
                        self.stdout.write(
                            self.style.WARNING(f'⚠ {header}: {value} (expected {expected})')
                        )
            else:
                self.stdout.write(
                    self.style.ERROR(f'✗ {header}: Missing')
                )

Load Balancer and Proxy Configuration

Nginx Configuration

# nginx.conf - HTTPS configuration with Django
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    
    # Redirect all HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;
    
    # SSL certificate configuration
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    
    # SSL security configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # HSTS header (let Django handle this, or configure here)
    # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    
    # Security headers
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options DENY always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    
    # Pass real IP to Django
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    
    # Django application
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_redirect off;
    }
    
    # Static files (optional - serve directly from nginx)
    location /static/ {
        alias /path/to/static/files/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Media files (optional - serve directly from nginx)
    location /media/ {
        alias /path/to/media/files/;
        expires 1y;
        add_header Cache-Control "public";
    }
}

Apache Configuration

# apache.conf - HTTPS configuration with Django
<VirtualHost *:80>
    ServerName yourdomain.com
    ServerAlias www.yourdomain.com
    
    # Redirect all HTTP to HTTPS
    Redirect permanent / https://yourdomain.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName yourdomain.com
    ServerAlias www.yourdomain.com
    
    # SSL configuration
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/yourdomain.com/chain.pem
    
    # SSL security settings
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
    SSLHonorCipherOrder off
    SSLSessionTickets off
    
    # Security headers
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set X-Content-Type-Options nosniff
    Header always set X-Frame-Options DENY
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    
    # Pass headers to Django
    SetEnvIf X-Forwarded-Proto https HTTPS=on
    
    # Django application (using mod_wsgi)
    WSGIDaemonProcess yourdomain python-path=/path/to/your/project
    WSGIProcessGroup yourdomain
    WSGIScriptAlias / /path/to/your/project/wsgi.py
    
    <Directory /path/to/your/project>
        WSGIApplicationGroup %{GLOBAL}
        Require all granted
    </Directory>
    
    # Static files
    Alias /static /path/to/static/files
    <Directory /path/to/static/files>
        Require all granted
    </Directory>
    
    # Media files
    Alias /media /path/to/media/files
    <Directory /path/to/media/files>
        Require all granted
    </Directory>
</VirtualHost>

Testing HTTPS Configuration

Security Tests

# tests.py - HTTPS configuration tests
from django.test import TestCase, Client, override_settings
from django.urls import reverse

class HTTPSConfigurationTests(TestCase):
    """Test HTTPS configuration and security headers"""
    
    def setUp(self):
        self.client = Client()
    
    @override_settings(SECURE_SSL_REDIRECT=True)
    def test_http_redirect_to_https(self):
        """Test that HTTP requests are redirected to HTTPS"""
        
        response = self.client.get('/', secure=False)
        
        self.assertEqual(response.status_code, 301)
        self.assertTrue(response.url.startswith('https://'))
    
    @override_settings(
        SECURE_HSTS_SECONDS=31536000,
        SECURE_HSTS_INCLUDE_SUBDOMAINS=True,
        SECURE_HSTS_PRELOAD=True
    )
    def test_hsts_header(self):
        """Test HSTS header configuration"""
        
        response = self.client.get('/', secure=True)
        
        hsts_header = response.get('Strict-Transport-Security')
        self.assertIsNotNone(hsts_header)
        self.assertIn('max-age=31536000', hsts_header)
        self.assertIn('includeSubDomains', hsts_header)
        self.assertIn('preload', hsts_header)
    
    @override_settings(
        SESSION_COOKIE_SECURE=True,
        CSRF_COOKIE_SECURE=True
    )
    def test_secure_cookies(self):
        """Test that cookies are marked as secure"""
        
        # Login to create session
        from django.contrib.auth.models import User
        user = User.objects.create_user('testuser', 'test@example.com', 'testpass')
        
        response = self.client.post(reverse('login'), {
            'username': 'testuser',
            'password': 'testpass'
        }, secure=True)
        
        # Check session cookie
        session_cookie = response.cookies.get('sessionid')
        if session_cookie:
            self.assertTrue(session_cookie['secure'])
        
        # Check CSRF cookie
        csrf_cookie = response.cookies.get('csrftoken')
        if csrf_cookie:
            self.assertTrue(csrf_cookie['secure'])
    
    def test_security_headers(self):
        """Test security headers are present"""
        
        response = self.client.get('/', secure=True)
        
        # Check for security headers
        self.assertEqual(response.get('X-Content-Type-Options'), 'nosniff')
        self.assertEqual(response.get('X-Frame-Options'), 'DENY')
        self.assertEqual(response.get('X-XSS-Protection'), '1; mode=block')
        
        referrer_policy = response.get('Referrer-Policy')
        self.assertIsNotNone(referrer_policy)

class SSLCertificateTests(TestCase):
    """Test SSL certificate validation"""
    
    def test_certificate_validity(self):
        """Test SSL certificate is valid and properly configured"""
        
        import ssl
        import socket
        from datetime import datetime
        
        try:
            # Test certificate for your domain
            context = ssl.create_default_context()
            
            with socket.create_connection(('yourdomain.com', 443), timeout=10) as sock:
                with context.wrap_socket(sock, server_hostname='yourdomain.com') as ssock:
                    cert = ssock.getpeercert()
            
            # Check certificate expiration
            not_after = cert['notAfter']
            expiry_date = datetime.strptime(not_after, '%b %d %H:%M:%S %Y %Z')
            days_until_expiry = (expiry_date - datetime.now()).days
            
            # Certificate should not expire within 30 days
            self.assertGreater(days_until_expiry, 30, 
                             f"Certificate expires in {days_until_expiry} days")
            
            # Check subject matches domain
            subject = dict(x[0] for x in cert['subject'])
            self.assertIn('yourdomain.com', subject.get('commonName', ''))
            
        except Exception as e:
            self.skipTest(f"Could not test certificate: {e}")

Best Practices Summary

HTTPS Implementation

  • Always use HTTPS in production environments
  • Implement proper certificate management and monitoring
  • Configure secure cipher suites and protocols
  • Use HTTP to HTTPS redirects for all traffic

HSTS Configuration

  • Start with shorter HSTS max-age values and gradually increase
  • Include subdomains in HSTS policy when appropriate
  • Consider HSTS preloading for maximum security
  • Monitor HSTS compliance and configuration

Certificate Management

  • Use automated certificate renewal (Let's Encrypt)
  • Monitor certificate expiration dates
  • Implement proper certificate validation
  • Use strong key sizes (2048-bit RSA minimum, prefer ECDSA)

Security Headers

  • Implement comprehensive security headers
  • Use Content Security Policy for additional protection
  • Configure proper referrer policies
  • Regular security header audits and updates

Next Steps

Now that you understand HTTPS setup and HSTS, let's explore password storage and cryptography to ensure sensitive data is properly protected in Django applications.