1use serde::Deserialize;
7use std::collections::HashMap;
8use std::path::Path;
9
10#[derive(Debug, Clone, Deserialize)]
12pub struct ServiceRule {
13 pub id: String,
15 pub service: String,
17 pub protocol: String,
19 pub common_ports: Vec<u16>,
21 pub patterns: Vec<String>,
23 pub version_pattern: Option<String>,
25 pub security_signals: Vec<String>,
27 pub priority: u8,
29}
30
31#[derive(Debug, Deserialize)]
32struct ServiceRulesFile {
33 #[serde(default)]
34 rule: Vec<ServiceRule>,
35}
36
37pub fn load_from_toml<P: AsRef<Path>>(path: P) -> Result<Vec<ServiceRule>, anyhow::Error> {
55 let body = std::fs::read_to_string(path.as_ref())?;
56 let parsed: ServiceRulesFile = toml::from_str(&body)?;
57 Ok(parsed.rule)
58}
59
60pub fn builtin_plus<P: AsRef<Path>>(custom_path: P) -> Vec<ServiceRule> {
64 let mut out = load_from_toml(custom_path).unwrap_or_default();
65 out.extend(builtin_rules());
66 out
67}
68
69#[derive(Debug, Clone)]
71pub struct ServiceMatch {
72 pub rule_id: String,
74 pub service: String,
76 pub version: Option<String>,
78 pub confidence: f32,
80 pub signals: Vec<String>,
82 pub metadata: HashMap<String, String>,
84}
85
86pub fn builtin_rules() -> Vec<ServiceRule> {
91 let mut out = vec![
92 ServiceRule {
94 id: "http-apache".into(),
95 service: "Apache HTTP Server".into(),
96 protocol: "tcp".into(),
97 common_ports: vec![80, 443, 8080, 8443],
98 patterns: vec!["Apache/".into(), "Server: Apache".into()],
99 version_pattern: Some(r"Apache/(\d+\.\d+\.\d+)".into()),
100 security_signals: vec!["server-version-disclosure".into()],
101 priority: 10,
102 },
103 ServiceRule {
104 id: "http-nginx".into(),
105 service: "nginx".into(),
106 protocol: "tcp".into(),
107 common_ports: vec![80, 443, 8080, 8443],
108 patterns: vec!["nginx/".into(), "Server: nginx".into()],
109 version_pattern: Some(r"nginx/(\d+\.\d+\.\d+)".into()),
110 security_signals: vec!["server-version-disclosure".into()],
111 priority: 10,
112 },
113 ServiceRule {
114 id: "http-iis".into(),
115 service: "Microsoft IIS".into(),
116 protocol: "tcp".into(),
117 common_ports: vec![80, 443, 8080],
118 patterns: vec!["Microsoft-IIS/".into(), "Server: Microsoft-IIS".into()],
119 version_pattern: Some(r"Microsoft-IIS/(\d+\.\d+)".into()),
120 security_signals: vec!["server-version-disclosure".into()],
121 priority: 10,
122 },
123 ServiceRule {
125 id: "ssh-openssh".into(),
126 service: "OpenSSH".into(),
127 protocol: "tcp".into(),
128 common_ports: vec![22, 2222],
129 patterns: vec!["SSH-2.0-OpenSSH".into(), "SSH-1.99-OpenSSH".into()],
130 version_pattern: Some(r"OpenSSH[_\s](\d+\.\d+\S*)".into()),
131 security_signals: vec!["ssh-version-disclosure".into()],
132 priority: 10,
133 },
134 ServiceRule {
135 id: "ssh-dropbear".into(),
136 service: "Dropbear SSH".into(),
137 protocol: "tcp".into(),
138 common_ports: vec![22, 2222],
139 patterns: vec!["dropbear".into()],
140 version_pattern: Some(r"dropbear[_\s](\d+\.\d+)".into()),
141 security_signals: vec!["ssh-version-disclosure".into()],
142 priority: 8,
143 },
144 ServiceRule {
146 id: "ftp-vsftpd".into(),
147 service: "vsftpd".into(),
148 protocol: "tcp".into(),
149 common_ports: vec![21],
150 patterns: vec!["vsftpd".into(), "vsFTPd".into()],
151 version_pattern: Some(r"vsftpd\s+(\d+\.\d+\.\d+)".into()),
152 security_signals: vec!["ftp-version-disclosure".into()],
153 priority: 10,
154 },
155 ServiceRule {
156 id: "ftp-proftpd".into(),
157 service: "ProFTPD".into(),
158 protocol: "tcp".into(),
159 common_ports: vec![21],
160 patterns: vec!["ProFTPD".into()],
161 version_pattern: Some(r"ProFTPD\s+(\d+\.\d+\.\d+\S*)".into()),
162 security_signals: vec!["ftp-version-disclosure".into()],
163 priority: 10,
164 },
165 ServiceRule {
167 id: "mysql".into(),
168 service: "MySQL".into(),
169 protocol: "tcp".into(),
170 common_ports: vec![3306],
171 patterns: vec![
172 "mysql_native_password".into(),
173 "MariaDB".into(),
174 "\x00\x00\x00\x0a".into(), ],
176 version_pattern: Some(r"(\d+\.\d+\.\d+)".into()),
177 security_signals: vec!["database-exposed".into(), "version-disclosure".into()],
178 priority: 10,
179 },
180 ServiceRule {
181 id: "postgresql".into(),
182 service: "PostgreSQL".into(),
183 protocol: "tcp".into(),
184 common_ports: vec![5432],
185 patterns: vec!["PostgreSQL".into()],
186 version_pattern: Some(r"PostgreSQL\s+(\d+\.\d+)".into()),
187 security_signals: vec!["database-exposed".into()],
188 priority: 10,
189 },
190 ServiceRule {
191 id: "redis".into(),
192 service: "Redis".into(),
193 protocol: "tcp".into(),
194 common_ports: vec![6379],
195 patterns: vec![
196 "-ERR".into(),
197 "+PONG".into(),
198 "$".into(),
199 "redis_version:".into(),
200 ],
201 version_pattern: Some(r"redis_version:(\d+\.\d+\.\d+)".into()),
202 security_signals: vec!["redis-unauthenticated".into(), "database-exposed".into()],
203 priority: 10,
204 },
205 ServiceRule {
206 id: "mongodb".into(),
207 service: "MongoDB".into(),
208 protocol: "tcp".into(),
209 common_ports: vec![27017],
210 patterns: vec!["ismaster".into(), "MongoDB".into()],
211 version_pattern: Some(r"(\d+\.\d+\.\d+)".into()),
212 security_signals: vec!["database-exposed".into()],
213 priority: 10,
214 },
215 ServiceRule {
217 id: "smtp".into(),
218 service: "SMTP".into(),
219 protocol: "tcp".into(),
220 common_ports: vec![25, 465, 587],
221 patterns: vec![
222 "220 ".into(),
223 "ESMTP".into(),
224 "Postfix".into(),
225 "Exim".into(),
226 ],
227 version_pattern: Some(r"(?:Postfix|Exim|Sendmail)\s*(\d+\.\d+\S*)".into()),
228 security_signals: vec!["smtp-open-relay-check".into()],
229 priority: 8,
230 },
231 ServiceRule {
233 id: "elasticsearch".into(),
234 service: "Elasticsearch".into(),
235 protocol: "tcp".into(),
236 common_ports: vec![9200, 9300],
237 patterns: vec![
238 "\"cluster_name\"".into(),
239 "\"tagline\" : \"You Know, for Search\"".into(),
240 ],
241 version_pattern: Some(r#""number"\s*:\s*"(\d+\.\d+\.\d+)""#.into()),
242 security_signals: vec![
243 "elasticsearch-unauthenticated".into(),
244 "search-engine-exposed".into(),
245 ],
246 priority: 10,
247 },
248 ServiceRule {
250 id: "rdp".into(),
251 service: "RDP".into(),
252 protocol: "tcp".into(),
253 common_ports: vec![3389],
254 patterns: vec![
255 "\x03\x00".into(), ],
257 version_pattern: None,
258 security_signals: vec!["rdp-exposed".into()],
259 priority: 10,
260 },
261 ];
262 out.extend(extended_rules());
264 out
265}
266
267fn extended_rules() -> Vec<ServiceRule> {
268 let r = |id: &str,
269 service: &str,
270 protocol: &str,
271 ports: Vec<u16>,
272 patterns: Vec<&str>,
273 version: Option<&str>,
274 signals: Vec<&str>,
275 priority: u8|
276 -> ServiceRule {
277 ServiceRule {
278 id: id.into(),
279 service: service.into(),
280 protocol: protocol.into(),
281 common_ports: ports,
282 patterns: patterns.into_iter().map(String::from).collect(),
283 version_pattern: version.map(String::from),
284 security_signals: signals.into_iter().map(String::from).collect(),
285 priority,
286 }
287 };
288 vec![
289 r(
291 "http-haproxy",
292 "HAProxy",
293 "tcp",
294 vec![80, 443, 8080, 8443],
295 vec!["Server: HAProxy", "HAProxy", "X-Haproxy"],
296 Some(r"HAProxy[/\s]+(\d+\.\d+(?:\.\d+)?)"),
297 vec!["server-version-disclosure"],
298 9,
299 ),
300 r(
301 "http-caddy",
302 "Caddy",
303 "tcp",
304 vec![80, 443, 2015, 2019],
305 vec!["Server: Caddy"],
306 Some(r"Caddy/(\d+\.\d+\.\d+)"),
307 vec!["server-version-disclosure"],
308 10,
309 ),
310 r(
311 "http-litespeed",
312 "LiteSpeed",
313 "tcp",
314 vec![80, 443, 7080],
315 vec!["Server: LiteSpeed", "LiteSpeed/"],
316 Some(r"LiteSpeed[/\s](\d+\.\d+\.\d+)"),
317 vec!["server-version-disclosure"],
318 9,
319 ),
320 r(
321 "http-tomcat",
322 "Apache Tomcat",
323 "tcp",
324 vec![8080, 8443, 8005, 8009],
325 vec!["Apache-Coyote/", "Apache Tomcat"],
326 Some(r"Apache Tomcat/?(\d+\.\d+\.\d+)"),
327 vec!["servlet-container-exposed", "server-version-disclosure"],
328 10,
329 ),
330 r(
331 "http-jetty",
332 "Eclipse Jetty",
333 "tcp",
334 vec![8080, 8443],
335 vec!["Server: Jetty", "Jetty("],
336 Some(r"Jetty\(?(\d+\.\d+\.\d+)"),
337 vec!["server-version-disclosure"],
338 9,
339 ),
340 r(
341 "http-tornado",
342 "Tornado",
343 "tcp",
344 vec![8888, 8080],
345 vec!["Server: TornadoServer"],
346 Some(r"TornadoServer/(\d+\.\d+\.\d+)"),
347 vec!["server-version-disclosure"],
348 8,
349 ),
350 r(
351 "http-gunicorn",
352 "Gunicorn",
353 "tcp",
354 vec![8000, 8080],
355 vec!["Server: gunicorn"],
356 Some(r"gunicorn/(\d+\.\d+\.\d+)"),
357 vec!["server-version-disclosure"],
358 8,
359 ),
360 r(
361 "http-werkzeug",
362 "Werkzeug",
363 "tcp",
364 vec![5000, 8000],
365 vec!["Werkzeug/"],
366 Some(r"Werkzeug/(\d+\.\d+\.\d+)"),
367 vec!["debug-server-exposed", "server-version-disclosure"],
368 9,
369 ),
370 r(
371 "http-uvicorn",
372 "Uvicorn",
373 "tcp",
374 vec![8000, 8080],
375 vec!["Server: uvicorn"],
376 Some(r"uvicorn"),
377 vec!["server-version-disclosure"],
378 7,
379 ),
380 r(
381 "http-puma",
382 "Puma",
383 "tcp",
384 vec![3000, 9292],
385 vec!["Server: puma"],
386 Some(r"puma\s+(\d+\.\d+\.\d+)"),
387 vec!["server-version-disclosure"],
388 8,
389 ),
390 r(
391 "http-unicorn",
392 "Unicorn",
393 "tcp",
394 vec![3000, 8080],
395 vec!["Server: Unicorn"],
396 Some(r"Unicorn\s+(\d+\.\d+\.\d+)"),
397 vec!["server-version-disclosure"],
398 7,
399 ),
400 r(
401 "http-node-express",
402 "Express (Node)",
403 "tcp",
404 vec![3000, 8080],
405 vec!["X-Powered-By: Express"],
406 None,
407 vec!["framework-disclosure"],
408 8,
409 ),
410 r(
411 "http-aws-elb",
412 "AWS ELB",
413 "tcp",
414 vec![80, 443],
415 vec!["Server: awselb"],
416 Some(r"awselb/(\d+\.\d+)"),
417 vec![],
418 7,
419 ),
420 r(
421 "http-cloudflare",
422 "Cloudflare",
423 "tcp",
424 vec![80, 443],
425 vec!["Server: cloudflare", "CF-Ray:"],
426 None,
427 vec!["cdn-fronted"],
428 6,
429 ),
430 r(
431 "http-akamai",
432 "Akamai",
433 "tcp",
434 vec![80, 443],
435 vec!["Server: AkamaiGHost", "AkamaiGHost"],
436 None,
437 vec!["cdn-fronted"],
438 6,
439 ),
440 r(
441 "http-fastly",
442 "Fastly",
443 "tcp",
444 vec![80, 443],
445 vec!["X-Served-By: cache-", "Via: 1.1 varnish", "Fastly-Debug"],
446 None,
447 vec!["cdn-fronted"],
448 5,
449 ),
450 r(
451 "http-varnish",
452 "Varnish",
453 "tcp",
454 vec![80, 443, 6081, 6082],
455 vec!["Via: 1.1 varnish", "X-Varnish:"],
456 Some(r"varnish\s+v?(\d+\.\d+\.\d+)"),
457 vec![],
458 7,
459 ),
460 r(
461 "http-traefik",
462 "Traefik",
463 "tcp",
464 vec![80, 443, 8080],
465 vec!["Server: Traefik"],
466 Some(r"Traefik/(\d+\.\d+\.\d+)"),
467 vec!["server-version-disclosure"],
468 8,
469 ),
470 r(
471 "http-envoy",
472 "Envoy",
473 "tcp",
474 vec![80, 443, 9901],
475 vec!["Server: envoy", "X-Envoy-Upstream-Service-Time"],
476 None,
477 vec![],
478 7,
479 ),
480 r(
482 "memcached",
483 "Memcached",
484 "tcp",
485 vec![11211],
486 vec!["VERSION ", "STAT pid"],
487 Some(r"VERSION\s+(\d+\.\d+\.\d+)"),
488 vec!["memcached-unauthenticated", "database-exposed"],
489 10,
490 ),
491 r(
492 "etcd",
493 "etcd",
494 "tcp",
495 vec![2379, 2380],
496 vec!["etcdserver", "/v2/keys", "/version"],
497 Some(r#""etcdserver"\s*:\s*"(\d+\.\d+\.\d+)""#),
498 vec!["etcd-exposed"],
499 10,
500 ),
501 r(
502 "consul",
503 "Consul",
504 "tcp",
505 vec![8500, 8501, 8600],
506 vec!["X-Consul-Index", "Consul Agent"],
507 Some(r"Consul/(\d+\.\d+\.\d+)"),
508 vec!["consul-exposed"],
509 10,
510 ),
511 r(
512 "vault",
513 "HashiCorp Vault",
514 "tcp",
515 vec![8200, 8201],
516 vec!["X-Vault-", "vault/"],
517 Some(r"vault/(\d+\.\d+\.\d+)"),
518 vec!["vault-api-exposed"],
519 10,
520 ),
521 r(
523 "couchdb",
524 "CouchDB",
525 "tcp",
526 vec![5984, 6984],
527 vec!["Welcome to Apache CouchDB", "couchdb"],
528 Some(r#""version"\s*:\s*"(\d+\.\d+\.\d+)""#),
529 vec!["database-exposed"],
530 10,
531 ),
532 r(
533 "cassandra",
534 "Cassandra",
535 "tcp",
536 vec![9042, 7000, 7199],
537 vec!["Apache Cassandra"],
538 Some(r"Cassandra\s+(\d+\.\d+\.\d+)"),
539 vec!["database-exposed"],
540 9,
541 ),
542 r(
543 "clickhouse",
544 "ClickHouse",
545 "tcp",
546 vec![8123, 9000, 9009],
547 vec!["X-ClickHouse-Server-Display-Name"],
548 Some(r"ClickHouse/(\d+\.\d+\.\d+)"),
549 vec!["database-exposed"],
550 9,
551 ),
552 r(
553 "influxdb",
554 "InfluxDB",
555 "tcp",
556 vec![8086, 8088],
557 vec!["X-Influxdb-Version"],
558 Some(r"X-Influxdb-Version:\s*(\d+\.\d+\.\d+)"),
559 vec!["database-exposed"],
560 9,
561 ),
562 r(
564 "rabbitmq",
565 "RabbitMQ",
566 "tcp",
567 vec![5672, 15672, 25672],
568 vec!["RabbitMQ", "AMQP\x00\x00"],
569 Some(r"RabbitMQ[/\s](\d+\.\d+\.\d+)"),
570 vec!["message-broker-exposed"],
571 10,
572 ),
573 r(
574 "rabbitmq-mgmt",
575 "RabbitMQ Management",
576 "tcp",
577 vec![15672],
578 vec!["RabbitMQ Management"],
579 Some(r"RabbitMQ\s+(\d+\.\d+\.\d+)"),
580 vec!["mgmt-ui-exposed"],
581 9,
582 ),
583 r(
584 "kafka",
585 "Kafka",
586 "tcp",
587 vec![9092, 9093, 9094],
588 vec!["org.apache.kafka", "broker.id"],
589 None,
590 vec!["broker-exposed"],
591 9,
592 ),
593 r(
594 "zookeeper",
595 "ZooKeeper",
596 "tcp",
597 vec![2181, 2888, 3888],
598 vec!["zookeeper.version", "ZooKeeper"],
599 Some(r"version=(\d+\.\d+\.\d+)"),
600 vec!["zookeeper-exposed"],
601 10,
602 ),
603 r(
604 "nats",
605 "NATS",
606 "tcp",
607 vec![4222, 6222, 8222],
608 vec!["INFO {", "\"server_name\""],
609 Some(r#""version"\s*:\s*"(\d+\.\d+\.\d+)""#),
610 vec!["nats-exposed"],
611 9,
612 ),
613 r(
614 "mosquitto",
615 "Mosquitto MQTT",
616 "tcp",
617 vec![1883, 8883, 9001],
618 vec!["mosquitto"],
619 Some(r"mosquitto\s+version\s+(\d+\.\d+\.\d+)"),
620 vec!["mqtt-exposed"],
621 9,
622 ),
623 r(
624 "pulsar",
625 "Apache Pulsar",
626 "tcp",
627 vec![6650, 8080, 6651],
628 vec!["pulsar://", "Pulsar"],
629 Some(r"Pulsar\s+(\d+\.\d+\.\d+)"),
630 vec!["broker-exposed"],
631 8,
632 ),
633 r(
635 "docker-daemon",
636 "Docker daemon",
637 "tcp",
638 vec![2375, 2376],
639 vec!["Docker/", "ApiVersion", "\"DockerRootDir\""],
640 Some(r"Docker/(\d+\.\d+\.\d+)"),
641 vec!["docker-api-exposed", "rce-on-unauth"],
642 10,
643 ),
644 r(
645 "kubernetes-api",
646 "Kubernetes API",
647 "tcp",
648 vec![6443, 8443, 10250],
649 vec!["k8s.io", "/api/v1", "\"kind\":\"APIVersions\""],
650 Some(r#""gitVersion"\s*:\s*"v(\d+\.\d+\.\d+)""#),
651 vec!["k8s-api-exposed"],
652 10,
653 ),
654 r(
655 "portainer",
656 "Portainer",
657 "tcp",
658 vec![9000, 9443],
659 vec!["Portainer", "X-Portainer"],
660 Some(r"Portainer-(\d+\.\d+\.\d+)"),
661 vec!["mgmt-ui-exposed"],
662 9,
663 ),
664 r(
665 "rancher",
666 "Rancher",
667 "tcp",
668 vec![8443, 80, 443],
669 vec!["X-Rancher-", "rancher"],
670 Some(r"rancher/(\d+\.\d+\.\d+)"),
671 vec!["mgmt-ui-exposed"],
672 8,
673 ),
674 r(
675 "kubelet",
676 "kubelet",
677 "tcp",
678 vec![10250, 10255, 10256],
679 vec!["/runningpods", "/healthz", "kubelet"],
680 None,
681 vec!["kubelet-exposed", "rce-on-unauth"],
682 10,
683 ),
684 r(
686 "jenkins",
687 "Jenkins",
688 "tcp",
689 vec![8080, 50000, 8443],
690 vec!["X-Jenkins:", "Jenkins-Version"],
691 Some(r"X-Jenkins:\s*(\d+\.\d+(?:\.\d+)?)"),
692 vec!["ci-server-exposed"],
693 10,
694 ),
695 r(
696 "gitlab",
697 "GitLab",
698 "tcp",
699 vec![80, 443, 8081],
700 vec!["GitLab", "X-Gitlab-Meta"],
701 Some(r"GitLab/(\d+\.\d+\.\d+)"),
702 vec!["scm-server-exposed"],
703 9,
704 ),
705 r(
706 "gitea",
707 "Gitea",
708 "tcp",
709 vec![3000, 80, 443],
710 vec!["Gitea", "X-Gitea-"],
711 Some(r"Gitea/(\d+\.\d+\.\d+)"),
712 vec!["scm-server-exposed"],
713 9,
714 ),
715 r(
716 "gerrit",
717 "Gerrit",
718 "tcp",
719 vec![8080, 29418],
720 vec!["Gerrit-", "X-Gerrit-"],
721 Some(r"Gerrit/(\d+\.\d+\.\d+)"),
722 vec!["scm-server-exposed"],
723 8,
724 ),
725 r(
726 "sonarqube",
727 "SonarQube",
728 "tcp",
729 vec![9000, 9001],
730 vec!["SonarQube", "x-sonar-"],
731 Some(r"SonarQube/(\d+\.\d+\.\d+)"),
732 vec!["mgmt-ui-exposed"],
733 8,
734 ),
735 r(
736 "nexus",
737 "Sonatype Nexus",
738 "tcp",
739 vec![8081, 8443],
740 vec!["X-Nexus-", "Server: Nexus"],
741 Some(r"Nexus/(\d+\.\d+\.\d+)"),
742 vec!["registry-exposed"],
743 9,
744 ),
745 r(
746 "artifactory",
747 "JFrog Artifactory",
748 "tcp",
749 vec![8081, 8082],
750 vec!["X-JFrog-Art-", "X-Artifactory-"],
751 Some(r"Artifactory/(\d+\.\d+\.\d+)"),
752 vec!["registry-exposed"],
753 9,
754 ),
755 r(
756 "teamcity",
757 "JetBrains TeamCity",
758 "tcp",
759 vec![8111, 80, 443],
760 vec!["TeamCity", "X-TeamCity-"],
761 Some(r"TeamCity (\d+\.\d+\.\d+)"),
762 vec!["ci-server-exposed"],
763 8,
764 ),
765 r(
766 "concourse",
767 "Concourse CI",
768 "tcp",
769 vec![8080],
770 vec!["X-Concourse-", "concourse-version"],
771 Some(r"v(\d+\.\d+\.\d+)"),
772 vec!["ci-server-exposed"],
773 7,
774 ),
775 r(
776 "drone-ci",
777 "Drone CI",
778 "tcp",
779 vec![80, 443, 8000],
780 vec!["X-Drone", "drone/"],
781 None,
782 vec!["ci-server-exposed"],
783 7,
784 ),
785 r(
786 "argocd",
787 "ArgoCD",
788 "tcp",
789 vec![80, 443, 8080],
790 vec!["X-ArgoCD-", "Argo CD"],
791 None,
792 vec!["mgmt-ui-exposed"],
793 8,
794 ),
795 r(
796 "spinnaker",
797 "Spinnaker",
798 "tcp",
799 vec![8084, 9000],
800 vec!["X-Spinnaker-"],
801 None,
802 vec!["mgmt-ui-exposed"],
803 7,
804 ),
805 r(
806 "harbor",
807 "Harbor",
808 "tcp",
809 vec![80, 443, 4443],
810 vec!["X-Harbor-Version", "Harbor-Api-Version"],
811 Some(r"X-Harbor-Version:\s*v?(\d+\.\d+\.\d+)"),
812 vec!["registry-exposed"],
813 9,
814 ),
815 r(
816 "quay",
817 "Quay",
818 "tcp",
819 vec![80, 443, 8443],
820 vec!["X-Quay-", "quay-version"],
821 None,
822 vec!["registry-exposed"],
823 8,
824 ),
825 r(
827 "bamboo",
828 "Atlassian Bamboo",
829 "tcp",
830 vec![8085, 80, 443],
831 vec!["X-AUSERNAME", "Bamboo"],
832 None,
833 vec!["ci-server-exposed"],
834 7,
835 ),
836 r(
837 "bitbucket",
838 "Atlassian Bitbucket",
839 "tcp",
840 vec![7990, 7999],
841 vec!["X-AUSERNAME", "atl_bitbucket_session"],
842 None,
843 vec!["scm-server-exposed"],
844 7,
845 ),
846 r(
847 "confluence",
848 "Confluence",
849 "tcp",
850 vec![8090, 80, 443],
851 vec!["X-Confluence-Request-Time"],
852 None,
853 vec!["wiki-exposed"],
854 7,
855 ),
856 r(
857 "jira",
858 "Jira",
859 "tcp",
860 vec![8080, 80, 443],
861 vec!["X-AREQUESTID", "JSESSIONID", "atlassian.xsrf.token"],
862 None,
863 vec!["issue-tracker-exposed"],
864 7,
865 ),
866 r(
868 "grafana",
869 "Grafana",
870 "tcp",
871 vec![3000, 80, 443],
872 vec!["grafana_session", "X-Grafana-"],
873 Some(r"Grafana v(\d+\.\d+\.\d+)"),
874 vec!["mgmt-ui-exposed"],
875 9,
876 ),
877 r(
878 "prometheus",
879 "Prometheus",
880 "tcp",
881 vec![9090, 9093, 9100],
882 vec!["prometheus_build_info", "/api/v1/query", "Prometheus"],
883 Some(r#"version="(\d+\.\d+\.\d+)""#),
884 vec!["metrics-exposed"],
885 9,
886 ),
887 r(
888 "alertmanager",
889 "Alertmanager",
890 "tcp",
891 vec![9093, 9094],
892 vec!["Alertmanager"],
893 Some(r"Alertmanager/(\d+\.\d+\.\d+)"),
894 vec!["mgmt-ui-exposed"],
895 7,
896 ),
897 r(
898 "loki",
899 "Grafana Loki",
900 "tcp",
901 vec![3100],
902 vec!["loki", "/loki/api/v1"],
903 Some(r"loki/(\d+\.\d+\.\d+)"),
904 vec!["log-server-exposed"],
905 7,
906 ),
907 r(
908 "tempo",
909 "Grafana Tempo",
910 "tcp",
911 vec![3200, 4317, 9411],
912 vec!["tempo", "/api/traces"],
913 None,
914 vec!["trace-server-exposed"],
915 6,
916 ),
917 r(
918 "kibana",
919 "Kibana",
920 "tcp",
921 vec![5601],
922 vec!["kbn-name", "kibana"],
923 Some(r#""version"\s*:\s*"(\d+\.\d+\.\d+)""#),
924 vec!["mgmt-ui-exposed"],
925 9,
926 ),
927 r(
928 "fluentd",
929 "Fluentd",
930 "tcp",
931 vec![24224, 9880],
932 vec!["fluentd"],
933 Some(r"fluentd[/-](\d+\.\d+\.\d+)"),
934 vec!["log-server-exposed"],
935 7,
936 ),
937 r(
938 "logstash",
939 "Logstash",
940 "tcp",
941 vec![5044, 9600],
942 vec!["logstash"],
943 Some(r"logstash[/-](\d+\.\d+\.\d+)"),
944 vec!["log-server-exposed"],
945 7,
946 ),
947 r(
949 "keycloak",
950 "Keycloak",
951 "tcp",
952 vec![8080, 8443],
953 vec!["X-Keycloak-", "kc_restart", "keycloak"],
954 Some(r"Keycloak/(\d+\.\d+\.\d+)"),
955 vec!["auth-server-exposed"],
956 8,
957 ),
958 r(
959 "openldap",
960 "OpenLDAP",
961 "tcp",
962 vec![389, 636],
963 vec!["LDAP", "objectClass"],
964 None,
965 vec!["directory-exposed"],
966 8,
967 ),
968 r(
969 "freeradius",
970 "FreeRADIUS",
971 "udp",
972 vec![1812, 1813],
973 vec!["FreeRADIUS"],
974 Some(r"FreeRADIUS\s+Version\s+(\d+\.\d+\.\d+)"),
975 vec!["radius-exposed"],
976 7,
977 ),
978 r(
980 "dovecot",
981 "Dovecot",
982 "tcp",
983 vec![110, 143, 993, 995],
984 vec!["Dovecot ready", "+OK Dovecot"],
985 Some(r"Dovecot\s+\(?(\d+\.\d+\.\d+)"),
986 vec!["mail-version-disclosure"],
987 9,
988 ),
989 r(
990 "courier-imap",
991 "Courier IMAP",
992 "tcp",
993 vec![143, 993],
994 vec!["Courier-IMAP"],
995 Some(r"Courier-IMAP\s+(\d+\.\d+\.\d+)"),
996 vec!["mail-version-disclosure"],
997 7,
998 ),
999 r(
1000 "sendmail",
1001 "Sendmail",
1002 "tcp",
1003 vec![25, 587],
1004 vec!["Sendmail"],
1005 Some(r"Sendmail\s+(\d+\.\d+\.\d+)"),
1006 vec!["mail-version-disclosure"],
1007 7,
1008 ),
1009 r(
1011 "filezilla-server",
1012 "FileZilla Server",
1013 "tcp",
1014 vec![21, 990],
1015 vec!["FileZilla Server"],
1016 Some(r"FileZilla Server\s+(\d+\.\d+\.\d+)"),
1017 vec!["ftp-version-disclosure"],
1018 8,
1019 ),
1020 r(
1021 "pure-ftpd",
1022 "Pure-FTPd",
1023 "tcp",
1024 vec![21],
1025 vec!["Pure-FTPd"],
1026 Some(r"Pure-FTPd\s+(\d+\.\d+\S*)"),
1027 vec!["ftp-version-disclosure"],
1028 8,
1029 ),
1030 r(
1031 "samba",
1032 "Samba",
1033 "tcp",
1034 vec![139, 445],
1035 vec!["Samba", "SMB"],
1036 Some(r"Samba\s+(\d+\.\d+\.\d+)"),
1037 vec!["smb-exposed"],
1038 9,
1039 ),
1040 r(
1041 "nfs",
1042 "NFS",
1043 "tcp",
1044 vec![2049],
1045 vec!["NFS_PROGRAM"],
1046 None,
1047 vec!["nfs-exposed"],
1048 8,
1049 ),
1050 r(
1052 "ipmi",
1053 "IPMI",
1054 "udp",
1055 vec![623],
1056 vec!["IPMI"],
1057 None,
1058 vec!["ipmi-exposed", "remote-mgmt-exposed"],
1059 10,
1060 ),
1061 r(
1062 "ilo",
1063 "HP iLO",
1064 "tcp",
1065 vec![80, 443, 17988],
1066 vec!["Server: HP-iLO", "iLO"],
1067 Some(r"HP-iLO/(\d+\.\d+)"),
1068 vec!["remote-mgmt-exposed"],
1069 10,
1070 ),
1071 r(
1072 "idrac",
1073 "Dell iDRAC",
1074 "tcp",
1075 vec![80, 443, 5900],
1076 vec!["iDRAC", "Dell Inc."],
1077 Some(r"iDRAC[/\s](\d+)"),
1078 vec!["remote-mgmt-exposed"],
1079 10,
1080 ),
1081 r(
1082 "supermicro-ipmi",
1083 "Supermicro BMC",
1084 "tcp",
1085 vec![80, 443],
1086 vec!["ATEN International", "Supermicro"],
1087 None,
1088 vec!["remote-mgmt-exposed"],
1089 9,
1090 ),
1091 r(
1093 "ipp-cups",
1094 "IPP/CUPS",
1095 "tcp",
1096 vec![631],
1097 vec!["IPP/", "CUPS"],
1098 Some(r"CUPS/(\d+\.\d+\.\d+)"),
1099 vec!["printer-exposed"],
1100 7,
1101 ),
1102 r(
1103 "jetdirect",
1104 "HP JetDirect",
1105 "tcp",
1106 vec![9100],
1107 vec!["@PJL", "HP JetDirect"],
1108 None,
1109 vec!["printer-exposed"],
1110 7,
1111 ),
1112 r(
1117 "telnet",
1118 "Telnet",
1119 "tcp",
1120 vec![23, 2323],
1121 vec!["login: ", "Password: ", "Welcome to "],
1122 None,
1123 vec!["cleartext-protocol", "telnet-exposed"],
1124 10,
1125 ),
1126 r(
1127 "snmp",
1128 "SNMP",
1129 "udp",
1130 vec![161, 162],
1131 vec!["public", "private"],
1132 None,
1133 vec!["snmp-exposed"],
1134 9,
1135 ),
1136 r(
1137 "rpcbind",
1138 "rpcbind/portmap",
1139 "tcp",
1140 vec![111],
1141 vec!["rpcbind", "program vers"],
1142 None,
1143 vec!["rpc-exposed"],
1144 7,
1145 ),
1146 r(
1148 "powerdns",
1149 "PowerDNS",
1150 "udp",
1151 vec![53, 8081],
1152 vec!["PowerDNS"],
1153 Some(r"PowerDNS\s+Authoritative\s+Server\s+(\d+\.\d+\.\d+)"),
1154 vec!["dns-version-disclosure"],
1155 9,
1156 ),
1157 r(
1158 "nsd",
1159 "NLnet NSD",
1160 "udp",
1161 vec![53],
1162 vec!["NSD "],
1163 Some(r"NSD\s+(\d+\.\d+\.\d+)"),
1164 vec!["dns-version-disclosure"],
1165 8,
1166 ),
1167 r(
1168 "knot-dns",
1169 "Knot DNS",
1170 "udp",
1171 vec![53],
1172 vec!["Knot DNS"],
1173 Some(r"Knot DNS\s+(\d+\.\d+\.\d+)"),
1174 vec!["dns-version-disclosure"],
1175 8,
1176 ),
1177 r(
1178 "unbound",
1179 "Unbound",
1180 "udp",
1181 vec![53],
1182 vec!["unbound"],
1183 Some(r"unbound\s+(\d+\.\d+\.\d+)"),
1184 vec!["dns-version-disclosure"],
1185 7,
1186 ),
1187 r(
1188 "dnsdist",
1189 "dnsdist",
1190 "tcp",
1191 vec![53, 5199],
1192 vec!["dnsdist"],
1193 Some(r"dnsdist\s+(\d+\.\d+\.\d+)"),
1194 vec!["dns-version-disclosure"],
1195 7,
1196 ),
1197 r(
1199 "ntp",
1200 "NTP",
1201 "udp",
1202 vec![123],
1203 vec!["\x1c"],
1204 None,
1205 vec![],
1206 6,
1207 ),
1208 r(
1210 "openvpn",
1211 "OpenVPN",
1212 "udp",
1213 vec![1194, 443],
1214 vec!["P_CONTROL", "OpenVPN"],
1215 None,
1216 vec!["vpn-exposed"],
1217 8,
1218 ),
1219 r(
1220 "wireguard",
1221 "WireGuard",
1222 "udp",
1223 vec![51820, 443],
1224 vec!["WireGuard"],
1225 None,
1226 vec!["vpn-exposed"],
1227 7,
1228 ),
1229 r(
1230 "pptp",
1231 "PPTP",
1232 "tcp",
1233 vec![1723],
1234 vec!["pptp"],
1235 None,
1236 vec!["legacy-vpn-exposed"],
1237 8,
1238 ),
1239 r(
1240 "strongswan",
1241 "strongSwan IPsec",
1242 "udp",
1243 vec![500, 4500],
1244 vec!["strongSwan"],
1245 Some(r"strongSwan\s+(\d+\.\d+\.\d+)"),
1246 vec!["vpn-exposed"],
1247 7,
1248 ),
1249 r(
1250 "xrdp",
1251 "xrdp",
1252 "tcp",
1253 vec![3389],
1254 vec!["xrdp"],
1255 Some(r"xrdp\s+(\d+\.\d+\.\d+)"),
1256 vec!["rdp-exposed"],
1257 8,
1258 ),
1259 r(
1260 "vnc",
1261 "VNC",
1262 "tcp",
1263 vec![5900, 5901, 5902],
1264 vec!["RFB 003.", "RFB"],
1265 Some(r"RFB 003\.(\d+)"),
1266 vec!["vnc-exposed", "remote-desktop-exposed"],
1267 9,
1268 ),
1269 r(
1271 "minecraft",
1272 "Minecraft Server",
1273 "tcp",
1274 vec![25565],
1275 vec![
1276 "Minecraft Server",
1277 "minecraft.net",
1278 "Vanilla 1.",
1279 "Bukkit on",
1280 ],
1281 None,
1282 vec!["game-server-exposed"],
1283 5,
1284 ),
1285 r(
1286 "ts3",
1287 "TeamSpeak 3",
1288 "udp",
1289 vec![9987, 10011, 30033],
1290 vec!["TS3", "TeaSpeak"],
1291 None,
1292 vec![],
1293 5,
1294 ),
1295 r(
1296 "plex",
1297 "Plex Media Server",
1298 "tcp",
1299 vec![32400],
1300 vec!["X-Plex-", "Plex Media Server"],
1301 Some(r"X-Plex-Version:\s*(\d+\.\d+\.\d+)"),
1302 vec!["media-server-exposed"],
1303 6,
1304 ),
1305 r(
1306 "jellyfin",
1307 "Jellyfin",
1308 "tcp",
1309 vec![8096, 8920],
1310 vec!["X-Application", "Jellyfin"],
1311 Some(r"Jellyfin/(\d+\.\d+\.\d+)"),
1312 vec!["media-server-exposed"],
1313 6,
1314 ),
1315 r(
1322 "modbus",
1323 "Modbus TCP",
1324 "tcp",
1325 vec![502],
1326 vec!["\u{0}\u{0}\u{0}\u{0}\u{0}\u{6}\u{1}"],
1327 None,
1328 vec!["ics-exposed", "modbus-exposed"],
1329 10,
1330 ),
1331 r(
1332 "siemens-s7",
1333 "Siemens S7",
1334 "tcp",
1335 vec![102],
1336 vec!["S7", "ISO-on-TCP"],
1337 None,
1338 vec!["ics-exposed"],
1339 10,
1340 ),
1341 r(
1343 "iscsi",
1344 "iSCSI",
1345 "tcp",
1346 vec![3260],
1347 vec!["iSCSI", "iqn."],
1348 None,
1349 vec!["storage-exposed"],
1350 8,
1351 ),
1352 r(
1353 "minio",
1354 "MinIO",
1355 "tcp",
1356 vec![9000, 9001],
1357 vec!["X-Amz-Bucket-Region", "MinIO", "MinIO Console"],
1358 Some(r"MinIO/(\S+)"),
1359 vec!["object-storage-exposed"],
1360 9,
1361 ),
1362 r(
1364 "solr",
1365 "Apache Solr",
1366 "tcp",
1367 vec![8983, 7574],
1368 vec!["Solr", "X-Solr-"],
1369 Some(r"Solr/(\d+\.\d+\.\d+)"),
1370 vec!["search-engine-exposed"],
1371 9,
1372 ),
1373 r(
1374 "opensearch",
1375 "OpenSearch",
1376 "tcp",
1377 vec![9200],
1378 vec!["opensearch", "OpenSearch"],
1379 Some(r#""number"\s*:\s*"(\d+\.\d+\.\d+)""#),
1380 vec!["search-engine-exposed"],
1381 9,
1382 ),
1383 r(
1384 "milvus",
1385 "Milvus",
1386 "tcp",
1387 vec![19530, 9091],
1388 vec!["milvus"],
1389 Some(r"milvus[/\s](\d+\.\d+\.\d+)"),
1390 vec!["vector-db-exposed"],
1391 7,
1392 ),
1393 r(
1394 "qdrant",
1395 "Qdrant",
1396 "tcp",
1397 vec![6333, 6334],
1398 vec!["qdrant", "Qdrant"],
1399 Some(r"Qdrant/(\d+\.\d+\.\d+)"),
1400 vec!["vector-db-exposed"],
1401 7,
1402 ),
1403 r(
1404 "weaviate",
1405 "Weaviate",
1406 "tcp",
1407 vec![8080, 50051],
1408 vec!["weaviate"],
1409 Some(r"weaviate[/\s](\d+\.\d+\.\d+)"),
1410 vec!["vector-db-exposed"],
1411 7,
1412 ),
1413 r(
1415 "smb1",
1416 "SMBv1",
1417 "tcp",
1418 vec![139, 445],
1419 vec!["SMB 1.0"],
1420 None,
1421 vec!["smbv1-exposed", "deprecated-protocol"],
1422 10,
1423 ),
1424 r(
1425 "rsync",
1426 "rsync",
1427 "tcp",
1428 vec![873],
1429 vec!["@RSYNCD:"],
1430 Some(r"@RSYNCD:\s*(\d+\.\d+)"),
1431 vec!["rsync-exposed"],
1432 8,
1433 ),
1434 ]
1435}
1436
1437#[cfg(test)]
1438mod tests {
1439 use super::*;
1440
1441 #[test]
1442 fn builtin_rules_not_empty() {
1443 let rules = builtin_rules();
1444 assert!(rules.len() > 10, "should have substantial rule coverage");
1445 }
1446
1447 #[test]
1448 fn all_rules_have_unique_ids() {
1449 let rules = builtin_rules();
1450 let mut seen = std::collections::HashSet::new();
1451 for rule in &rules {
1452 assert!(seen.insert(&rule.id), "duplicate rule id: {}", rule.id);
1453 }
1454 }
1455
1456 #[test]
1457 fn all_rules_have_patterns() {
1458 for rule in builtin_rules() {
1459 assert!(
1460 !rule.patterns.is_empty(),
1461 "rule {} has no patterns",
1462 rule.id
1463 );
1464 }
1465 }
1466}