Mis flujos de trabajo
Este es unSecOpsflujo de automatización del dominio deautomatización que contiene 16 nodos.Utiliza principalmente nodos como If, Ssh, Code, Notion, Discord. 全面deSSL证书monitoreo,integraciónDiscord警报yNotion
- •Clave de API de Notion
- •Bot Token de Discord o Webhook
- •Pueden requerirse credenciales de autenticación para la API de destino
Nodos utilizados (16)
Categoría
{
"id": "REwp6JdhNjvbGQ1J",
"meta": {
"instanceId": "0728bbcb360bfc31668af41e0b3086e417a4d7ae6718b1f1eb6ce6f25309b188"
},
"name": "My workflow",
"tags": [],
"nodes": [
{
"id": "106c2b40-43f5-432d-9fdd-446e7309abc7",
"name": "Verificar SSL",
"type": "n8n-nodes-base.httpRequest",
"position": [
2300,
580
],
"parameters": {
"url": "=https://ssl-checker.io/api/v1/check/{{ $json[\"property_domains\"].replace(/^https?:\\/\\//, \"\").replace(/\\/$/, \"\") }}",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "92e421ce-6f88-44d4-9018-8b39e9fad396",
"name": "Alerta de Expiración",
"type": "n8n-nodes-base.if",
"position": [
2660,
580
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ee6e2ce8-569a-4f1f-91b5-2c55f605a16b",
"operator": {
"type": "number",
"operation": "lte"
},
"leftValue": "={{ $json.result.days_left }}",
"rightValue": 7
},
{
"id": "d82f8203-0908-4a48-9eb7-48e11555c1c2",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "bafe935b-b283-428f-934a-49d03de5d38f",
"name": "Activador Diario",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1620,
960
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 10
}
]
}
},
"typeVersion": 1.2
},
{
"id": "873fc171-6b69-4e80-b81a-f14d68c885f4",
"name": "Nota Adhesiva",
"type": "n8n-nodes-base.stickyNote",
"position": [
540,
140
],
"parameters": {
"width": 920,
"height": 1760,
"content": "## 🔐 Advanced SSL Health Monitor\n### 👤 Who is this for?\n#### This workflow is designed for **DevOps engineers, IT administrators, and security professionals** who need comprehensive **SSL certificate monitoring and health assessment** across multiple domains — featuring **dual verification** and **professional reporting** without relying on expensive monitoring services.\n\n---\n\n### 🧩 What It Does\n1. **Daily Trigger** runs the workflow every morning for proactive monitoring.\n2. **URL Collection** fetches the list of website URLs to monitor from your data source.\n3. **Dual SSL Analysis**:\n - **Bubobot SSL Scanner** — Our custom SSL health assessment tool (SSL Labs-style analysis)\n - **SSL-Checker.io API** — External verification for cross-validation\n4. **Comprehensive Health Check**:\n - Certificate expiration monitoring (customizable threshold)\n - SSL configuration security assessment\n - Protocol support analysis (TLS 1.3, 1.2, deprecated protocols)\n - Cipher suite strength evaluation\n - Vulnerability scanning (POODLE, BEAST, etc.)\n - Compliance checking (PCI DSS, NIST, FIPS)\n5. **Smart Alert System** sends Discord notifications when:\n - Certificates expire within threshold (default: 30 days)\n - SSL configuration issues detected (weak ciphers, deprecated protocols)\n - Security vulnerabilities found\n - Compliance standards not met\n - Grade drops below acceptable level (configurable)\n\n---\n\n### 🎯 Key Features\n- **🔄 Dual Verification**: Cross-checks results between internal scanner and external API\n- **📊 SSL Labs-Style Grading**: A+ to F rating system with detailed analysis\n- **🛡️ Security Assessment**: Vulnerability detection and compliance checking\n- **📱 Discord Integration**: Rich embed notifications with color-coded alerts\n- **📈 Historical Tracking**: Maintain SSL health trends over time\n- **🚨 Smart Alerting**: Only alerts on actual issues, reducing noise\n- **📄 Professional Reports**: HTML reports for stakeholder sharing\n\n---\n\n### ⚙️ Setup Instructions\n1. **Data Source**: \n - Configure your URL source from Notion\n - Ensure it contains a `URL` column with domains to monitor\n2. **Credentials**: \n - Set up **Discord webhook** for alert notifications\n - Configure any required **API credentials** for data sources\n3. **Customize Thresholds**: \n - **Expiration Alert**: Days before expiry (default: 30 days)\n - **Grade Threshold**: Minimum acceptable SSL grade (default: B)\n - **Alert Severity**: Choose which issues trigger notifications\n4. **Advanced Configuration**:\n - Modify **vulnerability checks** based on your security requirements\n - Adjust **compliance standards** for your industry needs\n - Customize **Discord message formatting** and alert channels\n\n---\n\n### 🧠 Technical Notes\n- **Dual-Check Reliability**: Combines custom Bubobot scanner with ssl-checker.io for maximum accuracy\n- **No Vendor Lock-in**: Uses free public APIs and open-source tools\n- **Scalable Architecture**: Easily handles monitoring of hundreds of domains\n- **Professional Reporting**: Generates SSL Labs-quality assessments\n- **Security-First Approach**: Comprehensive vulnerability and compliance checking\n- **Flexible Alerting**: Discord integration with rich formatting and conditional logic\n\n\nThis workflow transforms basic SSL expiry checking into a **comprehensive SSL security monitoring solution** that rivals enterprise-grade tools while remaining completely open-source and cost-effective."
},
"typeVersion": 1
},
{
"id": "b3cf7e3e-3d7b-484c-bd26-2d1aa996d020",
"name": "Nota Adhesiva1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1560,
780
],
"parameters": {
"height": 380,
"content": "### 🕒 Daily Trigger\nRuns the workflow every day at 10:00 AM. \nAdjust the time or frequency as needed using cron or interval settings.\n"
},
"typeVersion": 1
},
{
"id": "d55ad3b8-6cf1-479c-9646-973ff2946964",
"name": "Nota Adhesiva2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1860,
780
],
"parameters": {
"height": 380,
"content": "### 📄 Fetch URLs\nReads website URLs from Notion. \nMake sure the sheet has a column named `URL` containing the domains you want to monitor.\n"
},
"typeVersion": 1
},
{
"id": "fbb9752a-9231-4a3e-93ed-149770335f64",
"name": "Nota Adhesiva3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2260,
420
],
"parameters": {
"height": 320,
"content": "### 🔍 Check SSL\nQueries `ssl-checker.io` (free API) for SSL certificate data of each domain. \nReturns valid_from, valid_till, days_left, and host info.\n"
},
"typeVersion": 1
},
{
"id": "b5018cec-3c2e-4824-9d57-87157fc28616",
"name": "Nota Adhesiva4",
"type": "n8n-nodes-base.stickyNote",
"disabled": true,
"position": [
2600,
420
],
"parameters": {
"height": 320,
"content": "### ⚠️ Expiry Alert\nChecks if `days_left` is less than or equal to 7. \nOnly domains that meet this condition will trigger an email alert.\n"
},
"typeVersion": 1
},
{
"id": "a9913ede-0f83-4352-bf31-3634b856c35e",
"name": "Nota Adhesiva5",
"type": "n8n-nodes-base.stickyNote",
"disabled": true,
"position": [
2940,
420
],
"parameters": {
"width": 260,
"height": 320,
"content": "### 📧 Send Discord alerts\nSends alerts whether there are problems"
},
"typeVersion": 1
},
{
"id": "819e2cb7-d1a4-4fdd-b62a-d2378161373a",
"name": "Nota Adhesiva7",
"type": "n8n-nodes-base.stickyNote",
"position": [
2280,
1180
],
"parameters": {
"width": 520,
"height": 320,
"content": "### 🔍 Check SSL health score\nUse sysadmin-toolkit/scripts/ssl/ssl-health-assessment.js\n \nReturns SSL overall grade, vulnerabilities and actions to take"
},
"typeVersion": 1
},
{
"id": "e5ef4591-f45c-4712-97b4-0d7a520c238d",
"name": "SSH - Analizar sistema",
"type": "n8n-nodes-base.ssh",
"position": [
2380,
1340
],
"parameters": {
"command": "=node /opt/sysadmin-toolkit/scripts/ssl/ssl-health-assessment.js {{ $json.property_domains }} --json",
"authentication": "privateKey"
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "240f78ee-96ab-4f53-bb9c-c40e0f8d0961",
"name": "Discord",
"type": "n8n-nodes-base.discord",
"position": [
2980,
1340
],
"webhookId": "6e1014b5-d89d-4939-a1f3-b74c280ae73b",
"parameters": {
"content": "={{ $json.discord.title }}\n\n{{ $json.discord.fullDescription }}",
"guildId": {
"__rl": true,
"mode": "list",
"value": "1254730427805990942",
"cachedResultUrl": "https://discord.com/channels/1254730427805990942",
"cachedResultName": "b0ld8"
},
"options": {},
"resource": "message",
"channelId": {
"__rl": true,
"mode": "list",
"value": "1254730427805990945",
"cachedResultUrl": "https://discord.com/channels/1254730427805990942/1254730427805990945",
"cachedResultName": "general"
}
},
"typeVersion": 2
},
{
"id": "edc63e4c-8a87-4fbb-ba61-195b3e587965",
"name": "Discord1",
"type": "n8n-nodes-base.discord",
"position": [
3000,
560
],
"webhookId": "6e1014b5-d89d-4939-a1f3-b74c280ae73b",
"parameters": {
"content": "=SSL Expiry - {{ $json.result.days_left }} days Left - {{ $json.result.host }}",
"guildId": {
"__rl": true,
"mode": "list",
"value": "1254730427805990942",
"cachedResultUrl": "https://discord.com/channels/1254730427805990942",
"cachedResultName": "b0ld8"
},
"options": {},
"resource": "message",
"channelId": {
"__rl": true,
"mode": "list",
"value": "1254730427805990945",
"cachedResultUrl": "https://discord.com/channels/1254730427805990942/1254730427805990945",
"cachedResultName": "general"
}
},
"typeVersion": 2
},
{
"id": "c58aad29-de35-46e1-b43a-dfcfac0cffc2",
"name": "Obtener dominios para verificar SSL",
"type": "n8n-nodes-base.notion",
"position": [
1920,
960
],
"parameters": {
"options": {},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "list",
"value": "22612359-20f8-80f4-897f-eae0ad313057",
"cachedResultUrl": "https://www.notion.so/2261235920f880f4897feae0ad313057",
"cachedResultName": "n8n flow - SSL Certificate Expiry Monitoring"
},
"filterType": "manual"
},
"typeVersion": 2.2
},
{
"id": "b544b226-ceb6-439d-b350-4e77b39e2fde",
"name": "Código - Formatear salida",
"type": "n8n-nodes-base.code",
"position": [
2620,
1340
],
"parameters": {
"jsCode": "// n8n Code Node - Parse SSL Scanner Output\n// This code processes the SSH node output and formats it for Discord notifications\n\nconst results = [];\n\nfor (const item of $input.all()) {\n try {\n // Extract the stdout from SSH response\n const sshOutput = item.json;\n \n // Check if SSH command was successful\n if (sshOutput.code !== 0) {\n results.push({\n json: {\n hostname: 'unknown',\n status: 'error',\n error: sshOutput.stderr || 'SSH command failed',\n alert: true,\n alertLevel: 'critical'\n }\n });\n continue;\n }\n \n // Parse the JSON output from stdout\n let sslData;\n try {\n sslData = JSON.parse(sshOutput.stdout);\n } catch (parseError) {\n results.push({\n json: {\n hostname: 'unknown',\n status: 'error',\n error: 'Failed to parse SSL scanner output',\n alert: true,\n alertLevel: 'critical'\n }\n });\n continue;\n }\n \n // Extract key information for Discord notification\n const parsedResult = {\n // Basic info\n hostname: sslData.hostname,\n port: sslData.port || 443,\n scanTime: sslData.scanTime,\n scanDuration: sslData.scanDuration,\n \n // Overall status\n overallGrade: sslData.overallGrade,\n connectionStatus: sslData.connectionStatus || 'unknown',\n success: sslData.success || false,\n \n // Certificate info\n certificate: {\n subject: sslData.certificate?.subject || 'unknown',\n issuer: sslData.certificate?.issuer || 'unknown',\n validUntil: sslData.certificate?.validUntil,\n daysUntilExpiry: sslData.certificate?.daysUntilExpiry,\n isValid: sslData.certificate?.isValid || false,\n issues: sslData.certificate?.issues || [],\n hostnameMatch: sslData.certificate?.hostnameMatch || false\n },\n \n // Protocol support\n protocols: {\n tls13: sslData.protocols?.tls13 || false,\n tls12: sslData.protocols?.tls12 || false,\n tls11: sslData.protocols?.tls11 || false,\n tls10: sslData.protocols?.tls10 || false,\n ssl3: sslData.protocols?.ssl3 || false\n },\n \n // Security info\n vulnerabilities: {\n hasVulnerabilities: sslData.vulnerabilities?.hasVulnerabilities || false,\n poodle: sslData.vulnerabilities?.poodle || false,\n freak: sslData.vulnerabilities?.freak || false,\n details: sslData.vulnerabilities || {}\n },\n \n // Grades breakdown\n grades: sslData.grades || {},\n \n // Recommendations\n recommendations: sslData.recommendations || [],\n \n // Error handling\n error: sslData.error,\n \n // Alert logic\n alert: false,\n alertLevel: 'info',\n alertReasons: []\n };\n \n // Determine if this needs an alert\n const alertReasons = [];\n let alertLevel = 'info';\n \n // Critical alerts\n if (sslData.error) {\n alertReasons.push(`Scan failed: ${sslData.error}`);\n alertLevel = 'critical';\n }\n \n if (!parsedResult.certificate.isValid) {\n alertReasons.push('Certificate is invalid');\n alertLevel = 'critical';\n }\n \n if (parsedResult.certificate.daysUntilExpiry <= 0) {\n alertReasons.push('Certificate has expired');\n alertLevel = 'critical';\n }\n \n // High priority alerts\n if (parsedResult.certificate.daysUntilExpiry <= 7 && parsedResult.certificate.daysUntilExpiry > 0) {\n alertReasons.push(`Certificate expires in ${parsedResult.certificate.daysUntilExpiry} days`);\n if (alertLevel === 'info') alertLevel = 'high';\n }\n \n if (parsedResult.vulnerabilities.hasVulnerabilities) {\n alertReasons.push('Security vulnerabilities detected');\n if (alertLevel === 'info') alertLevel = 'high';\n }\n \n if (!parsedResult.certificate.hostnameMatch) {\n alertReasons.push('Hostname mismatch detected');\n if (alertLevel === 'info') alertLevel = 'high';\n }\n \n // Medium priority alerts\n if (parsedResult.certificate.daysUntilExpiry <= 30 && parsedResult.certificate.daysUntilExpiry > 7) {\n alertReasons.push(`Certificate expires in ${parsedResult.certificate.daysUntilExpiry} days`);\n if (alertLevel === 'info') alertLevel = 'medium';\n }\n \n if (['C', 'D', 'F'].includes(parsedResult.overallGrade)) {\n alertReasons.push(`Poor SSL grade: ${parsedResult.overallGrade}`);\n if (alertLevel === 'info') alertLevel = 'medium';\n }\n \n if (parsedResult.protocols.ssl3 || parsedResult.protocols.tls10) {\n alertReasons.push('Deprecated protocols enabled');\n if (alertLevel === 'info') alertLevel = 'medium';\n }\n \n // Low priority alerts\n if (parsedResult.recommendations && parsedResult.recommendations.length > 0) {\n alertReasons.push(`${parsedResult.recommendations.length} recommendations available`);\n if (alertLevel === 'info') alertLevel = 'low';\n }\n \n // Set alert status\n parsedResult.alert = alertReasons.length > 0;\n parsedResult.alertLevel = alertLevel;\n parsedResult.alertReasons = alertReasons;\n \n // Add Discord formatting helpers\n parsedResult.discord = {\n color: getDiscordColor(alertLevel, parsedResult.overallGrade),\n title: `SSL Health Check: ${parsedResult.hostname}`,\n description: getDiscordDescription(parsedResult),\n fields: getDiscordFields(parsedResult),\n timestamp: new Date().toISOString(),\n \n // Pre-formatted content for easy Discord input\n content: alertLevel === 'critical' ? '@here 🚨 SSL Health Monitor Alert' : '🔐 SSL Health Monitor Alert',\n \n // Simple descriptions without complex logic\n simpleDescription: parsedResult.alert \n ? `⚠️ SSL issues detected for ${parsedResult.hostname}`\n : `✅ SSL looks healthy for ${parsedResult.hostname}`,\n \n // Detailed info as simple strings\n certificateInfo: `Grade: ${parsedResult.overallGrade} | Expires in ${parsedResult.certificate.daysUntilExpiry} days`,\n \n // Alert details as simple string\n alertSummary: parsedResult.alert \n ? `Issues: ${parsedResult.alertReasons.join(', ')}`\n : `Certificate expires ${parsedResult.certificate.validUntil}`,\n \n // Complete formatted description\n fullDescription: getFullDiscordDescription(parsedResult)\n };\n \n results.push({ json: parsedResult });\n \n } catch (error) {\n // Handle any unexpected errors\n results.push({\n json: {\n hostname: 'unknown',\n status: 'error',\n error: `Processing error: ${error.message}`,\n alert: true,\n alertLevel: 'critical'\n }\n });\n }\n}\n\n// Helper functions for Discord formatting\nfunction getDiscordColor(alertLevel, grade) {\n // Color mapping for Discord embeds\n const colors = {\n critical: 15158332, // Red\n high: 15105570, // Orange\n medium: 15844367, // Yellow\n low: 5763719, // Blue\n info: 5763719 // Blue\n };\n \n // Grade-based colors for non-alerts\n const gradeColors = {\n 'A+': 5763719, // Green\n 'A': 3066993, // Green\n 'B': 15844367, // Yellow\n 'C': 15105570, // Orange\n 'D': 15158332, // Red\n 'F': 10038562 // Dark Red\n };\n \n if (alertLevel !== 'info') {\n return colors[alertLevel] || colors.info;\n }\n \n return gradeColors[grade] || colors.info;\n}\n\nfunction getFullDiscordDescription(result) {\n if (result.error) {\n return `❌ SSL scan failed: ${result.error}`;\n }\n \n if (result.alert) {\n let description = `⚠️ SSL issues detected for ${result.hostname}\\n`;\n description += `Grade: ${result.overallGrade} | Expires in ${result.certificate.daysUntilExpiry} days\\n\\n`;\n description += `Issues found:\\n`;\n result.alertReasons.forEach(reason => {\n description += `• ${reason}\\n`;\n });\n return description.trim();\n }\n \n return `✅ SSL configuration looks good for ${result.hostname}\\nGrade: ${result.overallGrade} | Certificate expires in ${result.certificate.daysUntilExpiry} days`;\n}\n\nfunction getDiscordDescription(result) {\n if (result.error) {\n return `❌ SSL scan failed: ${result.error}`;\n }\n \n if (result.alert) {\n // Create simple alert description\n let description = \"⚠️ SSL issues detected:\\n\";\n result.alertReasons.forEach(reason => {\n description += `• ${reason}\\n`;\n });\n return description.trim();\n }\n \n return `✅ SSL configuration looks good\\nGrade: ${result.overallGrade} | Expires in ${result.certificate.daysUntilExpiry} days`;\n}\n\nfunction getDiscordFields(result) {\n const fields = [];\n \n // Certificate info\n fields.push({\n name: \"📜 Certificate\",\n value: [\n `**Subject:** ${result.certificate.subject}`,\n `**Issuer:** ${result.certificate.issuer}`,\n `**Expires:** ${new Date(result.certificate.validUntil).toLocaleDateString()}`,\n `**Days Left:** ${result.certificate.daysUntilExpiry}`\n ].join('\\n'),\n inline: true\n });\n \n // Security info\n fields.push({\n name: \"🔐 Security\",\n value: [\n `**Overall Grade:** ${result.overallGrade}`,\n `**TLS 1.2:** ${result.protocols.tls12 ? '✅' : '❌'}`,\n `**TLS 1.3:** ${result.protocols.tls13 ? '✅' : '❌'}`,\n `**Vulnerabilities:** ${result.vulnerabilities.hasVulnerabilities ? '⚠️ Found' : '✅ None'}`\n ].join('\\n'),\n inline: true\n });\n \n // Add recommendations if any\n if (result.recommendations.length > 0) {\n fields.push({\n name: \"💡 Recommendations\",\n value: result.recommendations.slice(0, 3).map(rec => `• ${rec.message || rec}`).join('\\n') + \n (result.recommendations.length > 3 ? `\\n... and ${result.recommendations.length - 3} more` : ''),\n inline: false\n });\n }\n \n return fields;\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "8e86994c-5ebc-4600-80b1-ad187b17d125",
"name": "Nota Adhesiva6",
"type": "n8n-nodes-base.stickyNote",
"disabled": true,
"position": [
2900,
1180
],
"parameters": {
"width": 260,
"height": 320,
"content": "### 📧 Send Discord alerts\nSends alerts whether there are problems"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "746cb098-9c5f-4b65-9bd2-c7f20d40df61",
"connections": {
"106c2b40-43f5-432d-9fdd-446e7309abc7": {
"main": [
[
{
"node": "92e421ce-6f88-44d4-9018-8b39e9fad396",
"type": "main",
"index": 0
}
]
]
},
"92e421ce-6f88-44d4-9018-8b39e9fad396": {
"main": [
[
{
"node": "edc63e4c-8a87-4fbb-ba61-195b3e587965",
"type": "main",
"index": 0
}
]
]
},
"bafe935b-b283-428f-934a-49d03de5d38f": {
"main": [
[
{
"node": "c58aad29-de35-46e1-b43a-dfcfac0cffc2",
"type": "main",
"index": 0
}
]
]
},
"b544b226-ceb6-439d-b350-4e77b39e2fde": {
"main": [
[
{
"node": "240f78ee-96ab-4f53-bb9c-c40e0f8d0961",
"type": "main",
"index": 0
}
]
]
},
"e5ef4591-f45c-4712-97b4-0d7a520c238d": {
"main": [
[
{
"node": "b544b226-ceb6-439d-b350-4e77b39e2fde",
"type": "main",
"index": 0
}
]
]
},
"c58aad29-de35-46e1-b43a-dfcfac0cffc2": {
"main": [
[
{
"node": "e5ef4591-f45c-4712-97b4-0d7a520c238d",
"type": "main",
"index": 0
},
{
"node": "106c2b40-43f5-432d-9fdd-446e7309abc7",
"type": "main",
"index": 0
}
]
]
}
}
}¿Cómo usar este flujo de trabajo?
Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.
¿En qué escenarios es adecuado este flujo de trabajo?
Avanzado - Operaciones de seguridad
¿Es de pago?
Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.
Flujos de trabajo relacionados recomendados
Tom Cao
@tomcaoFounder at Bubobot - AI-powered monitoring platform Sharing n8n automation flows to help DevOps engineers and IT admins build better monitoring and incident response workflows. 🚀
Compartir este flujo de trabajo