Exemplos de Gerenciamento de Certificados SSL

Exemplos de gerenciamento de certificados SSL incluindo automação Let's Encrypt, renovação, monitoramento e melhores práticas de segurança

💻 Automação de Certificados SSL com Let's Encrypt python

🟡 intermediate ⭐⭐⭐⭐

Geração e renovação automatizadas de certificados SSL usando o protocolo ACME do Let's Encrypt

⏱️ 45 min 🏷️ ssl, letsencrypt, certificate, automation, security
Prerequisites: Python, Let's Encrypt ACME protocol, SSL/TLS concepts, Web server configuration
#!/usr/bin/env python3
"""
SSL Certificate Automation with Let's Encrypt
Automated certificate generation, renewal, and management using ACME protocol
"""

import os
import sys
import json
import time
import logging
import hashlib
import base64
import secrets
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
import subprocess
import shutil
from pathlib import Path

# Third-party imports
import requests
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
import josepy as jose
from acme import client, messages
from acme.messages import Error

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class CertificateManager:
    """SSL Certificate Manager using Let's Encrypt ACME"""

    def __init__(self, config: Dict):
        self.config = config
        self.work_dir = Path(config.get('work_dir', '/etc/ssl/cert-manager'))
        self.cert_dir = self.work_dir / 'certificates'
        self.key_dir = self.work_dir / 'private'
        self.acme_dir = self.work_dir / 'acme'

        # Create directories
        for directory in [self.cert_dir, self.key_dir, self.acme_dir]:
            directory.mkdir(parents=True, exist_ok=True)

        # ACME directory URL
        self.acme_url = config.get('acme_url', 'https://acme-v02.api.letsencrypt.org/directory')
        if config.get('staging', False):
            self.acme_url = 'https://acme-staging-v02.api.letsencrypt.org/directory'

        # Account information
        self.account_key_file = self.acme_dir / 'account.key'
        self.account_file = self.acme_dir / 'account.json'

        # Email for registration
        self.email = config.get('email', '[email protected]')

        # Initialize ACME client
        self._initialize_client()

    def _initialize_client(self):
        """Initialize ACME client"""
        try:
            # Load or generate account key
            if self.account_key_file.exists():
                logger.info("Loading existing account key")
                with open(self.account_key_file, 'rb') as f:
                    key = serialization.load_pem_private_key(
                        f.read(),
                        password=None
                    )
            else:
                logger.info("Generating new account key")
                key = rsa.generate_private_key(
                    public_exponent=65537,
                    key_size=2048
                )
                with open(self.account_key_file, 'wb') as f:
                    f.write(key.private_bytes(
                        encoding=Encoding.PEM,
                        format=PrivateFormat.PKCS8,
                        encryption_algorithm=NoEncryption()
                    ))
                os.chmod(self.account_key_file, 0o600)

            # Create ACME client
            net = client.ClientNetwork(key, user_agent='cert-manager/1.0')
            directory = client.ClientV2.get_directory(self.acme_url, net)
            self.acme = client.ClientV2(directory, net=key)

            # Register or load account
            if self.account_file.exists():
                logger.info("Loading existing account")
                with open(self.account_file, 'r') as f:
                    account_data = json.load(f)
                    self.account = messages.RegistrationResource(
                        uri=account_data['uri'],
                        body=messages.Registration.from_json(account_data['body'])
                    )
            else:
                logger.info("Registering new account")
                self.account = self._register_account()

        except Exception as e:
            logger.error(f"Failed to initialize ACME client: {e}")
            raise

    def _register_account(self) -> messages.RegistrationResource:
        """Register new ACME account"""
        try:
            # New registration
            new_reg = messages.NewRegistration.from_data(
                email=self.email,
                terms_of_service_agreed=True
            )

            # Register account
            regr = self.acme.new_account(new_reg)

            # Save account information
            account_data = {
                'uri': regr.uri,
                'body': regr.body.to_json()
            }
            with open(self.account_file, 'w') as f:
                json.dump(account_data, f, indent=2)
            os.chmod(self.account_file, 0o644)

            logger.info(f"Successfully registered account: {regr.uri}")
            return regr

        except Exception as e:
            logger.error(f"Failed to register account: {e}")
            raise

    def generate_certificate(self, domains: List[str], rsa_key_size: int = 2048) -> Dict:
        """
        Generate SSL certificate for specified domains

        Args:
            domains: List of domains to include in certificate
            rsa_key_size: RSA key size (default 2048)

        Returns:
            Dictionary with certificate information
        """
        try:
            logger.info(f"Generating certificate for domains: {domains}")

            # Validate domains
            for domain in domains:
                if not self._validate_domain(domain):
                    raise ValueError(f"Invalid domain: {domain}")

            # Generate private key
            private_key = rsa.generate_private_key(
                public_exponent=65537,
                key_size=rsa_key_size
            )

            # Create CSR
            builder = x509.CertificateSigningRequestBuilder()

            # Add subject information
            name_attrs = [
                x509.NameAttribute(NameOID.COMMON_NAME, domains[0]),
                x509.NameAttribute(NameOID.ORGANIZATION_NAME, self.config.get('organization', 'My Organization')),
                x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, self.config.get('organizational_unit', 'IT Department')),
            ]

            builder = builder.subject_name(x509.Name(name_attrs))

            # Add SAN (Subject Alternative Names)
            san_names = []
            for domain in domains:
                san_names.append(x509.DNSName(domain))

            builder = builder.add_extension(
                x509.SubjectAlternativeName(san_names),
                critical=False
            )

            # Create CSR
            csr = builder.sign(private_key, hashes.SHA256())
            csr_pem = csr.public_bytes(serialization.Encoding.PEM).decode('utf-8')

            # Submit certificate request
            order = self._submit_order(csr_pem)

            # Complete challenges
            certificate_pem = self._complete_challenges(order)

            # Save certificate and key
            cert_path, key_path = self._save_certificate(
                domains[0], certificate_pem, private_key, domains
            )

            return {
                'domains': domains,
                'certificate_path': str(cert_path),
                'key_path': str(key_path),
                'issued_at': datetime.utcnow().isoformat(),
                'expires_at': self._get_certificate_expiration(certificate_pem).isoformat()
            }

        except Exception as e:
            logger.error(f"Failed to generate certificate: {e}")
            raise

    def _validate_domain(self, domain: str) -> bool:
        """Validate domain name format"""
        import re
        pattern = r'^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'
        return bool(re.match(pattern, domain))

    def _submit_order(self, csr_pem: str) -> messages.OrderResource:
        """Submit certificate order to ACME server"""
        try:
            # Create new order
            identifiers = []
            csr = x509.load_pem_x509_csr(csr_pem.encode('utf-8'))

            # Extract domains from CSR
            for ext in csr.extensions:
                if ext.oid == x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
                    san = ext.value
                    for name in san:
                        if isinstance(name, x509.DNSName):
                            identifiers.append(
                                messages.Identifier(typ=messages.IDENTIFIER_DNS, value=name.value)
                            )

            # Add CN if not in SAN
            cn = csr.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
            if cn not in [identifier.value for identifier in identifiers]:
                identifiers.append(
                    messages.Identifier(typ=messages.IDENTIFIER_DNS, value=cn)
                )

            order = self.acme.new_order(messages.NewOrder(identifiers=identifiers))

            logger.info(f"Created order: {order.uri}")
            return order

        except Exception as e:
            logger.error(f"Failed to submit order: {e}")
            raise

    def _complete_challenges(self, order: messages.OrderResource) -> str:
        """Complete ACME challenges for certificate issuance"""
        try:
            authorizations = []
            for authz_url in order.authorizations:
                authorization = self.acme.get_authorization(authz_url)
                authorizations.append(authorization)

            # Complete challenges
            for authz in authorizations:
                for combo in authz.body.challenges:
                    if isinstance(combo.chall, messages.HTTP01Challenge):
                        # Handle HTTP-01 challenge
                        self._handle_http01_challenge(combo)
                    elif isinstance(combo.chall, messages.DNS01Challenge):
                        # Handle DNS-01 challenge
                        self._handle_dns01_challenge(combo)

            # Wait for authorization
            time.sleep(2)

            # Finalize order
            logger.info("Finalizing certificate order")
            finalized_order = self.acme.finalize_order(order, deadline=60)

            # Download certificate
            certificate_pem = finalized_order.fullchain_pem
            logger.info("Certificate issued successfully")

            return certificate_pem

        except Exception as e:
            logger.error(f"Failed to complete challenges: {e}")
            raise

    def _handle_http01_challenge(self, challenge: messages.ChallengeResource):
        """Handle HTTP-01 challenge"""
        try:
            # Calculate token and key authorization
            token = challenge.chall.token
            key_authorization = challenge.response_and_validation(self.acme.net.key)[1]

            # Create challenge file
            webroot = self.config.get('webroot', '/var/www/html')
            challenge_dir = Path(webroot) / '.well-known' / 'acme-challenge'
            challenge_dir.mkdir(parents=True, exist_ok=True)

            challenge_file = challenge_dir / token
            challenge_file.write_text(key_authorization)

            logger.info(f"Created HTTP challenge file: {challenge_file}")

            # Notify ACME server
            self.acme.answer_challenge(challenge, challenge.response(self.acme.net.key))

            # Clean up challenge file after completion
            # (In production, you might want to keep it for a while)

        except Exception as e:
            logger.error(f"Failed to handle HTTP-01 challenge: {e}")
            raise

    def _handle_dns01_challenge(self, challenge: messages.ChallengeResource):
        """Handle DNS-01 challenge"""
        try:
            # Calculate token and key authorization
            token = challenge.chall.token
            key_authorization = challenge.response_and_validation(self.acme.net.key)[1]

            # Calculate TXT record value
            txt_record_value = hashlib.sha256(
                key_authorization.encode('utf-8')
            ).digest()
            txt_record_value = base64.urlsafe_b64encode(txt_record_value).decode('utf-8').rstrip('=')

            # DNS record name
            domain = challenge.chall.domain
            txt_record_name = f'_acme-challenge.{domain}'

            logger.info(f"DNS TXT record: {txt_record_name} = {txt_record_value}")

            # In a real implementation, you would update DNS records here
            # This is just a placeholder for demonstration
            self._update_dns_record(txt_record_name, 'TXT', txt_record_value)

            # Notify ACME server
            self.acme.answer_challenge(challenge, challenge.response(self.acme.net.key))

            # Wait for DNS propagation
            time.sleep(30)

        except Exception as e:
            logger.error(f"Failed to handle DNS-01 challenge: {e}")
            raise

    def _update_dns_record(self, name: str, record_type: str, value: str):
        """Update DNS record (placeholder implementation)"""
        logger.info(f"Would update DNS record: {name} {record_type} {value}")
        # In a real implementation, use your DNS provider's API
        # For example, AWS Route53, Cloudflare DNS, etc.
        pass

    def _save_certificate(self, domain: str, cert_pem: str, private_key, domains: List[str]) -> Tuple[Path, Path]:
        """Save certificate and private key"""
        try:
            timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
            cert_filename = f"{domain}_{timestamp}.crt"
            key_filename = f"{domain}_{timestamp}.key"

            cert_path = self.cert_dir / cert_filename
            key_path = self.key_dir / key_filename

            # Save certificate
            with open(cert_path, 'w') as f:
                f.write(cert_pem)
            os.chmod(cert_path, 0o644)

            # Save private key
            with open(key_path, 'wb') as f:
                f.write(private_key.private_bytes(
                    encoding=Encoding.PEM,
                    format=PrivateFormat.PKCS8,
                    encryption_algorithm=NoEncryption()
                ))
            os.chmod(key_path, 0o600)

            # Create symlink to latest certificate
            latest_cert = self.cert_dir / f"{domain}.latest.crt"
            latest_key = self.key_dir / f"{domain}.latest.key"

            if latest_cert.exists():
                latest_cert.unlink()
            if latest_key.exists():
                latest_key.unlink()

            latest_cert.symlink_to(cert_path)
            latest_key.symlink_to(key_path)

            logger.info(f"Saved certificate: {cert_path}")
            logger.info(f"Saved private key: {key_path}")

            # Save metadata
            metadata = {
                'domain': domain,
                'domains': domains,
                'certificate_path': str(cert_path),
                'key_path': str(key_path),
                'created_at': datetime.utcnow().isoformat(),
                'expires_at': self._get_certificate_expiration(cert_pem).isoformat()
            }

            metadata_file = self.cert_dir / f"{domain}_{timestamp}.json"
            with open(metadata_file, 'w') as f:
                json.dump(metadata, f, indent=2)

            return cert_path, key_path

        except Exception as e:
            logger.error(f"Failed to save certificate: {e}")
            raise

    def _get_certificate_expiration(self, cert_pem: str) -> datetime:
        """Get certificate expiration date"""
        try:
            cert = x509.load_pem_x509_certificate(cert_pem.encode('utf-8'))
            return cert.not_valid_after.replace(tzinfo=None)
        except Exception as e:
            logger.error(f"Failed to get certificate expiration: {e}")
            return datetime.utcnow() + timedelta(days=90)

    def renew_certificate(self, domain: str, force_renewal: bool = False) -> bool:
        """Renew SSL certificate"""
        try:
            # Find existing certificate
            cert_info = self._find_certificate_info(domain)
            if not cert_info:
                logger.warning(f"No existing certificate found for {domain}")
                return False

            expiration = datetime.fromisoformat(cert_info['expires_at'])
            days_until_expiration = (expiration - datetime.utcnow()).days

            # Check if renewal is needed
            if not force_renewal and days_until_expiration > 30:
                logger.info(f"Certificate for {domain} is still valid for {days_until_expiration} days")
                return False

            logger.info(f"Renewing certificate for {domain}")

            # Get domains from existing certificate
            domains = cert_info.get('domains', [domain])

            # Generate new certificate
            new_cert_info = self.generate_certificate(domains)

            # Update web server configuration if needed
            self._update_webserver_config(domain, new_cert_info)

            logger.info(f"Successfully renewed certificate for {domain}")
            return True

        except Exception as e:
            logger.error(f"Failed to renew certificate for {domain}: {e}")
            return False

    def _find_certificate_info(self, domain: str) -> Optional[Dict]:
        """Find certificate information for domain"""
        try:
            # Look for latest certificate metadata
            for metadata_file in self.cert_dir.glob(f"{domain}_*.json"):
                with open(metadata_file, 'r') as f:
                    metadata = json.load(f)
                    return metadata

            # Try to parse certificate file directly
            latest_cert = self.cert_dir / f"{domain}.latest.crt"
            if latest_cert.exists():
                with open(latest_cert, 'r') as f:
                    cert_pem = f.read()
                    expiration = self._get_certificate_expiration(cert_pem)
                    return {
                        'domain': domain,
                        'certificate_path': str(latest_cert),
                        'expires_at': expiration.isoformat()
                    }

            return None

        except Exception as e:
            logger.error(f"Failed to find certificate info for {domain}: {e}")
            return None

    def _update_webserver_config(self, domain: str, cert_info: Dict):
        """Update web server configuration"""
        try:
            # Update Nginx configuration
            nginx_config_path = f"/etc/nginx/sites-available/{domain}"
            if os.path.exists(nginx_config_path):
                self._update_nginx_config(nginx_config_path, cert_info)

            # Update Apache configuration
            apache_config_path = f"/etc/apache2/sites-available/{domain}.conf"
            if os.path.exists(apache_config_path):
                self._update_apache_config(apache_config_path, cert_info)

            # Reload web servers
            if os.path.exists('/etc/init.d/nginx'):
                subprocess.run(['systemctl', 'reload', 'nginx'], check=True)
            if os.path.exists('/etc/init.d/apache2'):
                subprocess.run(['systemctl', 'reload', 'apache2'], check=True)

        except Exception as e:
            logger.error(f"Failed to update web server config: {e}")

    def _update_nginx_config(self, config_path: str, cert_info: Dict):
        """Update Nginx SSL configuration"""
        try:
            with open(config_path, 'r') as f:
                config_content = f.read()

            # Update SSL certificate paths
            config_content = config_content.replace(
                'ssl_certificate .*;',
                f"ssl_certificate {cert_info['certificate_path']};"
            )
            config_content = config_content.replace(
                'ssl_certificate_key .*;',
                f"ssl_certificate_key {cert_info['key_path']};"
            )

            with open(config_path, 'w') as f:
                f.write(config_content)

            logger.info(f"Updated Nginx config: {config_path}")

        except Exception as e:
            logger.error(f"Failed to update Nginx config: {e}")

    def _update_apache_config(self, config_path: str, cert_info: Dict):
        """Update Apache SSL configuration"""
        try:
            with open(config_path, 'r') as f:
                config_content = f.read()

            # Update SSL certificate paths
            config_content = config_content.replace(
                'SSLCertificateFile .*',
                f"SSLCertificateFile {cert_info['certificate_path']}"
            )
            config_content = config_content.replace(
                'SSLCertificateKeyFile .*',
                f"SSLCertificateKeyFile {cert_info['key_path']}"
            )

            with open(config_path, 'w') as f:
                f.write(config_content)

            logger.info(f"Updated Apache config: {config_path}")

        except Exception as e:
            logger.error(f"Failed to update Apache config: {e}")

    def list_certificates(self) -> List[Dict]:
        """List all managed certificates"""
        try:
            certificates = []

            for metadata_file in self.cert_dir.glob("*.json"):
                with open(metadata_file, 'r') as f:
                    metadata = json.load(f)
                    certificates.append(metadata)

            # Sort by expiration date
            certificates.sort(key=lambda x: x.get('expires_at', ''))

            return certificates

        except Exception as e:
            logger.error(f"Failed to list certificates: {e}")
            return []

    def check_certificate_status(self, domain: str) -> Dict:
        """Check certificate status and expiration"""
        try:
            cert_info = self._find_certificate_info(domain)
            if not cert_info:
                return {
                    'domain': domain,
                    'status': 'not_found',
                    'message': 'No certificate found for this domain'
                }

            expiration = datetime.fromisoformat(cert_info['expires_at'])
            days_until_expiration = (expiration - datetime.utcnow()).days

            if days_until_expiration < 0:
                status = 'expired'
            elif days_until_expiration < 7:
                status = 'critical'
            elif days_until_expiration < 30:
                status = 'warning'
            else:
                status = 'valid'

            return {
                'domain': domain,
                'status': status,
                'expires_at': cert_info['expires_at'],
                'days_until_expiration': days_until_expiration,
                'certificate_path': cert_info.get('certificate_path'),
                'key_path': cert_info.get('key_path')
            }

        except Exception as e:
            logger.error(f"Failed to check certificate status for {domain}: {e}")
            return {
                'domain': domain,
                'status': 'error',
                'message': str(e)
            }

    def revoke_certificate(self, domain: str) -> bool:
        """Revoke SSL certificate"""
        try:
            cert_info = self._find_certificate_info(domain)
            if not cert_info:
                logger.error(f"No certificate found for {domain}")
                return False

            # Load certificate
            cert_path = cert_info['certificate_path']
            with open(cert_path, 'rb') as f:
                cert = x509.load_pem_x509_certificate(f.read())

            # Create revocation request
            revocation = messages.Revocation(certificate=cert)

            # Submit revocation
            self.acme.revoke(revocation)

            logger.info(f"Successfully revoked certificate for {domain}")

            # Remove certificate files
            os.remove(cert_path)
            key_path = cert_info.get('key_path')
            if key_path and os.path.exists(key_path):
                os.remove(key_path)

            return True

        except Exception as e:
            logger.error(f"Failed to revoke certificate for {domain}: {e}")
            return False

# Example usage and CLI interface
if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description='SSL Certificate Manager')
    parser.add_argument('command', choices=[
        'generate', 'renew', 'list', 'status', 'revoke'
    ])
    parser.add_argument('--domain', help='Domain name')
    parser.add_argument('--domains', nargs='+', help='Multiple domain names')
    parser.add_argument('--config', default='config.json', help='Configuration file')
    parser.add_argument('--force', action='store_true', help='Force action')

    args = parser.parse_args()

    # Load configuration
    try:
        with open(args.config, 'r') as f:
            config = json.load(f)
    except FileNotFoundError:
        print(f"Configuration file {args.config} not found")
        sys.exit(1)

    # Initialize certificate manager
    manager = CertificateManager(config)

    try:
        if args.command == 'generate':
            domains = args.domains or [args.domain]
            if not domains:
                print("Domain name is required")
                sys.exit(1)

            result = manager.generate_certificate(domains)
            print(f"Certificate generated successfully:")
            print(f"  Domains: {result['domains']}")
            print(f"  Certificate: {result['certificate_path']}")
            print(f"  Private Key: {result['key_path']}")
            print(f"  Expires: {result['expires_at']}")

        elif args.command == 'renew':
            domain = args.domain
            if not domain:
                print("Domain name is required")
                sys.exit(1)

            success = manager.renew_certificate(domain, args.force)
            if success:
                print(f"Certificate for {domain} renewed successfully")
            else:
                print(f"Certificate for {domain} renewal failed or not needed")

        elif args.command == 'list':
            certificates = manager.list_certificates()
            if certificates:
                print(f"{'Domain':<30} {'Expires':<20} {'Status':<10}")
                print("-" * 60)
                for cert in certificates:
                    domain = cert['domain']
                    expires = cert['expires_at'][:10]
                    status = manager.check_certificate_status(domain)['status']
                    print(f"{domain:<30} {expires:<20} {status:<10}")
            else:
                print("No certificates found")

        elif args.command == 'status':
            domain = args.domain
            if not domain:
                print("Domain name is required")
                sys.exit(1)

            status = manager.check_certificate_status(domain)
            print(f"Domain: {status['domain']}")
            print(f"Status: {status['status']}")
            print(f"Expires: {status.get('expires_at', 'N/A')}")
            if 'days_until_expiration' in status:
                print(f"Days until expiration: {status['days_until_expiration']}")

        elif args.command == 'revoke':
            domain = args.domain
            if not domain:
                print("Domain name is required")
                sys.exit(1)

            success = manager.revoke_certificate(domain)
            if success:
                print(f"Certificate for {domain} revoked successfully")
            else:
                print(f"Failed to revoke certificate for {domain}")

    except KeyboardInterrupt:
        print("\nOperation cancelled")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        sys.exit(1)

# Configuration example (config.json)
{
    "email": "[email protected]",
    "work_dir": "/etc/ssl/cert-manager",
    "webroot": "/var/www/html",
    "organization": "My Company",
    "organizational_unit": "IT Department",
    "staging": false,
    "dns_provider": {
        "type": "cloudflare",
        "api_token": "your_api_token_here"
    }
}

# Crontab example for automatic renewal
# 0 2 * * * /usr/bin/python3 /path/to/cert_manager.py renew --config /etc/ssl/cert-manager/config.json >> /var/log/cert-manager.log 2>&1

export { CertificateManager };

💻 Configuração SSL/TLS do Nginx nginx

🟡 intermediate ⭐⭐⭐⭐

Configuração completa SSL/TLS do Nginx com melhores práticas de segurança, HSTS e gerenciamento de certificados

⏱️ 35 min 🏷️ nginx, ssl, tls, security, configuration
Prerequisites: Nginx configuration, SSL/TLS concepts, Web server administration
# Complete Nginx SSL/TLS Configuration
# Production-ready configuration with security best practices

# Global SSL Configuration
# Add this to /etc/nginx/nginx.conf in the http block

# SSL Settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

# Modern cipher suite (2024 recommendations)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# SSL session cache for better performance
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;

# DNS resolvers for OCSP stapling
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# SSL buffer sizes
ssl_buffer_size 8k;
ssl_ecdh_curve X25519:P-256:P-384:P-521;

# Example SSL Site Configuration
# /etc/nginx/sites-available/example.com

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Redirect all HTTP traffic to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/example.com/fullchain.pem;
    ssl_certificate_key /etc/ssl/private/example.com/privkey.pem;

    # SSL security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # Content Security Policy (adjust according to your needs)
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; frame-ancestors 'none';" always;

    # Root directory
    root /var/www/example.com;
    index index.html index.php;

    # Log files
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;

    # Enable Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        application/atom+xml
        application/javascript
        application/json
        application/rss+xml
        application/vnd.ms-fontobject
        application/x-font-ttf
        application/x-web-app-manifest+json
        application/xhtml+xml
        application/xml
        font/opentype
        image/svg+xml
        image/x-icon
        text/css
        text/plain
        text/x-component;

    # Security headers for static files
    location ~* \.(css|js|ico|gif|jpe?g|png|svg|eot|otf|ttf|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header X-Content-Type-Options nosniff always;
    }

    # Block access to sensitive files
    location ~ /. {
        deny all;
        access_log off;
        log_not_found off;
    }

    location ~ ~$ {
        deny all;
        access_log off;
        log_not_found off;
    }

    # PHP processing (if using PHP)
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param HTTPS on;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Default location
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Health check endpoint
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

# Multi-domain SSL Configuration
# Handle multiple domains with different certificates

map $http_host $certificate_name {
    default                     "";
    example.com                  "example.com";
    www.example.com              "example.com";
    api.example.com              "api.example.com";
    blog.example.com             "blog.example.com";
    shop.example.com             "shop.example.com";
}

server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;

    # Dynamic certificate selection
    ssl_certificate /etc/ssl/certs/$certificate_name/fullchain.pem;
    ssl_certificate_key /etc/ssl/private/$certificate_name/privkey.pem;

    # Fallback certificate for unknown domains
    ssl_certificate /etc/ssl/certs/default.crt;
    ssl_certificate_key /etc/ssl/private/default.key;

    # Rest of the configuration...
}

# Load Balancing with SSL
# SSL termination at load balancer

upstream backend_servers {
    least_conn;
    server 192.168.1.10:80 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:80 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:80 max_fails=3 fail_timeout=30s;
}

server {
    listen 443 ssl http2;
    server_name loadbalancer.example.com;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/loadbalancer.crt;
    ssl_certificate_key /etc/ssl/private/loadbalancer.key;

    # Proxy headers
    proxy_set_header Host $host;
    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 X-Forwarded-SSL-Protocol $ssl_protocol;
    proxy_set_header X-Forwarded-SSL-Cipher $ssl_cipher;
    proxy_set_header X-Forwarded-SSL-Session-ID $ssl_session_id;

    location / {
        proxy_pass http://backend_servers;
        proxy_ssl_verify off;
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

# Rate Limiting Configuration
# Prevent brute force attacks and DDoS

# Rate limit zone
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s;

# Connection limit zone
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

server {
    listen 443 ssl http2;
    server_name example.com;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # Apply rate limiting to specific locations
    location /login {
        limit_req zone=login_limit burst=3 nodelay;
        limit_req_status 429;

        # Your login handling configuration
        proxy_pass http://backend;
    }

    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        limit_req_status 429;

        # API configuration
        proxy_pass http://api_backend;
    }

    location / {
        limit_conn conn_limit 20;
        limit_req zone=general_limit burst=50 nodelay;
        limit_req_status 429;

        # General content configuration
        try_files $uri $uri/ =404;
    }

    # Custom error page for rate limiting
    error_page 429 @rate_limit_exceeded;
    location @rate_limit_exceeded {
        return 429 "Rate limit exceeded. Please try again later.";
        add_header Content-Type text/plain;
    }
}

# WebSocket with SSL
# For real-time applications

upstream websocket_backend {
    server 127.0.0.1:8080;
    # Add more servers for load balancing
}

server {
    listen 443 ssl http2;
    server_name websocket.example.com;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/websocket.example.com.crt;
    ssl_certificate_key /etc/ssl/private/websocket.example.com.key;

    location /ws/ {
        proxy_pass http://websocket_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        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_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

# SSL Certificate Monitoring
# Configuration for certificate monitoring endpoints

server {
    listen 443 ssl http2;
    server_name monitoring.example.com;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/monitoring.example.com.crt;
    ssl_certificate_key /etc/ssl/private/monitoring.example.com.key;

    # Certificate status endpoint
    location /cert-status {
        access_log off;
        allow 127.0.0.1;
        allow 192.168.1.0/24;
        deny all;

        # This would typically be handled by a backend script
        # that checks certificate expiration and returns JSON
        return 200 '{"certificates": [{"domain": "example.com", "expires": "2024-12-31", "days_left": 45}]}';
        add_header Content-Type application/json;
    }

    # SSL configuration endpoint
    location /ssl-config {
        access_log off;
        allow 127.0.0.1;
        deny all;

        return 200 '{"ssl_version": "TLSv1.3", "cipher": "TLS_AES_256_GCM_SHA384", "protocols": ["TLSv1.2", "TLSv1.3"]}';
        add_header Content-Type application/json;
    }
}

# HTTP/2 Push Configuration
# Optimize resource loading with HTTP/2

server {
    listen 443 ssl http2;
    server_name optimized.example.com;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/optimized.example.com.crt;
    ssl_certificate_key /etc/ssl/private/optimized.example.com.key;

    root /var/www/optimized.example.com;

    location = / {
        http2_push /css/style.css;
        http2_push /js/main.js;
        http2_push /fonts/main.woff2;
        http2_push /images/logo.svg;

        try_files $uri $uri/ /index.html;
    }

    # Cache static resources
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Vary Accept-Encoding;
    }
}

# Automatic Certificate Reloading
# Script to reload Nginx when certificates are updated

# Add this to /etc/nginx/conf.d/ssl-reload.conf

# This requires a separate process to monitor certificate changes
# Here's a basic example of what such a script might do:

#!/bin/bash
# /usr/local/bin/ssl-reload.sh

CERT_DIR="/etc/ssl/certs"
LOG_FILE="/var/log/nginx-ssl-reload.log"

function log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}

function reload_nginx() {
    log_message "Certificate changes detected, reloading Nginx"

    # Test configuration first
    if nginx -t; then
        systemctl reload nginx
        log_message "Nginx reloaded successfully"
    else
        log_message "ERROR: Nginx configuration test failed"
    fi
}

# Monitor certificate directory for changes
inotifywait -m -r -e modify,create,delete --format '%w%f %e' "$CERT_DIR" | while read file event; do
    if [[ "$file" == *".crt" ]] || [[ "$file" == *".pem" ]]; then
        # Small delay to ensure certificate is fully written
        sleep 2
        reload_nginx
    fi
done

# Performance Optimization
# SSL session resumption and connection pooling

server {
    listen 443 ssl http2;
    server_name performance.example.com;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/performance.example.com.crt;
    ssl_certificate_key /etc/ssl/private/performance.example.com.key;

    # Enable session tickets with key rotation
    ssl_session_tickets on;
    ssl_session_ticket_key /etc/nginx/ssl/ticket.key;
    ssl_session_ticket_key /etc/nginx/ssl/ticket2.key;

    # Keep-alive connections
    keepalive_timeout 70;
    keepalive_requests 1000;

    # TCP optimization
    tcp_nodelay on;
    tcp_nopush on;

    # Worker connections optimization
    # This should be set in nginx.conf events block:
    # worker_connections 4096;
    # use epoll;
    # multi_accept on;

    location / {
        # Your application configuration
    }
}

# Security Headers Configuration
# Comprehensive security headers for modern web applications

server {
    listen 443 ssl http2;
    server_name secure.example.com;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/secure.example.com.crt;
    ssl_certificate_key /etc/ssl/private/secure.example.com.key;

    # Comprehensive security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()" always;

    # Report-To and NEL for security reporting
    add_header Report-To '{"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://csp-report.example.com/"}]}' always;
    add_header NEL '{"report_to":"csp-endpoint","max_age":10886400,"failure_fraction":0.1,"success_fraction":0.1}' always;

    # Content Security Policy
    add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content; script-src 'self' 'nonce-{random}' 'strict-dynamic'; style-src 'self' 'nonce-{random}'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; media-src 'self'; object-src 'none'; child-src 'none'; worker-src 'self'; manifest-src 'self';" always;

    location / {
        try_files $uri $uri/ =404;
    }
}

# Legacy Browser Support
# Configuration for older browsers that don't support modern SSL

server {
    listen 443 ssl;
    server_name legacy.example.com;

    # Include older SSL protocols for legacy support (not recommended for most sites)
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    # Legacy-compatible cipher suite
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/legacy.example.com.crt;
    ssl_certificate_key /etc/ssl/private/legacy.example.com.key;

    location / {
        # User agent detection to serve different content
        if ($http_user_agent ~* "MSIE [6-8]") {
            return 302 https://modern.example.com$request_uri;
        }

        # Content for legacy browsers
        try_files $uri $uri/ =404;
    }
}

💻 Dashboard de Monitoramento de Certificados SSL javascript

🔴 complex ⭐⭐⭐⭐⭐

Dashboard de monitoramento em tempo real para certificados SSL com alertas de expiração e análise de segurança

⏱️ 55 min 🏷️ monitoring, ssl, dashboard, javascript, websockets
Prerequisites: Node.js, WebSocket, SSL/TLS, Web development
/**
 * SSL Certificate Monitoring Dashboard
 * Real-time certificate monitoring with expiration alerts and security analysis
 */

const express = require('express');
const WebSocket = require('ws');
const https = require('https');
const tls = require('tls');
const fs = require('fs');
const path = require('path');
const cron = require('node-cron');
const axios = require('axios');
const { exec } = require('child_process');
const { promisify } = require('util');

const execAsync = promisify(exec);

class CertificateMonitor {
    constructor(config = {}) {
        this.config = {
            checkInterval: config.checkInterval || '0 */6 * * *', // Every 6 hours
            alertThreshold: config.alertThreshold || 30, // Days before expiration
            port: config.port || 3000,
            sslPort: config.sslPort || 3443,
            ...config
        };

        this.certificates = new Map();
        this.alerts = [];
        this.clients = new Set();
        this.history = [];
        this.initializeData();
        this.setupScheduledTasks();
    }

    async initializeData() {
        // Load saved data if exists
        try {
            const dataPath = path.join(__dirname, 'data', 'certificates.json');
            if (fs.existsSync(dataPath)) {
                const data = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
                this.certificates = new Map(data.certificates || []);
                this.history = data.history || [];
                console.log(`Loaded ${this.certificates.size} certificates from storage`);
            }
        } catch (error) {
            console.error('Failed to load saved data:', error);
        }
    }

    saveData() {
        try {
            const dataDir = path.join(__dirname, 'data');
            if (!fs.existsSync(dataDir)) {
                fs.mkdirSync(dataDir, { recursive: true });
            }

            const data = {
                certificates: Array.from(this.certificates.entries()),
                history: this.history.slice(-1000), // Keep last 1000 entries
                lastUpdated: new Date().toISOString()
            };

            fs.writeFileSync(
                path.join(dataDir, 'certificates.json'),
                JSON.stringify(data, null, 2)
            );
        } catch (error) {
            console.error('Failed to save data:', error);
        }
    }

    setupScheduledTasks() {
        // Schedule certificate checks
        cron.schedule(this.config.checkInterval, async () => {
            await this.checkAllCertificates();
        });

        // Schedule daily backup
        cron.schedule('0 2 * * *', async () => {
            await this.backupData();
        });

        console.log('Scheduled tasks configured');
    }

    async checkAllCertificates() {
        console.log('Starting certificate check cycle...');
        const startTime = Date.now();

        try {
            for (const [domain, certInfo] of this.certificates) {
                await this.checkCertificate(domain, certInfo);
            }

            const duration = Date.now() - startTime;
            console.log(`Certificate check completed in ${duration}ms`);

            // Broadcast updates to WebSocket clients
            this.broadcastUpdate({
                type: 'checkCompleted',
                timestamp: new Date().toISOString(),
                duration,
                certificates: Array.from(this.certificates.entries()).map(([domain, info]) => ({
                    domain,
                    ...info
                }))
            });

            this.saveData();

        } catch (error) {
            console.error('Certificate check failed:', error);
            this.broadcastUpdate({
                type: 'error',
                message: 'Certificate check failed',
                error: error.message,
                timestamp: new Date().toISOString()
            });
        }
    }

    async checkCertificate(domain, certInfo = {}) {
        try {
            const startTime = Date.now();
            const options = {
                host: domain,
                port: certInfo.port || 443,
                servername: domain,
                rejectUnauthorized: false // We handle validation ourselves
            };

            return new Promise((resolve, reject) => {
                const socket = tls.connect(443, domain, options, () => {
                    try {
                        const cert = socket.getPeerCertificate(true);
                        const checkDuration = Date.now() - startTime;

                        const certData = this.parseCertificate(cert, domain);
                        certData.checkDuration = checkDuration;
                        certData.lastChecked = new Date().toISOString();
                        certData.status = this.determineStatus(certData);

                        // Check for security issues
                        certData.securityIssues = this.analyzeSecurity(cert, domain);

                        // Check if certificate chain is complete
                        certData.chainComplete = this.checkCertificateChain(cert);

                        this.certificates.set(domain, certData);

                        // Check for alerts
                        this.checkAlerts(certData);

                        resolve(certData);

                    } catch (parseError) {
                        reject(new Error(`Failed to parse certificate: ${parseError.message}`));
                    } finally {
                        socket.end();
                    }
                });

                socket.on('error', (error) => {
                    const errorData = {
                        domain,
                        status: 'error',
                        error: error.message,
                        lastChecked: new Date().toISOString(),
                        checkDuration: Date.now() - startTime
                    };

                    this.certificates.set(domain, errorData);
                    reject(error);
                });

                socket.setTimeout(10000, () => {
                    socket.destroy();
                    reject(new Error('Connection timeout'));
                });
            });

        } catch (error) {
            const errorData = {
                domain,
                status: 'error',
                error: error.message,
                lastChecked: new Date().toISOString()
            };

            this.certificates.set(domain, errorData);
            throw error;
        }
    }

    parseCertificate(cert, domain) {
        if (!cert) {
            throw new Error('No certificate found');
        }

        const now = new Date();
        const validFrom = new Date(cert.valid_from);
        const validTo = new Date(cert.valid_to);
        const daysUntilExpiration = Math.ceil((validTo - now) / (1000 * 60 * 60 * 24));

        return {
            domain,
            subject: cert.subject,
            issuer: cert.issuer,
            serialNumber: cert.serialNumber,
            fingerprint: cert.fingerprint,
            fingerprint256: cert.fingerprint256,
            validFrom: validFrom.toISOString(),
            validTo: validTo.toISOString(),
            daysUntilExpiration,
            keyLength: cert.bits,
            signatureAlgorithm: cert.sigalg,
            protocol: cert.protocol,
            version: cert.version,
            altNames: this.parseAltNames(cert)
        };
    }

    parseAltNames(cert) {
        const altNames = [];

        if (cert.subjectaltname) {
            const sans = cert.subjectaltname.split(', ');
            for (const san of sans) {
                if (san.startsWith('DNS:')) {
                    altNames.push(san.substring(4));
                }
            }
        }

        return altNames;
    }

    determineStatus(certData) {
        const daysLeft = certData.daysUntilExpiration;

        if (certData.status === 'error') {
            return 'error';
        }

        if (daysLeft < 0) {
            return 'expired';
        } else if (daysLeft === 0) {
            return 'expiring-today';
        } else if (daysLeft <= 7) {
            return 'critical';
        } else if (daysLeft <= 30) {
            return 'warning';
        } else {
            return 'valid';
        }
    }

    analyzeSecurity(cert, domain) {
        const issues = [];

        // Check certificate key length
        if (cert.bits && cert.bits < 2048) {
            issues.push({
                type: 'weak_key',
                severity: 'high',
                message: `Certificate uses weak RSA key (${cert.bits} bits)`
            });
        }

        // Check signature algorithm
        if (cert.sigalg && cert.sigalg.includes('sha1')) {
            issues.push({
                type: 'weak_signature',
                severity: 'medium',
                message: 'Certificate uses SHA-1 signature algorithm'
            });
        }

        // Check for self-signed certificates
        if (cert.subject && cert.issuer &&
            JSON.stringify(cert.subject) === JSON.stringify(cert.issuer)) {
            issues.push({
                type: 'self_signed',
                severity: 'high',
                message: 'Certificate is self-signed'
            });
        }

        // Check certificate validity period
        const validFrom = new Date(cert.valid_from);
        const validTo = new Date(cert.valid_to);
        const validityPeriod = (validTo - validFrom) / (1000 * 60 * 60 * 24 * 365);

        if (validityPeriod > 2) {
            issues.push({
                type: 'long_validity',
                severity: 'low',
                message: `Certificate has long validity period (${Math.round(validityPeriod)} years)`
            });
        }

        // Check for certificate transparency (simplified check)
        // In production, you would use CT log monitoring
        if (!cert.fingerprint256) {
            issues.push({
                type: 'no_ct',
                severity: 'medium',
                message: 'Certificate transparency information not available'
            });
        }

        return issues;
    }

    checkCertificateChain(cert) {
        // Simplified chain check
        // In production, you would implement proper chain validation
        return true; // Placeholder
    }

    checkAlerts(certData) {
        const daysLeft = certData.daysUntilExpiration;

        // Check expiration alerts
        if (daysLeft <= this.config.alertThreshold && daysLeft > 0) {
            const alert = {
                id: `exp_${Date.now()}_${certData.domain}`,
                type: 'expiration',
                severity: daysLeft <= 7 ? 'critical' : 'warning',
                domain: certData.domain,
                message: `Certificate for ${certData.domain} expires in ${daysLeft} days`,
                timestamp: new Date().toISOString(),
                acknowledged: false
            };

            this.addAlert(alert);
        }

        // Check security issue alerts
        if (certData.securityIssues && certData.securityIssues.length > 0) {
            const highSeverityIssues = certData.securityIssues.filter(issue =>
                issue.severity === 'high' || issue.severity === 'critical'
            );

            if (highSeverityIssues.length > 0) {
                const alert = {
                    id: `security_${Date.now()}_${certData.domain}`,
                    type: 'security',
                    severity: 'high',
                    domain: certData.domain,
                    message: `Security issues detected for ${certData.domain}: ${highSeverityIssues.map(i => i.message).join(', ')}`,
                    timestamp: new Date().toISOString(),
                    acknowledged: false
                };

                this.addAlert(alert);
            }
        }
    }

    addAlert(alert) {
        // Check if similar alert already exists and not acknowledged
        const similarAlert = this.alerts.find(existing =>
            existing.domain === alert.domain &&
            existing.type === alert.type &&
            !existing.acknowledged
        );

        if (!similarAlert) {
            this.alerts.push(alert);
            this.broadcastAlert(alert);
            this.logAlert(alert);
        }
    }

    async logAlert(alert) {
        try {
            const logEntry = {
                ...alert,
                timestamp: new Date().toISOString()
            };

            this.history.push(logEntry);

            // Keep history size manageable
            if (this.history.length > 10000) {
                this.history = this.history.slice(-5000);
            }

            // Log to file if configured
            if (this.config.logFile) {
                fs.appendFileSync(this.config.logFile, JSON.stringify(logEntry) + '\n');
            }

            // Send notification if configured
            if (this.config.notificationWebhook) {
                await this.sendNotification(alert);
            }

        } catch (error) {
            console.error('Failed to log alert:', error);
        }
    }

    async sendNotification(alert) {
        try {
            await axios.post(this.config.notificationWebhook, {
                text: `🚨 Certificate Alert

${alert.message}

Severity: ${alert.severity.toUpperCase()}
Domain: ${alert.domain}
Time: ${alert.timestamp}`,
                username: 'Certificate Monitor',
                icon_emoji: ':warning:'
            });
        } catch (error) {
            console.error('Failed to send notification:', error);
        }
    }

    addCertificate(domain, options = {}) {
        const certInfo = {
            domain,
            port: options.port || 443,
            description: options.description,
            tags: options.tags || [],
            addedAt: new Date().toISOString(),
            lastChecked: null,
            status: 'pending'
        };

        this.certificates.set(domain, certInfo);
        this.saveData();

        // Check certificate immediately
        this.checkCertificate(domain, certInfo).catch(error => {
            console.error(`Failed to check new certificate ${domain}:`, error);
        });

        return certInfo;
    }

    removeCertificate(domain) {
        this.certificates.delete(domain);
        this.saveData();
    }

    acknowledgeAlert(alertId) {
        const alert = this.alerts.find(a => a.id === alertId);
        if (alert) {
            alert.acknowledged = true;
            alert.acknowledgedAt = new Date().toISOString();
            this.saveData();
            return true;
        }
        return false;
    }

    getCertificate(domain) {
        return this.certificates.get(domain);
    }

    getAllCertificates() {
        return Array.from(this.certificates.entries()).map(([domain, info]) => ({
            domain,
            ...info
        }));
    }

    getAlerts(severity = null, acknowledged = false) {
        return this.alerts.filter(alert => {
            if (severity && alert.severity !== severity) return false;
            if (!acknowledged && alert.acknowledged) return false;
            return true;
        });
    }

    getStatistics() {
        const certs = this.getAllCertificates();
        const stats = {
            total: certs.length,
            valid: 0,
            warning: 0,
            critical: 0,
            expired: 0,
            error: 0,
            averageDaysUntilExpiration: 0,
            securityIssues: 0
        };

        let totalDaysLeft = 0;
        let validCertsCount = 0;

        for (const cert of certs) {
            switch (cert.status) {
                case 'valid':
                    stats.valid++;
                    break;
                case 'warning':
                    stats.warning++;
                    break;
                case 'critical':
                    stats.critical++;
                    break;
                case 'expired':
                    stats.expired++;
                    break;
                case 'error':
                    stats.error++;
                    break;
            }

            if (cert.daysUntilExpiration !== undefined) {
                totalDaysLeft += cert.daysUntilExpiration;
                if (cert.daysUntilExpiration > 0) {
                    validCertsCount++;
                }
            }

            if (cert.securityIssues) {
                stats.securityIssues += cert.securityIssues.length;
            }
        }

        if (validCertsCount > 0) {
            stats.averageDaysUntilExpiration = Math.round(totalDaysLeft / validCertsCount);
        }

        return stats;
    }

    broadcastUpdate(data) {
        const message = JSON.stringify(data);
        this.clients.forEach(client => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(message);
            }
        });
    }

    broadcastAlert(alert) {
        this.broadcastUpdate({
            type: 'alert',
            alert
        });
    }

    async backupData() {
        try {
            const backupDir = path.join(__dirname, 'backups');
            if (!fs.existsSync(backupDir)) {
                fs.mkdirSync(backupDir, { recursive: true });
            }

            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            const backupFile = path.join(backupDir, `certificates-backup-${timestamp}.json`);

            const data = {
                certificates: Array.from(this.certificates.entries()),
                alerts: this.alerts,
                history: this.history,
                timestamp: new Date().toISOString()
            };

            fs.writeFileSync(backupFile, JSON.stringify(data, null, 2));

            // Clean old backups (keep last 30 days)
            const files = fs.readdirSync(backupDir);
            const backupFiles = files.filter(f => f.startsWith('certificates-backup-'));
            backupFiles.sort().reverse();

            if (backupFiles.length > 30) {
                const filesToDelete = backupFiles.slice(30);
                for (const file of filesToDelete) {
                    fs.unlinkSync(path.join(backupDir, file));
                }
            }

            console.log(`Backup created: ${backupFile}`);

        } catch (error) {
            console.error('Failed to create backup:', error);
        }
    }

    createServer() {
        const app = express();

        // Middleware
        app.use(express.json());
        app.use(express.static(path.join(__dirname, 'public')));

        // API Routes
        app.get('/api/certificates', (req, res) => {
            const certificates = this.getAllCertificates();
            res.json(certificates);
        });

        app.get('/api/certificates/:domain', (req, res) => {
            const { domain } = req.params;
            const certificate = this.getCertificate(domain);

            if (!certificate) {
                return res.status(404).json({ error: 'Certificate not found' });
            }

            res.json(certificate);
        });

        app.post('/api/certificates', async (req, res) => {
            try {
                const { domain, description, port, tags } = req.body;

                if (!domain) {
                    return res.status(400).json({ error: 'Domain is required' });
                }

                const certificate = this.addCertificate(domain, { description, port, tags });
                res.status(201).json(certificate);

            } catch (error) {
                res.status(500).json({ error: error.message });
            }
        });

        app.delete('/api/certificates/:domain', (req, res) => {
            const { domain } = req.params;
            this.removeCertificate(domain);
            res.status(204).send();
        });

        app.post('/api/certificates/:domain/check', async (req, res) => {
            try {
                const { domain } = req.params;
                const certificate = this.getCertificate(domain);

                if (!certificate) {
                    return res.status(404).json({ error: 'Certificate not found' });
                }

                await this.checkCertificate(domain, certificate);
                const updatedCertificate = this.getCertificate(domain);
                res.json(updatedCertificate);

            } catch (error) {
                res.status(500).json({ error: error.message });
            }
        });

        app.get('/api/alerts', (req, res) => {
            const { severity, acknowledged } = req.query;
            const alerts = this.getAlerts(severity, acknowledged === 'true');
            res.json(alerts);
        });

        app.post('/api/alerts/:id/acknowledge', (req, res) => {
            const { id } = req.params;
            const success = this.acknowledgeAlert(id);

            if (!success) {
                return res.status(404).json({ error: 'Alert not found' });
            }

            res.json({ success: true });
        });

        app.get('/api/statistics', (req, res) => {
            const stats = this.getStatistics();
            res.json(stats);
        });

        app.get('/api/history', (req, res) => {
            const { limit = 100, offset = 0 } = req.query;
            const history = this.history
                .slice(parseInt(offset), parseInt(offset) + parseInt(limit));
            res.json(history);
        });

        // Health check
        app.get('/api/health', (req, res) => {
            res.json({
                status: 'healthy',
                timestamp: new Date().toISOString(),
                certificates: this.certificates.size,
                alerts: this.alerts.filter(a => !a.acknowledged).length
            });
        });

        return app;
    }

    start() {
        const app = this.createServer();

        // HTTP Server
        app.listen(this.config.port, () => {
            console.log(`Certificate Monitor HTTP server running on port ${this.config.port}`);
        });

        // WebSocket Server
        const wss = new WebSocket.Server({ port: this.config.port + 1 });

        wss.on('connection', (ws) => {
            this.clients.add(ws);
            console.log('New WebSocket client connected');

            // Send current state
            ws.send(JSON.stringify({
                type: 'initial',
                certificates: this.getAllCertificates(),
                alerts: this.getAlerts(null, false),
                statistics: this.getStatistics()
            }));

            ws.on('close', () => {
                this.clients.delete(ws);
                console.log('WebSocket client disconnected');
            });

            ws.on('error', (error) => {
                console.error('WebSocket error:', error);
            });
        });

        console.log(`WebSocket server running on port ${this.config.port + 1}`);
        console.log('Certificate Monitor started successfully');
    }
}

// Example usage
const monitor = new CertificateMonitor({
    checkInterval: '0 */6 * * *', // Every 6 hours
    alertThreshold: 30, // Alert 30 days before expiration
    port: 3000,
    notificationWebhook: process.env.SLACK_WEBHOOK_URL, // Optional Slack webhook
    logFile: path.join(__dirname, 'logs', 'certificates.log')
});

// Add some initial certificates
monitor.addCertificate('google.com', {
    description: 'Google Search',
    tags: ['search', 'google']
});

monitor.addCertificate('github.com', {
    description: 'GitHub',
    tags: ['development', 'git']
});

// Start the monitor
monitor.start();

export { CertificateMonitor };