yusufkhon-guard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +734 -0
- package/bin/cli.js +39 -0
- package/bin/renew.js +149 -0
- package/bin/scan.js +124 -0
- package/bin/ssl.js +181 -0
- package/package.json +80 -0
- package/src/index.js +206 -0
- package/src/modules/acme/client.js +240 -0
- package/src/modules/acme/csr.js +172 -0
- package/src/modules/acme/detectDomains.js +132 -0
- package/src/modules/acme/index.js +211 -0
- package/src/modules/acme/jose.js +125 -0
- package/src/modules/acme/renew.js +89 -0
- package/src/modules/adaptiveRateLimiter.js +154 -0
- package/src/modules/alerts.js +168 -0
- package/src/modules/bodyLimit.js +106 -0
- package/src/modules/botDetector.js +144 -0
- package/src/modules/connectionGuard.js +128 -0
- package/src/modules/csrf.js +130 -0
- package/src/modules/dashboard.js +257 -0
- package/src/modules/dashboardHtml.js +235 -0
- package/src/modules/datacenterRanges.js +121 -0
- package/src/modules/ddosGuard.js +323 -0
- package/src/modules/firewall.js +239 -0
- package/src/modules/geoBlock.js +169 -0
- package/src/modules/headers.js +130 -0
- package/src/modules/honeypot.js +149 -0
- package/src/modules/httpsRedirect.js +67 -0
- package/src/modules/integrity.js +164 -0
- package/src/modules/ipBlocker.js +372 -0
- package/src/modules/ja3.js +155 -0
- package/src/modules/logExport.js +134 -0
- package/src/modules/nosqlGuard.js +143 -0
- package/src/modules/osFirewall.js +182 -0
- package/src/modules/rateLimiter.js +196 -0
- package/src/modules/sanitizer.js +253 -0
- package/src/modules/threatDetector.js +202 -0
- package/src/scanner/checks/dependencies.js +125 -0
- package/src/scanner/checks/environment.js +118 -0
- package/src/scanner/checks/exposure.js +99 -0
- package/src/scanner/checks/headers.js +119 -0
- package/src/scanner/checks/ssl.js +151 -0
- package/src/scanner/index.js +209 -0
- package/src/utils/cidr.js +140 -0
- package/src/utils/logger.js +194 -0
- package/src/utils/privilege.js +212 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file bin/cli.js
|
|
6
|
+
* @description `yusufkhon-guard` CLI kirish nuqtasi (dispatcher). Birinchi
|
|
7
|
+
* argumentga qarab kichik buyruqni tanlaydi:
|
|
8
|
+
* yusufkhon-guard scan ... -> xavfsizlik auditi
|
|
9
|
+
* yusufkhon-guard ssl ... -> Let's Encrypt SSL sertifikati
|
|
10
|
+
* Kichik buyruq berilmasa yoki `--...` bilan boshlansa ā `scan`
|
|
11
|
+
* (orqaga moslik uchun).
|
|
12
|
+
* @module bin/cli
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const sub = process.argv[2];
|
|
16
|
+
|
|
17
|
+
if (sub === 'ssl') {
|
|
18
|
+
require('./ssl').main();
|
|
19
|
+
} else if (sub === 'renew') {
|
|
20
|
+
require('./renew').main();
|
|
21
|
+
} else if (sub === '--help' || sub === '-h' || sub === 'help') {
|
|
22
|
+
console.log(`
|
|
23
|
+
š”ļø yusufkhon-guard CLI
|
|
24
|
+
|
|
25
|
+
Buyruqlar:
|
|
26
|
+
scan Serverni xavfsizlik jihatidan tekshiradi (SSL, sarlavhalar, oshkorlik...)
|
|
27
|
+
ssl Let's Encrypt orqali SSL sertifikatini avtomat oladi (domenni o'zi aniqlaydi)
|
|
28
|
+
renew Sertifikatni tekshiradi/yangilaydi; --install bilan avtomat yangilash
|
|
29
|
+
|
|
30
|
+
Batafsil:
|
|
31
|
+
npx yusufkhon-guard scan --help
|
|
32
|
+
npx yusufkhon-guard ssl --help
|
|
33
|
+
npx yusufkhon-guard renew --help
|
|
34
|
+
`);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
} else {
|
|
37
|
+
// Standart: scan ('scan', undefined yoki `--domain ...` kabi eski chaqiruvlar).
|
|
38
|
+
require('./scan').run();
|
|
39
|
+
}
|
package/bin/renew.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file bin/renew.js
|
|
6
|
+
* @description `yusufkhon-guard renew` ā mavjud sertifikatni tekshiradi va
|
|
7
|
+
* `<N kun` qolgan bo'lsa AVTOMAT yangilaydi. `--install` bilan
|
|
8
|
+
* har kunlik avtomat yangilashni (cron/systemd/schtasks) rozilik
|
|
9
|
+
* qatlami orqali o'rnatadi.
|
|
10
|
+
*
|
|
11
|
+
* Misollar:
|
|
12
|
+
* npx yusufkhon-guard renew # kerak bo'lsa yangilaydi
|
|
13
|
+
* npx yusufkhon-guard renew --force # majburan yangilaydi
|
|
14
|
+
* sudo npx yusufkhon-guard renew --install # avtomat yangilashni o'rnatadi
|
|
15
|
+
* @module bin/renew
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const acme = require('../src/modules/acme');
|
|
20
|
+
const privilege = require('../src/utils/privilege');
|
|
21
|
+
const { createChallengeServer, parseArgs } = require('./ssl');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Avtomat yangilash (auto-renew) buyrug'ini platformaga mos tuzadi.
|
|
25
|
+
* @param {string} certDir
|
|
26
|
+
* @param {string} cwd
|
|
27
|
+
* @returns {{ command: string, args: string[] }}
|
|
28
|
+
*/
|
|
29
|
+
function buildAutoRenewCommand(certDir, cwd) {
|
|
30
|
+
if (process.platform === 'win32') {
|
|
31
|
+
const tr = `cmd /c cd /d "${cwd}" && npx yusufkhon-guard renew --cert-dir "${certDir}" --yes`;
|
|
32
|
+
return { command: 'schtasks', args: ['/create', '/tn', 'yusufkhon-guard-renew', '/tr', tr, '/sc', 'daily', '/st', '03:00', '/f'] };
|
|
33
|
+
}
|
|
34
|
+
const line = `0 3 * * * cd ${cwd} && npx yusufkhon-guard renew --cert-dir ${certDir} --yes >> ${certDir}/renew.log 2>&1 # yusufkhon-guard-renew`;
|
|
35
|
+
const pipeline = `(crontab -l 2>/dev/null | grep -v 'yusufkhon-guard-renew'; echo '${line}') | crontab -`;
|
|
36
|
+
return { command: 'sh', args: ['-c', pipeline] };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Avtomat yangilashni rozilik qatlami orqali o'rnatadi.
|
|
41
|
+
* @param {Object} params
|
|
42
|
+
* @param {string} params.certDir
|
|
43
|
+
* @param {boolean} [params.autoApprove]
|
|
44
|
+
* @param {boolean} [params.dryRun]
|
|
45
|
+
* @returns {Promise<import('../src/utils/privilege').PrivilegedResult>}
|
|
46
|
+
*/
|
|
47
|
+
function installAutoRenew({ certDir, autoApprove, dryRun }) {
|
|
48
|
+
const cmd = buildAutoRenewCommand(path.resolve(certDir), process.cwd());
|
|
49
|
+
return privilege.runPrivileged(cmd.command, cmd.args, {
|
|
50
|
+
description: 'Har kuni 03:00 da sertifikatni avtomat yangilash (auto-renew) o\'rnatish',
|
|
51
|
+
autoApprove,
|
|
52
|
+
dryRun,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Yordam matni. @returns {void} */
|
|
57
|
+
function printHelp() {
|
|
58
|
+
console.log(`
|
|
59
|
+
š yusufkhon-guard renew ā sertifikatni tekshiradi va kerak bo'lsa yangilaydi
|
|
60
|
+
|
|
61
|
+
Foydalanish:
|
|
62
|
+
npx yusufkhon-guard renew [parametrlar]
|
|
63
|
+
|
|
64
|
+
Parametrlar:
|
|
65
|
+
--cert-dir <yo'l> Sertifikat papkasi (standart: ./certs)
|
|
66
|
+
--days <n> Shu kundan kam qolsa yangilanadi (standart: 30)
|
|
67
|
+
--force Muddatidan qat'i nazar yangilaydi
|
|
68
|
+
--domain <d> Domen(lar)ni qo'lda ko'rsatish (aks holda cert SAN'idan olinadi)
|
|
69
|
+
--production Jonli muhit (standart: staging)
|
|
70
|
+
--port <n> Challenge porti (standart: 80)
|
|
71
|
+
--install Avtomat (har kunlik) yangilashni o'rnatadi (root/admin rozilik)
|
|
72
|
+
--yes Rozilikni so'ramasdan tasdiqlash
|
|
73
|
+
--dry-run O'rnatish buyrug'ini faqat ko'rsatadi (bajarmaydi)
|
|
74
|
+
--help Ushbu yordam
|
|
75
|
+
`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* CLI kirish nuqtasi.
|
|
80
|
+
* @returns {Promise<void>}
|
|
81
|
+
*/
|
|
82
|
+
async function main() {
|
|
83
|
+
const raw = process.argv.slice(2).filter((a) => a !== 'renew');
|
|
84
|
+
const args = parseArgs(raw);
|
|
85
|
+
if (args.help) { printHelp(); process.exit(0); }
|
|
86
|
+
|
|
87
|
+
const certDir = typeof args['cert-dir'] === 'string' ? args['cert-dir'] : './certs';
|
|
88
|
+
|
|
89
|
+
// 1) Avtomat yangilashni o'rnatish.
|
|
90
|
+
if (args.install) {
|
|
91
|
+
await installAutoRenew({ certDir, autoApprove: Boolean(args.yes), dryRun: Boolean(args['dry-run']) });
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2) Holatni tekshirish.
|
|
96
|
+
const days = Number(args.days) || 30;
|
|
97
|
+
const certPath = path.join(certDir, 'fullchain.pem');
|
|
98
|
+
const status = acme.checkCert(certPath, days);
|
|
99
|
+
|
|
100
|
+
console.log(`š Sertifikat: ${certPath}`);
|
|
101
|
+
console.log(` Holat: ${status.reason}`);
|
|
102
|
+
|
|
103
|
+
if (!status.exists && !args.domain) {
|
|
104
|
+
console.error('ā Sertifikat topilmadi. Avval oling: npx yusufkhon-guard ssl');
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const domains = args.domain
|
|
109
|
+
? String(args.domain).split(',').map((s) => s.trim()).filter(Boolean)
|
|
110
|
+
: (status.domains || []);
|
|
111
|
+
if (!domains.length) {
|
|
112
|
+
console.error('ā Yangilanadigan domen aniqlanmadi (--domain bering).');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!status.needsRenewal && !args.force) {
|
|
117
|
+
console.log(`ā
Hali yangi ā yangilash shart emas (${status.daysLeft} kun qoldi). Majburlash uchun: --force`);
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 3) Yangilash.
|
|
122
|
+
console.log(`š Yangilanyapti: ${domains.join(', ')}`);
|
|
123
|
+
const environment = args.production ? 'production' : 'staging';
|
|
124
|
+
const port = Number(args.port) || 80;
|
|
125
|
+
const accountKeyPath = typeof args['account-key'] === 'string' ? args['account-key'] : path.join(certDir, 'account.pem');
|
|
126
|
+
|
|
127
|
+
const store = new acme.ChallengeStore();
|
|
128
|
+
const server = createChallengeServer(store);
|
|
129
|
+
try {
|
|
130
|
+
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(port, resolve); });
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error(`ā ${port}-portni ochib bo'lmadi (${err && err.code}). sudo yoki --port bering.`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const result = await acme.obtainCertificate({ domains, email: typeof args.email === 'string' ? args.email : undefined, challengeStore: store, environment, certDir, accountKeyPath });
|
|
137
|
+
console.log(`ā
Sertifikat yangilandi: ${result.paths.fullchain}`);
|
|
138
|
+
process.exit(0);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error(`ā Yangilash muvaffaqiyatsiz: ${err && err.message}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
} finally {
|
|
143
|
+
server.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (require.main === module) main();
|
|
148
|
+
|
|
149
|
+
module.exports = { main, buildAutoRenewCommand, installAutoRenew };
|
package/bin/scan.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file bin/scan.js
|
|
6
|
+
* @description `yusufkhon-guard` ning buyruq qatori (CLI) skaneri.
|
|
7
|
+
* Foydalanuvchi kutubxonani o'rnatgach, terminalda quyidagicha
|
|
8
|
+
* ishga tushiradi:
|
|
9
|
+
*
|
|
10
|
+
* npx yusufkhon-guard scan --domain example.com --target http://localhost:3000 --fix
|
|
11
|
+
*
|
|
12
|
+
* yoki loyihada: npm run scan
|
|
13
|
+
* @module bin/scan
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { scan, formatReport } = require('../src/scanner');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `--kalit qiymat` va `--bayroq` ko'rinishidagi argumentlarni tahlil qiladi.
|
|
20
|
+
* @param {string[]} argv - process.argv.slice(2).
|
|
21
|
+
* @returns {Record<string, string|boolean>}
|
|
22
|
+
*/
|
|
23
|
+
function parseArgs(argv) {
|
|
24
|
+
/** @type {Record<string, string|boolean>} */
|
|
25
|
+
const args = {};
|
|
26
|
+
for (let i = 0; i < argv.length; i++) {
|
|
27
|
+
const token = argv[i];
|
|
28
|
+
if (token.startsWith('--')) {
|
|
29
|
+
const key = token.slice(2);
|
|
30
|
+
const next = argv[i + 1];
|
|
31
|
+
if (next && !next.startsWith('--')) {
|
|
32
|
+
args[key] = next;
|
|
33
|
+
i++;
|
|
34
|
+
} else {
|
|
35
|
+
args[key] = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return args;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Yordam matnini chiqaradi.
|
|
44
|
+
* @returns {void}
|
|
45
|
+
*/
|
|
46
|
+
function printHelp() {
|
|
47
|
+
console.log(`
|
|
48
|
+
š”ļø yusufkhon-guard ā Xavfsizlik skaneri
|
|
49
|
+
|
|
50
|
+
Foydalanish:
|
|
51
|
+
npx yusufkhon-guard scan [parametrlar]
|
|
52
|
+
|
|
53
|
+
Parametrlar:
|
|
54
|
+
--domain <domen> SSL/sertifikat tekshiruvi uchun domen (misol: example.com)
|
|
55
|
+
--target <url> Xavfsizlik sarlavhalarini tekshirish uchun server URL'i
|
|
56
|
+
(misol: http://localhost:3000)
|
|
57
|
+
--cwd <yo'l> Loyiha ildiz papkasi (standart: joriy papka)
|
|
58
|
+
--fix Muammolarni tuzatish (xavfsizlari avtomat; root talab
|
|
59
|
+
qiladiganlari rozilik so'rab, sudo/UAC bilan)
|
|
60
|
+
--yes Privilegiyali tuzatishlarga so'ramasdan rozilik berish
|
|
61
|
+
--dry-run Privilegiyali buyruqlarni bajarmasdan faqat ko'rsatish
|
|
62
|
+
--json Natijani JSON ko'rinishida chiqarish
|
|
63
|
+
--help Ushbu yordamni ko'rsatish
|
|
64
|
+
|
|
65
|
+
Misollar:
|
|
66
|
+
npx yusufkhon-guard scan --domain mysite.uz --target http://localhost:3000
|
|
67
|
+
npx yusufkhon-guard scan --fix
|
|
68
|
+
sudo npx yusufkhon-guard scan --domain mysite.uz --fix --yes
|
|
69
|
+
npx yusufkhon-guard scan --domain mysite.uz --fix --dry-run
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* CLI kirish nuqtasi.
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
77
|
+
async function main() {
|
|
78
|
+
// "scan" buyrug'ini (agar berilgan bo'lsa) tashlab yuboramiz.
|
|
79
|
+
const raw = process.argv.slice(2).filter((a) => a !== 'scan');
|
|
80
|
+
const args = parseArgs(raw);
|
|
81
|
+
|
|
82
|
+
if (args.help) {
|
|
83
|
+
printHelp();
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const result = await scan({
|
|
88
|
+
domain: typeof args.domain === 'string' ? args.domain : undefined,
|
|
89
|
+
target: typeof args.target === 'string' ? args.target : undefined,
|
|
90
|
+
cwd: typeof args.cwd === 'string' ? args.cwd : process.cwd(),
|
|
91
|
+
autoFix: Boolean(args.fix),
|
|
92
|
+
autoApprove: Boolean(args.yes),
|
|
93
|
+
dryRun: Boolean(args['dry-run']),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (args.json) {
|
|
97
|
+
// Ichki (_fix / _privileged) maydonlarni JSON'ga chiqarmaymiz.
|
|
98
|
+
const clean = {
|
|
99
|
+
...result,
|
|
100
|
+
findings: result.findings.map(({ _fix, _privileged, ...rest }) => rest),
|
|
101
|
+
};
|
|
102
|
+
console.log(JSON.stringify(clean, null, 2));
|
|
103
|
+
} else {
|
|
104
|
+
console.log(formatReport(result));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Kritik yoki yuqori darajadagi muammo bo'lsa ā nol bo'lmagan chiqish kodi
|
|
108
|
+
// (CI/CD quvurlarida to'xtatish uchun foydali).
|
|
109
|
+
const failing = result.summary.critical + result.summary.high;
|
|
110
|
+
process.exit(failing > 0 ? 1 : 0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Skaner CLI'ni ishga tushiradi (dispatcher yoki to'g'ridan-to'g'ri chaqiradi). */
|
|
114
|
+
function run() {
|
|
115
|
+
return main().catch((err) => {
|
|
116
|
+
console.error('Skaner xatosi:', err);
|
|
117
|
+
process.exit(2);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// To'g'ridan-to'g'ri (`node bin/scan.js`) chaqirilgandagina avtomat ishga tushadi.
|
|
122
|
+
if (require.main === module) run();
|
|
123
|
+
|
|
124
|
+
module.exports = { run, main, parseArgs };
|
package/bin/ssl.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file bin/ssl.js
|
|
6
|
+
* @description `yusufkhon-guard ssl` ā Let's Encrypt (ACME) orqali SSL sertifikatini
|
|
7
|
+
* KOD YOZMASDAN, bitta buyruq bilan oladi ("standalone" rejim, certbot
|
|
8
|
+
* kabi). O'zi vaqtinchalik HTTP server ochib, HTTP-01 challenge'ni
|
|
9
|
+
* bajaradi va sertifikatni saqlaydi.
|
|
10
|
+
*
|
|
11
|
+
* Misol:
|
|
12
|
+
* npx yusufkhon-guard ssl --domain sayt.uz --email admin@sayt.uz
|
|
13
|
+
* npx yusufkhon-guard ssl --domain sayt.uz,www.sayt.uz --production
|
|
14
|
+
* @module bin/ssl
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const http = require('http');
|
|
18
|
+
const acme = require('../src/modules/acme');
|
|
19
|
+
const privilege = require('../src/utils/privilege');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Sertifikat uchun domenlarni aniqlaydi: agar `--domain` berilgan bo'lsa o'shani,
|
|
23
|
+
* aks holda VPS konfiguratsiyasidan (nginx/apache/hostname) AVTOMAT topib, admin'dan
|
|
24
|
+
* tasdiq so'raydi.
|
|
25
|
+
* @param {Record<string,string|boolean>} args
|
|
26
|
+
* @param {Object} [deps] - Test uchun: { detect, confirm }.
|
|
27
|
+
* @returns {Promise<{ domains: string[], detected: Array, source: string, confirmed?: boolean }>}
|
|
28
|
+
*/
|
|
29
|
+
async function resolveDomains(args, deps = {}) {
|
|
30
|
+
const detectFn = deps.detect || (() => acme.detectDomains());
|
|
31
|
+
const confirmFn = deps.confirm || ((msg) => privilege.confirm(msg, { autoApprove: Boolean(args.yes), defaultYes: true }));
|
|
32
|
+
|
|
33
|
+
// 1) Aniq --domain berilgan bo'lsa ā o'shani ishlatamiz.
|
|
34
|
+
if (args.domain) {
|
|
35
|
+
const domains = String(args.domain).split(',').map((s) => s.trim()).filter(Boolean);
|
|
36
|
+
return { domains, detected: [], source: 'flag' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2) Avtomat aniqlash (nginx / apache / hostname).
|
|
40
|
+
const detected = detectFn();
|
|
41
|
+
if (!detected.length) return { domains: [], detected: [], source: 'none' };
|
|
42
|
+
|
|
43
|
+
const list = detected.map((d) => `${d.domain} (${d.source})`).join(', ');
|
|
44
|
+
const ok = await confirmFn(` š Aniqlangan domen(lar): ${list}\n ā Shu domen(lar) uchun SSL sertifikati olinsinmi?`);
|
|
45
|
+
return { domains: ok ? detected.map((d) => d.domain) : [], detected, source: 'detected', confirmed: ok };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* `--kalit qiymat` va `--bayroq` argumentlarini tahlil qiladi.
|
|
50
|
+
* @param {string[]} argv - process.argv.slice(2) ('ssl' tokeni chiqarilgan holda).
|
|
51
|
+
* @returns {Record<string, string|boolean>}
|
|
52
|
+
*/
|
|
53
|
+
function parseArgs(argv) {
|
|
54
|
+
/** @type {Record<string, string|boolean>} */
|
|
55
|
+
const args = {};
|
|
56
|
+
for (let i = 0; i < argv.length; i++) {
|
|
57
|
+
const token = argv[i];
|
|
58
|
+
if (token.startsWith('--')) {
|
|
59
|
+
const key = token.slice(2);
|
|
60
|
+
const next = argv[i + 1];
|
|
61
|
+
if (next && !next.startsWith('--')) { args[key] = next; i++; }
|
|
62
|
+
else args[key] = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return args;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Challenge'ni beruvchi vaqtinchalik HTTP serverni yaratadi (express'siz).
|
|
70
|
+
* `/.well-known/acme-challenge/<token>` so'roviga keyAuth qaytaradi, qolganiga 404.
|
|
71
|
+
* @param {acme.ChallengeStore} store
|
|
72
|
+
* @returns {import('http').Server}
|
|
73
|
+
*/
|
|
74
|
+
function createChallengeServer(store) {
|
|
75
|
+
const chal = acme.challengeMiddleware(store);
|
|
76
|
+
return http.createServer((req, res) => {
|
|
77
|
+
chal(req, res, () => { res.statusCode = 404; res.end('Not found'); });
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Yordam matnini chiqaradi. @returns {void} */
|
|
82
|
+
function printHelp() {
|
|
83
|
+
console.log(`
|
|
84
|
+
š yusufkhon-guard ssl ā Let's Encrypt SSL sertifikatini avtomat oladi
|
|
85
|
+
|
|
86
|
+
Foydalanish:
|
|
87
|
+
npx yusufkhon-guard ssl # domenni AVTOMAT aniqlab, tasdiq so'raydi
|
|
88
|
+
npx yusufkhon-guard ssl --domain <domen> # domenni qo'lda ko'rsatib
|
|
89
|
+
|
|
90
|
+
Parametrlar:
|
|
91
|
+
--domain <d> Domen(lar). Berilmasa ā nginx/apache/hostname'dan avtomat topiladi
|
|
92
|
+
--email <e> Bog'lanish uchun email (tavsiya etiladi)
|
|
93
|
+
--yes Aniqlangan domenni so'ramasdan tasdiqlash (non-interactive)
|
|
94
|
+
--staging Sinov muhiti (STANDART) ā soxta, lekin xavfsiz cert
|
|
95
|
+
--production Jonli muhit ā HAQIQIY cert (limitlarga e'tibor bering!)
|
|
96
|
+
--cert-dir <yo'l> Sertifikat saqlanadigan papka (standart: ./certs)
|
|
97
|
+
--account-key <f> ACME hisob kaliti fayli (standart: ./certs/account.pem)
|
|
98
|
+
--port <n> Challenge HTTP porti (standart: 80)
|
|
99
|
+
--help Ushbu yordam
|
|
100
|
+
|
|
101
|
+
Shartlar: real ommaviy domen, DNS domenni shu serverga yo'naltirgan, 80-port ochiq.
|
|
102
|
+
|
|
103
|
+
Misollar:
|
|
104
|
+
npx yusufkhon-guard ssl --domain sayt.uz --email admin@sayt.uz
|
|
105
|
+
npx yusufkhon-guard ssl --domain sayt.uz,www.sayt.uz --production
|
|
106
|
+
`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* CLI kirish nuqtasi.
|
|
111
|
+
* @returns {Promise<void>}
|
|
112
|
+
*/
|
|
113
|
+
async function main() {
|
|
114
|
+
const raw = process.argv.slice(2).filter((a) => a !== 'ssl');
|
|
115
|
+
const args = parseArgs(raw);
|
|
116
|
+
|
|
117
|
+
if (args.help) { printHelp(); process.exit(0); }
|
|
118
|
+
|
|
119
|
+
// Domenni aniqlaymiz (--domain yoki avtomat aniqlash + admin tasdiqi).
|
|
120
|
+
const resolved = await resolveDomains(args);
|
|
121
|
+
const domains = resolved.domains;
|
|
122
|
+
if (!domains.length) {
|
|
123
|
+
if (resolved.source === 'none') {
|
|
124
|
+
console.error('ā Domen avtomat aniqlanmadi (nginx/apache/hostname topilmadi).');
|
|
125
|
+
console.error(' --domain bilan qo\'lda ko\'rsating: npx yusufkhon-guard ssl --domain sayt.uz');
|
|
126
|
+
} else if (resolved.source === 'detected' && resolved.confirmed === false) {
|
|
127
|
+
console.error('š« Bekor qilindi (admin tasdiqlamadi).');
|
|
128
|
+
}
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
if (resolved.source === 'detected') console.log(`āļø Tasdiqlangan domen(lar): ${domains.join(', ')}`);
|
|
132
|
+
|
|
133
|
+
const environment = args.production ? 'production' : 'staging';
|
|
134
|
+
const port = Number(args.port) || 80;
|
|
135
|
+
const certDir = typeof args['cert-dir'] === 'string' ? args['cert-dir'] : './certs';
|
|
136
|
+
const accountKeyPath = typeof args['account-key'] === 'string' ? args['account-key'] : './certs/account.pem';
|
|
137
|
+
|
|
138
|
+
const store = new acme.ChallengeStore();
|
|
139
|
+
const server = createChallengeServer(store);
|
|
140
|
+
|
|
141
|
+
// Challenge serverni ishga tushiramiz (80-port ruxsat talab qilishi mumkin).
|
|
142
|
+
try {
|
|
143
|
+
await new Promise((resolve, reject) => {
|
|
144
|
+
server.once('error', reject);
|
|
145
|
+
server.listen(port, resolve);
|
|
146
|
+
});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (err && err.code === 'EACCES') {
|
|
149
|
+
console.error(`ā ${port}-portni ochib bo'lmadi (ruxsat yo'q). Root bilan ishlating (sudo) yoki --port 8080 bering (reverse-proxy orqasida).`);
|
|
150
|
+
} else if (err && err.code === 'EADDRINUSE') {
|
|
151
|
+
console.error(`ā ${port}-port band. Boshqa xizmatni to'xtating yoki --port bilan boshqa port bering.`);
|
|
152
|
+
} else {
|
|
153
|
+
console.error(`ā Server xatosi: ${err && err.message}`);
|
|
154
|
+
}
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(`š Challenge server ${port}-portda. Sertifikat olinyapti [${environment}]: ${domains.join(', ')}`);
|
|
159
|
+
if (environment === 'staging') console.log('ā¹ļø Sinov (staging) rejimi ā cert soxta. Jonli cert uchun --production bering.');
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const result = await acme.obtainCertificate({ domains, email: typeof args.email === 'string' ? args.email : undefined, challengeStore: store, environment, certDir, accountKeyPath });
|
|
163
|
+
console.log('\nā
Sertifikat muvaffaqiyatli olindi!');
|
|
164
|
+
console.log(` Zanjir : ${result.paths.fullchain}`);
|
|
165
|
+
console.log(` Kalit : ${result.paths.privkey}`);
|
|
166
|
+
console.log('\n HTTPS serverda ishlatish:');
|
|
167
|
+
console.log(` https.createServer({ cert: fs.readFileSync('${result.paths.fullchain}'), key: fs.readFileSync('${result.paths.privkey}') }, app)`);
|
|
168
|
+
process.exit(0);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error(`\nā Sertifikat olinmadi: ${err && err.message}`);
|
|
171
|
+
console.error(' Tekshiring: domen DNS shu serverga yo\'naltirilganmi, 80-port internetdan ochiqmi.');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
} finally {
|
|
174
|
+
server.close();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// To'g'ridan-to'g'ri chaqirilgandagina ishga tushadi (require qilinganda emas).
|
|
179
|
+
if (require.main === module) main();
|
|
180
|
+
|
|
181
|
+
module.exports = { main, parseArgs, createChallengeServer, resolveDomains };
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yusufkhon-guard",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Express.js va Node.js uchun yengil, mustaqil (zero-dependency) xavfsizlik middleware: aktiv himoya (blue team firewall), auto IP-ban, security headers, rate-limiting, input sanitization va audit-skaner.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"yusufkhon-guard": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node test.js",
|
|
12
|
+
"demo": "node test.js",
|
|
13
|
+
"test": "jest --runInBand",
|
|
14
|
+
"test:watch": "jest --watch",
|
|
15
|
+
"test:coverage": "jest --coverage --runInBand",
|
|
16
|
+
"scan": "node bin/scan.js",
|
|
17
|
+
"ssl": "node bin/ssl.js",
|
|
18
|
+
"renew": "node bin/renew.js",
|
|
19
|
+
"prepublishOnly": "npm test"
|
|
20
|
+
},
|
|
21
|
+
"jest": {
|
|
22
|
+
"testEnvironment": "node",
|
|
23
|
+
"setupFiles": [
|
|
24
|
+
"<rootDir>/tests/jest.setup.js"
|
|
25
|
+
],
|
|
26
|
+
"testMatch": [
|
|
27
|
+
"**/tests/**/*.test.js"
|
|
28
|
+
],
|
|
29
|
+
"testPathIgnorePatterns": [
|
|
30
|
+
"/node_modules/",
|
|
31
|
+
"/tests/legacy-manual/"
|
|
32
|
+
],
|
|
33
|
+
"testTimeout": 15000
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"express",
|
|
37
|
+
"security",
|
|
38
|
+
"middleware",
|
|
39
|
+
"rate-limit",
|
|
40
|
+
"rate-limiter",
|
|
41
|
+
"helmet",
|
|
42
|
+
"sanitizer",
|
|
43
|
+
"xss",
|
|
44
|
+
"sqli",
|
|
45
|
+
"security-headers",
|
|
46
|
+
"csp",
|
|
47
|
+
"hsts",
|
|
48
|
+
"firewall",
|
|
49
|
+
"waf",
|
|
50
|
+
"blue-team",
|
|
51
|
+
"ip-ban",
|
|
52
|
+
"auto-block",
|
|
53
|
+
"intrusion-detection",
|
|
54
|
+
"ssl-check",
|
|
55
|
+
"security-scanner",
|
|
56
|
+
"yusufkhon-guard"
|
|
57
|
+
],
|
|
58
|
+
"author": "Yusufkhon",
|
|
59
|
+
"license": "MIT",
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=14.0.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"express": ">=4.0.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"express": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"files": [
|
|
72
|
+
"src",
|
|
73
|
+
"bin",
|
|
74
|
+
"README.md"
|
|
75
|
+
],
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"jest": "^30.4.2",
|
|
78
|
+
"supertest": "^7.2.2"
|
|
79
|
+
}
|
|
80
|
+
}
|