278 lines
11 KiB
JSON
278 lines
11 KiB
JSON
{
|
|
"id": "ERCWB3oSYbgsUiqL",
|
|
"name": "NPM SSL Certificate Monitor",
|
|
"nodes": [
|
|
{
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "cronExpression",
|
|
"expression": "0 9 * * *"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"id": "schedule-trigger",
|
|
"name": "Daily SSL Check",
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
240,
|
|
400
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Process SSL certificates and check expiration\nconst certificates = $input.all();\nconst today = new Date();\nconst WARNING_DAYS = 7;\n\nconsole.log('=== NPM SSL Certificate Monitor ===');\nconsole.log('Total certificates:', certificates.length);\n\nconst certificateStatus = [];\nlet expiringCerts = [];\nlet skippedCount = 0;\n\nfor (const item of certificates) {\n const cert = item.json;\n \n // Skip if no expiration date or invalid\n if (!cert.expires_on || cert.expires_on === 0) {\n console.log('Skipping certificate (no expiration):', cert.nice_name || cert.id);\n skippedCount++;\n continue;\n }\n \n const domainNames = cert.domain_names?.join(', ') || cert.nice_name || `Certificate ${cert.id}`;\n \n // Create date and validate it\n const expiryDate = new Date(cert.expires_on * 1000);\n if (isNaN(expiryDate.getTime())) {\n console.log('Skipping certificate (invalid date):', domainNames);\n skippedCount++;\n continue;\n }\n \n const daysRemaining = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24));\n const isExpiring = daysRemaining <= WARNING_DAYS;\n \n let status = 'OK';\n if (daysRemaining < 0) {\n status = 'EXPIRED';\n } else if (isExpiring) {\n status = 'EXPIRING SOON';\n }\n \n const certInfo = {\n domain: domainNames,\n expiryDate: expiryDate.toISOString().split('T')[0],\n daysRemaining: daysRemaining,\n status: status,\n isExpiring: isExpiring\n };\n \n certificateStatus.push(certInfo);\n \n if (isExpiring) {\n expiringCerts.push(certInfo);\n console.log(`🔴 ${domainNames}: ${daysRemaining} days (expires ${certInfo.expiryDate})`);\n } else {\n console.log(`✅ ${domainNames}: ${daysRemaining} days`);\n }\n}\n\nconsole.log('\\n=== Summary ===');\nconsole.log('Valid certificates:', certificateStatus.length);\nconsole.log('Skipped (invalid/missing expiration):', skippedCount);\nconsole.log('Expiring within', WARNING_DAYS, 'days:', expiringCerts.length);\n\nreturn [{\n json: {\n totalCerts: certificateStatus.length,\n expiringCount: expiringCerts.length,\n hasExpiringCerts: expiringCerts.length > 0,\n allCertificates: certificateStatus,\n expiringCertificates: expiringCerts\n }\n}];"
|
|
},
|
|
"id": "process-certificates",
|
|
"name": "Process SSL Certificates",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
848,
|
|
400
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "strict",
|
|
"version": 1
|
|
},
|
|
"conditions": [
|
|
{
|
|
"id": "f5646765-356f-4270-b42b-8e86a3a64568",
|
|
"leftValue": "={{ $json.hasExpiringCerts }}",
|
|
"rightValue": "",
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
],
|
|
"combinator": "and"
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "check-expiring",
|
|
"name": "Any Expiring Certs?",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1040,
|
|
400
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"jsCode": "// Format warning email for expiring certificates\nconst data = $input.item.json;\nconst expiringCerts = data.expiringCertificates || [];\n\nlet tableRows = '';\nfor (const cert of expiringCerts) {\n const statusIcon = cert.status === 'EXPIRED' ? '🔴' : '⚠️';\n tableRows += `\n <tr>\n <td style=\"padding: 8px; border: 1px solid #ddd;\">${statusIcon} ${cert.domain}</td>\n <td style=\"padding: 8px; border: 1px solid #ddd;\">${cert.expiresOnFormatted}</td>\n <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; color: ${cert.daysRemaining <= 0 ? '#d32f2f' : '#f57c00'};\">${cert.daysRemaining} days</td>\n <td style=\"padding: 8px; border: 1px solid #ddd;\">${cert.status}</td>\n </tr>`;\n}\n\nconst subject = `⚠️ SSL Certificate Expiration Warning - ${expiringCerts.length} Certificate${expiringCerts.length > 1 ? 's' : ''}`;\n\nconst htmlBody = `\n<!DOCTYPE html>\n<html>\n<head>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }\n .container { max-width: 800px; margin: 0 auto; padding: 20px; }\n .header { background-color: #f57c00; color: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }\n .summary { background-color: #fff3e0; padding: 15px; border-radius: 5px; margin-bottom: 20px; border-left: 4px solid #f57c00; }\n table { width: 100%; border-collapse: collapse; margin: 20px 0; }\n th { background-color: #f5f5f5; padding: 10px; border: 1px solid #ddd; text-align: left; }\n .footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 12px; color: #666; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1>⚠️ SSL Certificate Expiration Warning</h1>\n </div>\n \n <div class=\"summary\">\n <h2>Summary</h2>\n <ul>\n <li><strong>${data.totalCertificates}</strong> SSL certificates checked</li>\n <li><strong>${expiringCerts.length}</strong> certificate${expiringCerts.length > 1 ? 's' : ''} expiring within ${data.warningDays} days</li>\n <li>Checked at: ${new Date(data.checkedAt).toLocaleString('en-US')}</li>\n </ul>\n </div>\n \n <h2>Certificates Requiring Attention</h2>\n <table>\n <thead>\n <tr>\n <th>Domain</th>\n <th>Expiration Date</th>\n <th>Days Remaining</th>\n <th>Status</th>\n </tr>\n </thead>\n <tbody>\n ${tableRows}\n </tbody>\n </table>\n \n <div class=\"footer\">\n <p><strong>Action Required:</strong> Please renew or update these SSL certificates in your Nginx Proxy Manager.</p>\n <p><a href=\"https://npm.dfw.ben.io\" style=\"color: #f57c00;\">Open Nginx Proxy Manager →</a></p>\n <p style=\"margin-top: 20px;\">This is an automated alert from your NPM SSL Certificate Monitor workflow.</p>\n </div>\n </div>\n</body>\n</html>\n`;\n\nreturn [{\n json: {\n subject: subject,\n htmlBody: htmlBody,\n to: 'admin@ben.io',\n expiringCount: expiringCerts.length\n }\n}];"
|
|
},
|
|
"id": "format-warning-email",
|
|
"name": "Format Warning Email",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1248,
|
|
320
|
|
]
|
|
},
|
|
{
|
|
"parameters": {},
|
|
"id": "manual-trigger",
|
|
"name": "Manual Trigger",
|
|
"type": "n8n-nodes-base.manualTrigger",
|
|
"typeVersion": 1,
|
|
"position": [
|
|
240,
|
|
272
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"sendTo": "={{ $json.to }}",
|
|
"subject": "={{ $json.subject }}",
|
|
"message": "={{ $json.htmlBody }}",
|
|
"options": {}
|
|
},
|
|
"id": "send-gmail",
|
|
"name": "Send Gmail Alert",
|
|
"type": "n8n-nodes-base.gmail",
|
|
"typeVersion": 2.1,
|
|
"position": [
|
|
1440,
|
|
400
|
|
],
|
|
"webhookId": "c9d0a896-4275-4387-a2c8-af1f138559a9",
|
|
"credentials": {
|
|
"gmailOAuth2": {
|
|
"id": "Os1ux3h3zFlC2XkG",
|
|
"name": "Gmail account"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"parameters": {
|
|
"url": "https://npm.dfw.ben.io/api/nginx/certificates",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Authorization",
|
|
"value": "=Bearer {{ $json.token }}"
|
|
}
|
|
]
|
|
},
|
|
"options": {}
|
|
},
|
|
"id": "fetch-certs",
|
|
"name": "Fetch Certificates",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
640,
|
|
400
|
|
]
|
|
},
|
|
{
|
|
"parameters": {
|
|
"operation": "select",
|
|
"schema": {
|
|
"__rl": true,
|
|
"mode": "list",
|
|
"value": "public"
|
|
},
|
|
"table": {
|
|
"__rl": true,
|
|
"value": "npm_tokens",
|
|
"mode": "list",
|
|
"cachedResultName": "npm_tokens"
|
|
},
|
|
"where": {
|
|
"values": [
|
|
{
|
|
"column": "service_name",
|
|
"value": "npm_dfw"
|
|
}
|
|
]
|
|
},
|
|
"options": {}
|
|
},
|
|
"type": "n8n-nodes-base.postgres",
|
|
"typeVersion": 2.6,
|
|
"position": [
|
|
448,
|
|
400
|
|
],
|
|
"id": "18c7d30b-bd5e-450c-9800-4f883c641b1c",
|
|
"name": "Select rows from a table",
|
|
"credentials": {
|
|
"postgres": {
|
|
"id": "Ik8CFyap8ic2Md3M",
|
|
"name": "n8n-infra"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"connections": {
|
|
"Daily SSL Check": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Select rows from a table",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Process SSL Certificates": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Any Expiring Certs?",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Manual Trigger": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Select rows from a table",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Format Warning Email": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Send Gmail Alert",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Any Expiring Certs?": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Format Warning Email",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Fetch Certificates": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Process SSL Certificates",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"Select rows from a table": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Fetch Certificates",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"saveExecutionProgress": true,
|
|
"saveManualExecutions": true,
|
|
"saveDataErrorExecution": "all",
|
|
"saveDataSuccessExecution": "all",
|
|
"executionOrder": "v1"
|
|
},
|
|
"triggerCount": 1,
|
|
"versionId": "67bbccd4-8c5e-4cc5-8969-9460ed529b23",
|
|
"owner": {
|
|
"type": "personal",
|
|
"projectId": "FeLO36wNUAcn61Wj",
|
|
"projectName": "Ben W <admin@ben.io>",
|
|
"personalEmail": "admin@ben.io"
|
|
},
|
|
"parentFolderId": "eWW72giJDI4fxlWw",
|
|
"isArchived": false
|
|
} |