decnet 1.0.0__py3-none-any.whl
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.
- decnet/__init__.py +13 -0
- decnet/agent/__init__.py +8 -0
- decnet/agent/app.py +366 -0
- decnet/agent/executor.py +317 -0
- decnet/agent/heartbeat.py +210 -0
- decnet/agent/server.py +71 -0
- decnet/agent/topology_ops.py +220 -0
- decnet/agent/topology_store.py +215 -0
- decnet/archetypes.py +171 -0
- decnet/artifacts/__init__.py +1 -0
- decnet/artifacts/paths.py +86 -0
- decnet/artifacts/shards.py +129 -0
- decnet/asn/__init__.py +93 -0
- decnet/asn/base.py +34 -0
- decnet/asn/factory.py +40 -0
- decnet/asn/iptoasn/__init__.py +10 -0
- decnet/asn/iptoasn/fetch.py +64 -0
- decnet/asn/iptoasn/parse.py +79 -0
- decnet/asn/iptoasn/provider.py +84 -0
- decnet/asn/lookup.py +143 -0
- decnet/asn/paths.py +19 -0
- decnet/bus/__init__.py +19 -0
- decnet/bus/app.py +93 -0
- decnet/bus/base.py +206 -0
- decnet/bus/factory.py +86 -0
- decnet/bus/fake.py +184 -0
- decnet/bus/protocol.py +145 -0
- decnet/bus/publish.py +212 -0
- decnet/bus/topics.py +653 -0
- decnet/bus/unix_client.py +269 -0
- decnet/bus/unix_server.py +310 -0
- decnet/bus/worker.py +122 -0
- decnet/canary/__init__.py +38 -0
- decnet/canary/_obfuscate_helper.js +19 -0
- decnet/canary/base.py +152 -0
- decnet/canary/cultivator.py +193 -0
- decnet/canary/dns_server.py +208 -0
- decnet/canary/factory.py +154 -0
- decnet/canary/fingerprint_payload.js +292 -0
- decnet/canary/generators/__init__.py +8 -0
- decnet/canary/generators/aws_creds.py +87 -0
- decnet/canary/generators/env_file.py +57 -0
- decnet/canary/generators/fingerprint_html.py +141 -0
- decnet/canary/generators/fingerprint_svg.py +89 -0
- decnet/canary/generators/git_config.py +54 -0
- decnet/canary/generators/honeydoc.py +62 -0
- decnet/canary/generators/honeydoc_docx.py +134 -0
- decnet/canary/generators/honeydoc_pdf.py +128 -0
- decnet/canary/generators/mysql_dump.py +191 -0
- decnet/canary/generators/ssh_key.py +69 -0
- decnet/canary/instrumenters/__init__.py +5 -0
- decnet/canary/instrumenters/docx.py +148 -0
- decnet/canary/instrumenters/html.py +46 -0
- decnet/canary/instrumenters/image.py +73 -0
- decnet/canary/instrumenters/passthrough.py +38 -0
- decnet/canary/instrumenters/pdf.py +77 -0
- decnet/canary/instrumenters/plain.py +80 -0
- decnet/canary/instrumenters/xlsx.py +96 -0
- decnet/canary/obfuscator.py +178 -0
- decnet/canary/package.json +10 -0
- decnet/canary/paths.py +87 -0
- decnet/canary/planter.py +307 -0
- decnet/canary/storage.py +90 -0
- decnet/canary/worker.py +421 -0
- decnet/cli/__init__.py +92 -0
- decnet/cli/agent.py +65 -0
- decnet/cli/api.py +54 -0
- decnet/cli/bus.py +46 -0
- decnet/cli/canary.py +104 -0
- decnet/cli/db.py +142 -0
- decnet/cli/deploy.py +308 -0
- decnet/cli/forwarder.py +75 -0
- decnet/cli/gating.py +78 -0
- decnet/cli/geoip.py +60 -0
- decnet/cli/init.py +864 -0
- decnet/cli/inventory.py +53 -0
- decnet/cli/lifecycle.py +148 -0
- decnet/cli/listener.py +58 -0
- decnet/cli/orchestrator.py +56 -0
- decnet/cli/profiler.py +35 -0
- decnet/cli/realism.py +112 -0
- decnet/cli/reconciler.py +63 -0
- decnet/cli/sniffer.py +32 -0
- decnet/cli/swarm.py +347 -0
- decnet/cli/swarmctl.py +142 -0
- decnet/cli/topology.py +349 -0
- decnet/cli/ttp.py +310 -0
- decnet/cli/updater.py +47 -0
- decnet/cli/utils.py +243 -0
- decnet/cli/web.py +154 -0
- decnet/cli/webhook.py +36 -0
- decnet/cli/workers.py +369 -0
- decnet/clustering/__init__.py +2 -0
- decnet/clustering/base.py +84 -0
- decnet/clustering/campaign/__init__.py +6 -0
- decnet/clustering/campaign/base.py +67 -0
- decnet/clustering/campaign/factory.py +32 -0
- decnet/clustering/campaign/impl/__init__.py +1 -0
- decnet/clustering/campaign/impl/connected_components.py +386 -0
- decnet/clustering/campaign/impl/similarity.py +479 -0
- decnet/clustering/campaign/worker.py +192 -0
- decnet/clustering/factory.py +47 -0
- decnet/clustering/impl/__init__.py +7 -0
- decnet/clustering/impl/connected_components.py +421 -0
- decnet/clustering/impl/similarity.py +314 -0
- decnet/clustering/ukc.py +203 -0
- decnet/clustering/worker.py +181 -0
- decnet/collector/__init__.py +14 -0
- decnet/collector/worker.py +913 -0
- decnet/composer.py +128 -0
- decnet/config.py +153 -0
- decnet/config_ini.py +211 -0
- decnet/correlation/__init__.py +14 -0
- decnet/correlation/attribution/__init__.py +22 -0
- decnet/correlation/attribution/_thresholds.py +63 -0
- decnet/correlation/attribution/aggregate.py +419 -0
- decnet/correlation/attribution_worker.py +395 -0
- decnet/correlation/engine.py +355 -0
- decnet/correlation/event_kinds.py +114 -0
- decnet/correlation/fingerprint_rotation.py +161 -0
- decnet/correlation/graph.py +148 -0
- decnet/correlation/parser.py +203 -0
- decnet/correlation/reuse_worker.py +154 -0
- decnet/custom_service.py +42 -0
- decnet/decky_io/__init__.py +40 -0
- decnet/decky_io/resolve.py +73 -0
- decnet/decky_io/write.py +125 -0
- decnet/distros.py +147 -0
- decnet/engine/__init__.py +16 -0
- decnet/engine/deployer.py +1230 -0
- decnet/engine/reaper.py +172 -0
- decnet/engine/services_live.py +693 -0
- decnet/env.py +330 -0
- decnet/fleet/__init__.py +186 -0
- decnet/fleet/reconciler.py +230 -0
- decnet/fleet/reconciler_worker.py +87 -0
- decnet/geoip/__init__.py +96 -0
- decnet/geoip/base.py +35 -0
- decnet/geoip/factory.py +48 -0
- decnet/geoip/lookup.py +122 -0
- decnet/geoip/paths.py +20 -0
- decnet/geoip/ptr.py +88 -0
- decnet/geoip/rir/__init__.py +10 -0
- decnet/geoip/rir/fetch.py +63 -0
- decnet/geoip/rir/parse.py +71 -0
- decnet/geoip/rir/provider.py +75 -0
- decnet/ini_loader.py +181 -0
- decnet/intel/__init__.py +11 -0
- decnet/intel/abuseipdb.py +117 -0
- decnet/intel/base.py +111 -0
- decnet/intel/factory.py +108 -0
- decnet/intel/feodo.py +114 -0
- decnet/intel/greynoise.py +122 -0
- decnet/intel/mal_hash.py +196 -0
- decnet/intel/threatfox.py +122 -0
- decnet/intel/worker.py +276 -0
- decnet/lifecycle/__init__.py +16 -0
- decnet/lifecycle/events.py +44 -0
- decnet/lifecycle/runner.py +97 -0
- decnet/lifecycle/strategies.py +377 -0
- decnet/logging/__init__.py +93 -0
- decnet/logging/file_handler.py +73 -0
- decnet/logging/forwarder.py +40 -0
- decnet/logging/inode_aware_handler.py +61 -0
- decnet/logging/syslog_formatter.py +87 -0
- decnet/models.py +124 -0
- decnet/mutator/__init__.py +4 -0
- decnet/mutator/engine.py +523 -0
- decnet/mutator/events.py +109 -0
- decnet/mutator/ops.py +1109 -0
- decnet/net/__init__.py +8 -0
- decnet/net/http.py +60 -0
- decnet/network.py +514 -0
- decnet/orchestrator/__init__.py +10 -0
- decnet/orchestrator/drivers/__init__.py +75 -0
- decnet/orchestrator/drivers/base.py +93 -0
- decnet/orchestrator/drivers/email.py +291 -0
- decnet/orchestrator/drivers/smtp_relay.py +81 -0
- decnet/orchestrator/drivers/ssh.py +271 -0
- decnet/orchestrator/emailgen/__init__.py +21 -0
- decnet/orchestrator/emailgen/events.py +50 -0
- decnet/orchestrator/emailgen/scheduler.py +256 -0
- decnet/orchestrator/emailgen/threads.py +76 -0
- decnet/orchestrator/events.py +69 -0
- decnet/orchestrator/scheduler.py +346 -0
- decnet/orchestrator/worker.py +629 -0
- decnet/os_fingerprint.py +114 -0
- decnet/privdrop.py +68 -0
- decnet/prober/__init__.py +14 -0
- decnet/prober/base.py +89 -0
- decnet/prober/hassh.py +253 -0
- decnet/prober/icmp6_error.py +314 -0
- decnet/prober/icmp_error.py +305 -0
- decnet/prober/ipv6_leak.py +131 -0
- decnet/prober/jarm.py +507 -0
- decnet/prober/osfp/__init__.py +28 -0
- decnet/prober/osfp/base.py +60 -0
- decnet/prober/osfp/factory.py +88 -0
- decnet/prober/osfp/p0f/__init__.py +7 -0
- decnet/prober/osfp/p0f/format.py +244 -0
- decnet/prober/osfp/p0f/provider.py +110 -0
- decnet/prober/osfp/p0f/signature.py +288 -0
- decnet/prober/probes/__init__.py +9 -0
- decnet/prober/probes/hassh.py +44 -0
- decnet/prober/probes/icmp6_error_probe.py +79 -0
- decnet/prober/probes/icmp_error_probe.py +73 -0
- decnet/prober/probes/ipv6_leak_probe.py +63 -0
- decnet/prober/probes/jarm.py +35 -0
- decnet/prober/probes/tcpfp.py +53 -0
- decnet/prober/probes/tlscert_probe.py +53 -0
- decnet/prober/tcpfp.py +240 -0
- decnet/prober/tlscert.py +132 -0
- decnet/prober/worker.py +499 -0
- decnet/profiler/__init__.py +6 -0
- decnet/profiler/behave_shell/__init__.py +26 -0
- decnet/profiler/behave_shell/_ctx.py +574 -0
- decnet/profiler/behave_shell/_features/__init__.py +107 -0
- decnet/profiler/behave_shell/_features/_emit.py +33 -0
- decnet/profiler/behave_shell/_features/cognitive.py +594 -0
- decnet/profiler/behave_shell/_features/emotional_valence.py +224 -0
- decnet/profiler/behave_shell/_features/environmental.py +353 -0
- decnet/profiler/behave_shell/_features/motor.py +489 -0
- decnet/profiler/behave_shell/_features/operational.py +219 -0
- decnet/profiler/behave_shell/_features/temporal.py +238 -0
- decnet/profiler/behave_shell/_handler.py +236 -0
- decnet/profiler/behave_shell/_intent.py +116 -0
- decnet/profiler/behave_shell/_parse.py +273 -0
- decnet/profiler/behave_shell/_thresholds.py +428 -0
- decnet/profiler/behave_shell/extract.py +52 -0
- decnet/profiler/behavioral.py +108 -0
- decnet/profiler/classify.py +58 -0
- decnet/profiler/fingerprint.py +297 -0
- decnet/profiler/identity_rollup.py +114 -0
- decnet/profiler/phases.py +69 -0
- decnet/profiler/timing.py +83 -0
- decnet/profiler/tools.py +180 -0
- decnet/profiler/worker.py +614 -0
- decnet/realism/__init__.py +28 -0
- decnet/realism/bodies.py +430 -0
- decnet/realism/diurnal.py +153 -0
- decnet/realism/llm/__init__.py +18 -0
- decnet/realism/llm/base.py +48 -0
- decnet/realism/llm/circuit.py +100 -0
- decnet/realism/llm/config.py +130 -0
- decnet/realism/llm/factory.py +61 -0
- decnet/realism/llm/impl/__init__.py +7 -0
- decnet/realism/llm/impl/fake.py +51 -0
- decnet/realism/llm/impl/ollama.py +175 -0
- decnet/realism/naming.py +187 -0
- decnet/realism/personas.py +144 -0
- decnet/realism/personas_pool.py +154 -0
- decnet/realism/planner.py +390 -0
- decnet/realism/prompts/__init__.py +10 -0
- decnet/realism/prompts/_style.py +40 -0
- decnet/realism/prompts/email.py +155 -0
- decnet/realism/prompts/filebody.py +92 -0
- decnet/realism/taxonomy.py +153 -0
- decnet/rpki/__init__.py +44 -0
- decnet/rpki/base.py +39 -0
- decnet/rpki/cache.py +74 -0
- decnet/rpki/factory.py +40 -0
- decnet/rpki/paths.py +19 -0
- decnet/rpki/ripestat/__init__.py +1 -0
- decnet/rpki/ripestat/validator.py +90 -0
- decnet/services/__init__.py +1 -0
- decnet/services/base.py +178 -0
- decnet/services/conpot.py +37 -0
- decnet/services/dns.py +127 -0
- decnet/services/docker_api.py +26 -0
- decnet/services/elasticsearch.py +29 -0
- decnet/services/ftp.py +28 -0
- decnet/services/http.py +99 -0
- decnet/services/https.py +125 -0
- decnet/services/imap.py +64 -0
- decnet/services/k8s.py +26 -0
- decnet/services/ldap.py +27 -0
- decnet/services/llmnr.py +33 -0
- decnet/services/mongodb.py +26 -0
- decnet/services/mqtt.py +26 -0
- decnet/services/mssql.py +26 -0
- decnet/services/mysql.py +43 -0
- decnet/services/pop3.py +52 -0
- decnet/services/postgres.py +26 -0
- decnet/services/rdp.py +45 -0
- decnet/services/redis.py +52 -0
- decnet/services/registry.py +52 -0
- decnet/services/sip.py +26 -0
- decnet/services/smb.py +29 -0
- decnet/services/smtp.py +66 -0
- decnet/services/smtp_relay.py +110 -0
- decnet/services/sniffer.py +43 -0
- decnet/services/snmp.py +26 -0
- decnet/services/ssh.py +121 -0
- decnet/services/telnet.py +117 -0
- decnet/services/tftp.py +26 -0
- decnet/services/vnc.py +26 -0
- decnet/sniffer/__init__.py +12 -0
- decnet/sniffer/fingerprint.py +1649 -0
- decnet/sniffer/p0f.py +239 -0
- decnet/sniffer/seq_class.py +64 -0
- decnet/sniffer/syslog.py +72 -0
- decnet/sniffer/worker.py +290 -0
- decnet/swarm/__init__.py +8 -0
- decnet/swarm/bundle_builder.py +210 -0
- decnet/swarm/client.py +342 -0
- decnet/swarm/log_forwarder.py +328 -0
- decnet/swarm/log_listener.py +220 -0
- decnet/swarm/pki.py +324 -0
- decnet/swarm/tar_tree.py +127 -0
- decnet/swarm/updater_client.py +205 -0
- decnet/tarpit/__init__.py +4 -0
- decnet/tarpit/worker.py +209 -0
- decnet/telemetry.py +309 -0
- decnet/templates/_caddy_modules/decnetfp/go.mod +130 -0
- decnet/templates/_caddy_modules/decnetfp/go.sum +491 -0
- decnet/templates/_caddy_modules/decnetfp/h1_test.go +150 -0
- decnet/templates/_caddy_modules/decnetfp/h2_test.go +206 -0
- decnet/templates/_caddy_modules/decnetfp/h3_tracer_test.go +170 -0
- decnet/templates/_caddy_modules/decnetfp/h3conn.go +117 -0
- decnet/templates/_caddy_modules/decnetfp/module.go +674 -0
- decnet/templates/_shared/auth-helper/auth-helper.c +190 -0
- decnet/templates/_shared/ntlmssp.py +133 -0
- decnet/templates/_shared/sessrec/Makefile +28 -0
- decnet/templates/_shared/sessrec/sessrec.c +564 -0
- decnet/templates/conpot/Dockerfile +28 -0
- decnet/templates/conpot/entrypoint.py +148 -0
- decnet/templates/conpot/instance_seed.py +121 -0
- decnet/templates/conpot/syslog_bridge.py +403 -0
- decnet/templates/cowrie/Dockerfile +22 -0
- decnet/templates/cowrie/cowrie.cfg.j2 +30 -0
- decnet/templates/cowrie/entrypoint.sh +34 -0
- decnet/templates/cowrie/honeyfs/etc/group +62 -0
- decnet/templates/cowrie/honeyfs/etc/hostname +1 -0
- decnet/templates/cowrie/honeyfs/etc/hosts +5 -0
- decnet/templates/cowrie/honeyfs/etc/issue +2 -0
- decnet/templates/cowrie/honeyfs/etc/issue.net +1 -0
- decnet/templates/cowrie/honeyfs/etc/motd +26 -0
- decnet/templates/cowrie/honeyfs/etc/os-release +12 -0
- decnet/templates/cowrie/honeyfs/etc/passwd +36 -0
- decnet/templates/cowrie/honeyfs/etc/resolv.conf +4 -0
- decnet/templates/cowrie/honeyfs/etc/shadow +36 -0
- decnet/templates/cowrie/honeyfs/var/log/auth.log +12 -0
- decnet/templates/dns/Dockerfile +26 -0
- decnet/templates/dns/entrypoint.sh +4 -0
- decnet/templates/dns/instance_seed.py +121 -0
- decnet/templates/dns/server.py +977 -0
- decnet/templates/dns/syslog_bridge.py +402 -0
- decnet/templates/docker_api/Dockerfile +26 -0
- decnet/templates/docker_api/entrypoint.sh +4 -0
- decnet/templates/docker_api/instance_seed.py +121 -0
- decnet/templates/docker_api/server.py +125 -0
- decnet/templates/docker_api/syslog_bridge.py +403 -0
- decnet/templates/elasticsearch/Dockerfile +24 -0
- decnet/templates/elasticsearch/entrypoint.sh +4 -0
- decnet/templates/elasticsearch/instance_seed.py +121 -0
- decnet/templates/elasticsearch/server.py +196 -0
- decnet/templates/elasticsearch/syslog_bridge.py +403 -0
- decnet/templates/ftp/Dockerfile +27 -0
- decnet/templates/ftp/entrypoint.sh +4 -0
- decnet/templates/ftp/instance_seed.py +121 -0
- decnet/templates/ftp/server.py +150 -0
- decnet/templates/ftp/syslog_bridge.py +403 -0
- decnet/templates/http/Dockerfile +44 -0
- decnet/templates/http/_caddy_modules/decnetfp/go.mod +130 -0
- decnet/templates/http/_caddy_modules/decnetfp/go.sum +491 -0
- decnet/templates/http/_caddy_modules/decnetfp/h1_test.go +150 -0
- decnet/templates/http/_caddy_modules/decnetfp/h2_test.go +206 -0
- decnet/templates/http/_caddy_modules/decnetfp/h3_tracer_test.go +170 -0
- decnet/templates/http/_caddy_modules/decnetfp/h3conn.go +117 -0
- decnet/templates/http/_caddy_modules/decnetfp/module.go +674 -0
- decnet/templates/http/entrypoint.sh +56 -0
- decnet/templates/http/instance_seed.py +121 -0
- decnet/templates/http/server.py +172 -0
- decnet/templates/http/syslog_bridge.py +403 -0
- decnet/templates/https/Dockerfile +45 -0
- decnet/templates/https/_caddy_modules/decnetfp/go.mod +130 -0
- decnet/templates/https/_caddy_modules/decnetfp/go.sum +491 -0
- decnet/templates/https/_caddy_modules/decnetfp/h1_test.go +150 -0
- decnet/templates/https/_caddy_modules/decnetfp/h2_test.go +206 -0
- decnet/templates/https/_caddy_modules/decnetfp/h3_tracer_test.go +170 -0
- decnet/templates/https/_caddy_modules/decnetfp/h3conn.go +117 -0
- decnet/templates/https/_caddy_modules/decnetfp/module.go +674 -0
- decnet/templates/https/entrypoint.sh +89 -0
- decnet/templates/https/instance_seed.py +121 -0
- decnet/templates/https/server.py +170 -0
- decnet/templates/https/syslog_bridge.py +403 -0
- decnet/templates/imap/Dockerfile +23 -0
- decnet/templates/imap/entrypoint.sh +4 -0
- decnet/templates/imap/instance_seed.py +121 -0
- decnet/templates/imap/server.py +772 -0
- decnet/templates/imap/syslog_bridge.py +403 -0
- decnet/templates/instance_seed.py +121 -0
- decnet/templates/k8s/Dockerfile +26 -0
- decnet/templates/k8s/entrypoint.sh +4 -0
- decnet/templates/k8s/instance_seed.py +121 -0
- decnet/templates/k8s/server.py +136 -0
- decnet/templates/k8s/syslog_bridge.py +403 -0
- decnet/templates/ldap/Dockerfile +24 -0
- decnet/templates/ldap/entrypoint.sh +4 -0
- decnet/templates/ldap/instance_seed.py +121 -0
- decnet/templates/ldap/server.py +215 -0
- decnet/templates/ldap/syslog_bridge.py +403 -0
- decnet/templates/llmnr/Dockerfile +24 -0
- decnet/templates/llmnr/entrypoint.sh +4 -0
- decnet/templates/llmnr/instance_seed.py +121 -0
- decnet/templates/llmnr/server.py +114 -0
- decnet/templates/llmnr/syslog_bridge.py +403 -0
- decnet/templates/mongodb/Dockerfile +24 -0
- decnet/templates/mongodb/entrypoint.sh +4 -0
- decnet/templates/mongodb/instance_seed.py +121 -0
- decnet/templates/mongodb/server.py +362 -0
- decnet/templates/mongodb/syslog_bridge.py +403 -0
- decnet/templates/mqtt/Dockerfile +24 -0
- decnet/templates/mqtt/entrypoint.sh +4 -0
- decnet/templates/mqtt/instance_seed.py +121 -0
- decnet/templates/mqtt/server.py +347 -0
- decnet/templates/mqtt/syslog_bridge.py +403 -0
- decnet/templates/mssql/Dockerfile +24 -0
- decnet/templates/mssql/entrypoint.sh +4 -0
- decnet/templates/mssql/instance_seed.py +121 -0
- decnet/templates/mssql/server.py +226 -0
- decnet/templates/mssql/syslog_bridge.py +403 -0
- decnet/templates/mysql/Dockerfile +24 -0
- decnet/templates/mysql/entrypoint.sh +4 -0
- decnet/templates/mysql/instance_seed.py +121 -0
- decnet/templates/mysql/server.py +174 -0
- decnet/templates/mysql/syslog_bridge.py +403 -0
- decnet/templates/pop3/Dockerfile +23 -0
- decnet/templates/pop3/entrypoint.sh +4 -0
- decnet/templates/pop3/instance_seed.py +121 -0
- decnet/templates/pop3/server.py +576 -0
- decnet/templates/pop3/syslog_bridge.py +403 -0
- decnet/templates/postgres/Dockerfile +24 -0
- decnet/templates/postgres/entrypoint.sh +4 -0
- decnet/templates/postgres/instance_seed.py +121 -0
- decnet/templates/postgres/server.py +191 -0
- decnet/templates/postgres/syslog_bridge.py +403 -0
- decnet/templates/rdp/Dockerfile +27 -0
- decnet/templates/rdp/entrypoint.sh +21 -0
- decnet/templates/rdp/instance_seed.py +121 -0
- decnet/templates/rdp/ntlmssp.py +133 -0
- decnet/templates/rdp/server.py +401 -0
- decnet/templates/rdp/syslog_bridge.py +403 -0
- decnet/templates/redis/Dockerfile +24 -0
- decnet/templates/redis/entrypoint.sh +4 -0
- decnet/templates/redis/instance_seed.py +121 -0
- decnet/templates/redis/server.py +339 -0
- decnet/templates/redis/syslog_bridge.py +403 -0
- decnet/templates/sip/Dockerfile +24 -0
- decnet/templates/sip/entrypoint.sh +4 -0
- decnet/templates/sip/instance_seed.py +121 -0
- decnet/templates/sip/server.py +155 -0
- decnet/templates/sip/syslog_bridge.py +403 -0
- decnet/templates/smb/Dockerfile +25 -0
- decnet/templates/smb/entrypoint.sh +4 -0
- decnet/templates/smb/instance_seed.py +121 -0
- decnet/templates/smb/ntlmssp.py +133 -0
- decnet/templates/smb/server.py +298 -0
- decnet/templates/smb/syslog_bridge.py +403 -0
- decnet/templates/smtp/Dockerfile +26 -0
- decnet/templates/smtp/entrypoint.sh +13 -0
- decnet/templates/smtp/instance_seed.py +121 -0
- decnet/templates/smtp/server.py +838 -0
- decnet/templates/smtp/syslog_bridge.py +403 -0
- decnet/templates/sniffer/Dockerfile +12 -0
- decnet/templates/sniffer/__pycache__/server.cpython-311.pyc +0 -0
- decnet/templates/sniffer/server.py +1053 -0
- decnet/templates/sniffer/syslog_bridge.py +403 -0
- decnet/templates/snmp/Dockerfile +23 -0
- decnet/templates/snmp/entrypoint.sh +4 -0
- decnet/templates/snmp/instance_seed.py +121 -0
- decnet/templates/snmp/server.py +273 -0
- decnet/templates/snmp/syslog_bridge.py +403 -0
- decnet/templates/ssh/Dockerfile +173 -0
- decnet/templates/ssh/_build_stealth.py +90 -0
- decnet/templates/ssh/argv_zap.c +65 -0
- decnet/templates/ssh/auth-helper/auth-helper.c +190 -0
- decnet/templates/ssh/capture.sh +266 -0
- decnet/templates/ssh/emit_capture.py +85 -0
- decnet/templates/ssh/entrypoint.sh +111 -0
- decnet/templates/ssh/instance_seed.py +121 -0
- decnet/templates/ssh/sessrec/Makefile +28 -0
- decnet/templates/ssh/sessrec/sessrec.c +564 -0
- decnet/templates/ssh/syslog_bridge.py +403 -0
- decnet/templates/syslog_bridge.py +403 -0
- decnet/templates/telnet/Dockerfile +109 -0
- decnet/templates/telnet/auth-helper/auth-helper.c +190 -0
- decnet/templates/telnet/entrypoint.sh +68 -0
- decnet/templates/telnet/instance_seed.py +121 -0
- decnet/templates/telnet/sessrec/Makefile +28 -0
- decnet/templates/telnet/sessrec/sessrec.c +564 -0
- decnet/templates/telnet/syslog_bridge.py +403 -0
- decnet/templates/tftp/Dockerfile +23 -0
- decnet/templates/tftp/entrypoint.sh +4 -0
- decnet/templates/tftp/instance_seed.py +121 -0
- decnet/templates/tftp/server.py +85 -0
- decnet/templates/tftp/syslog_bridge.py +403 -0
- decnet/templates/vnc/Dockerfile +23 -0
- decnet/templates/vnc/entrypoint.sh +4 -0
- decnet/templates/vnc/instance_seed.py +121 -0
- decnet/templates/vnc/server.py +110 -0
- decnet/templates/vnc/syslog_bridge.py +403 -0
- decnet/topology/__init__.py +24 -0
- decnet/topology/allocator.py +162 -0
- decnet/topology/compose.py +174 -0
- decnet/topology/config.py +114 -0
- decnet/topology/generator.py +260 -0
- decnet/topology/hashing.py +66 -0
- decnet/topology/persistence.py +226 -0
- decnet/topology/repository.py +30 -0
- decnet/topology/status.py +107 -0
- decnet/topology/validate.py +429 -0
- decnet/ttp/__init__.py +8 -0
- decnet/ttp/attack_catalog.py +20 -0
- decnet/ttp/attack_stix.py +576 -0
- decnet/ttp/attack_version.py +59 -0
- decnet/ttp/base.py +185 -0
- decnet/ttp/data/__init__.py +7 -0
- decnet/ttp/data/intel/__init__.py +9 -0
- decnet/ttp/data/intel_loader.py +230 -0
- decnet/ttp/factory.py +194 -0
- decnet/ttp/impl/__init__.py +7 -0
- decnet/ttp/impl/_emit.py +71 -0
- decnet/ttp/impl/_rule_index.py +190 -0
- decnet/ttp/impl/_state.py +56 -0
- decnet/ttp/impl/behavioral_lifter.py +276 -0
- decnet/ttp/impl/canary_fingerprint_lifter.py +160 -0
- decnet/ttp/impl/credential_lifter.py +186 -0
- decnet/ttp/impl/email_lifter.py +474 -0
- decnet/ttp/impl/http_fingerprint_lifter.py +127 -0
- decnet/ttp/impl/identity_lifter.py +115 -0
- decnet/ttp/impl/intel_lifter.py +374 -0
- decnet/ttp/impl/ipv6_leak_lifter.py +75 -0
- decnet/ttp/impl/rule_engine.py +439 -0
- decnet/ttp/misp_export.py +120 -0
- decnet/ttp/stix_custom.py +101 -0
- decnet/ttp/stix_export.py +518 -0
- decnet/ttp/store/__init__.py +26 -0
- decnet/ttp/store/base.py +150 -0
- decnet/ttp/store/factory.py +48 -0
- decnet/ttp/store/impl/__init__.py +2 -0
- decnet/ttp/store/impl/database.py +615 -0
- decnet/ttp/store/impl/filesystem.py +553 -0
- decnet/ttp/worker.py +657 -0
- decnet/updater/__init__.py +11 -0
- decnet/updater/app.py +219 -0
- decnet/updater/executor.py +771 -0
- decnet/updater/server.py +91 -0
- decnet/util/__init__.py +2 -0
- decnet/util/simhash.py +66 -0
- decnet/vectorstore/__init__.py +28 -0
- decnet/vectorstore/base.py +115 -0
- decnet/vectorstore/factory.py +74 -0
- decnet/vectorstore/fake.py +132 -0
- decnet/vectorstore/sqlite_vec.py +286 -0
- decnet/web/_mtls.py +127 -0
- decnet/web/_uvicorn_tls_scope.py +73 -0
- decnet/web/api.py +451 -0
- decnet/web/auth.py +71 -0
- decnet/web/db/factory.py +35 -0
- decnet/web/db/migrate.py +38 -0
- decnet/web/db/migrations/env.py +90 -0
- decnet/web/db/migrations/versions/4a914b1d62a0_baseline_schema.py +1109 -0
- decnet/web/db/models/__init__.py +417 -0
- decnet/web/db/models/_base.py +24 -0
- decnet/web/db/models/attachments.py +77 -0
- decnet/web/db/models/attacker_intel.py +163 -0
- decnet/web/db/models/attackers.py +353 -0
- decnet/web/db/models/attribution_state.py +79 -0
- decnet/web/db/models/auth.py +128 -0
- decnet/web/db/models/campaigns.py +84 -0
- decnet/web/db/models/canary.py +260 -0
- decnet/web/db/models/common.py +16 -0
- decnet/web/db/models/decky.py +131 -0
- decnet/web/db/models/decky_lifecycle.py +88 -0
- decnet/web/db/models/deploy.py +43 -0
- decnet/web/db/models/fleet.py +73 -0
- decnet/web/db/models/health.py +15 -0
- decnet/web/db/models/logs.py +238 -0
- decnet/web/db/models/observations.py +81 -0
- decnet/web/db/models/orchestrator.py +112 -0
- decnet/web/db/models/realism.py +108 -0
- decnet/web/db/models/swarm.py +232 -0
- decnet/web/db/models/tarpit.py +45 -0
- decnet/web/db/models/topology.py +455 -0
- decnet/web/db/models/ttp.py +469 -0
- decnet/web/db/models/updater.py +74 -0
- decnet/web/db/models/webhooks.py +163 -0
- decnet/web/db/models/workers.py +51 -0
- decnet/web/db/mysql/__init__.py +1 -0
- decnet/web/db/mysql/database.py +101 -0
- decnet/web/db/mysql/repository.py +116 -0
- decnet/web/db/repository.py +1776 -0
- decnet/web/db/secrets.py +44 -0
- decnet/web/db/sqlite/database.py +66 -0
- decnet/web/db/sqlite/repository.py +69 -0
- decnet/web/db/sqlmodel_repo/__init__.py +215 -0
- decnet/web/db/sqlmodel_repo/_helpers.py +160 -0
- decnet/web/db/sqlmodel_repo/attacker_intel.py +102 -0
- decnet/web/db/sqlmodel_repo/attackers/__init__.py +31 -0
- decnet/web/db/sqlmodel_repo/attackers/_core.py +136 -0
- decnet/web/db/sqlmodel_repo/attackers/activity.py +229 -0
- decnet/web/db/sqlmodel_repo/attackers/behavior.py +110 -0
- decnet/web/db/sqlmodel_repo/attackers/smtp.py +72 -0
- decnet/web/db/sqlmodel_repo/attribution.py +216 -0
- decnet/web/db/sqlmodel_repo/auth.py +104 -0
- decnet/web/db/sqlmodel_repo/bounties.py +189 -0
- decnet/web/db/sqlmodel_repo/campaigns.py +179 -0
- decnet/web/db/sqlmodel_repo/canary.py +207 -0
- decnet/web/db/sqlmodel_repo/credentials/__init__.py +21 -0
- decnet/web/db/sqlmodel_repo/credentials/_core.py +234 -0
- decnet/web/db/sqlmodel_repo/credentials/reuse.py +279 -0
- decnet/web/db/sqlmodel_repo/deckies.py +94 -0
- decnet/web/db/sqlmodel_repo/decky_lifecycle.py +107 -0
- decnet/web/db/sqlmodel_repo/fleet.py +166 -0
- decnet/web/db/sqlmodel_repo/identities.py +191 -0
- decnet/web/db/sqlmodel_repo/logs.py +226 -0
- decnet/web/db/sqlmodel_repo/observations.py +261 -0
- decnet/web/db/sqlmodel_repo/observed_attachments.py +109 -0
- decnet/web/db/sqlmodel_repo/orchestrator.py +235 -0
- decnet/web/db/sqlmodel_repo/realism.py +160 -0
- decnet/web/db/sqlmodel_repo/swarm.py +74 -0
- decnet/web/db/sqlmodel_repo/tarpit.py +73 -0
- decnet/web/db/sqlmodel_repo/topology/__init__.py +33 -0
- decnet/web/db/sqlmodel_repo/topology/_core.py +249 -0
- decnet/web/db/sqlmodel_repo/topology/deckies.py +142 -0
- decnet/web/db/sqlmodel_repo/topology/edges.py +84 -0
- decnet/web/db/sqlmodel_repo/topology/lans.py +129 -0
- decnet/web/db/sqlmodel_repo/topology/mutations.py +174 -0
- decnet/web/db/sqlmodel_repo/ttp.py +538 -0
- decnet/web/db/sqlmodel_repo/webhooks.py +137 -0
- decnet/web/dependencies.py +428 -0
- decnet/web/ingester.py +1568 -0
- decnet/web/limiter.py +89 -0
- decnet/web/router/__init__.py +220 -0
- decnet/web/router/artifacts/__init__.py +0 -0
- decnet/web/router/artifacts/api_get_artifact.py +60 -0
- decnet/web/router/attackers/__init__.py +1 -0
- decnet/web/router/attackers/_guards.py +28 -0
- decnet/web/router/attackers/api_events.py +235 -0
- decnet/web/router/attackers/api_export_attacker_misp.py +107 -0
- decnet/web/router/attackers/api_export_attacker_stix.py +111 -0
- decnet/web/router/attackers/api_export_attackers.py +101 -0
- decnet/web/router/attackers/api_export_attackers_misp.py +68 -0
- decnet/web/router/attackers/api_export_attackers_stix.py +78 -0
- decnet/web/router/attackers/api_get_attacker_artifacts.py +35 -0
- decnet/web/router/attackers/api_get_attacker_attribution.py +93 -0
- decnet/web/router/attackers/api_get_attacker_commands.py +43 -0
- decnet/web/router/attackers/api_get_attacker_detail.py +54 -0
- decnet/web/router/attackers/api_get_attacker_intel.py +39 -0
- decnet/web/router/attackers/api_get_attacker_mail.py +38 -0
- decnet/web/router/attackers/api_get_attacker_smtp_targets.py +37 -0
- decnet/web/router/attackers/api_get_attacker_transcripts.py +35 -0
- decnet/web/router/attackers/api_get_attackers.py +85 -0
- decnet/web/router/auth/api_change_pass.py +43 -0
- decnet/web/router/auth/api_login.py +66 -0
- decnet/web/router/auth/api_logout.py +36 -0
- decnet/web/router/auth/api_sse_ticket.py +39 -0
- decnet/web/router/bounty/api_get_bounties.py +81 -0
- decnet/web/router/campaigns/__init__.py +1 -0
- decnet/web/router/campaigns/api_events.py +125 -0
- decnet/web/router/campaigns/api_get_campaign_detail.py +41 -0
- decnet/web/router/campaigns/api_list_campaign_identities.py +42 -0
- decnet/web/router/campaigns/api_list_campaigns.py +36 -0
- decnet/web/router/canary/__init__.py +24 -0
- decnet/web/router/canary/api_blobs.py +173 -0
- decnet/web/router/canary/api_tokens.py +371 -0
- decnet/web/router/config/__init__.py +1 -0
- decnet/web/router/config/api_get_config.py +125 -0
- decnet/web/router/config/api_manage_users.py +154 -0
- decnet/web/router/config/api_reinit.py +30 -0
- decnet/web/router/config/api_update_config.py +51 -0
- decnet/web/router/credential_reuse/__init__.py +1 -0
- decnet/web/router/credential_reuse/api_get_credential_reuse.py +75 -0
- decnet/web/router/credentials/__init__.py +1 -0
- decnet/web/router/credentials/api_get_credentials.py +104 -0
- decnet/web/router/deckies/__init__.py +34 -0
- decnet/web/router/deckies/api_file_drop.py +127 -0
- decnet/web/router/deckies/api_services.py +314 -0
- decnet/web/router/deckies/api_tarpit.py +235 -0
- decnet/web/router/fleet/api_deploy_deckies.py +281 -0
- decnet/web/router/fleet/api_get_deckies.py +49 -0
- decnet/web/router/fleet/api_lifecycle.py +42 -0
- decnet/web/router/fleet/api_mutate_decky.py +102 -0
- decnet/web/router/fleet/api_mutate_interval.py +46 -0
- decnet/web/router/fleet/api_teardown_decky.py +92 -0
- decnet/web/router/health/__init__.py +1 -0
- decnet/web/router/health/api_get_health.py +156 -0
- decnet/web/router/identities/__init__.py +1 -0
- decnet/web/router/identities/api_events.py +145 -0
- decnet/web/router/identities/api_get_identity_detail.py +45 -0
- decnet/web/router/identities/api_list_identities.py +36 -0
- decnet/web/router/identities/api_list_identity_observations.py +49 -0
- decnet/web/router/logs/api_get_histogram.py +69 -0
- decnet/web/router/logs/api_get_logs.py +76 -0
- decnet/web/router/orchestrator/__init__.py +1 -0
- decnet/web/router/orchestrator/api_event_stats.py +100 -0
- decnet/web/router/orchestrator/api_events.py +124 -0
- decnet/web/router/orchestrator/api_list_events.py +88 -0
- decnet/web/router/realism/__init__.py +1 -0
- decnet/web/router/realism/api_config.py +127 -0
- decnet/web/router/realism/api_llm.py +177 -0
- decnet/web/router/realism/api_personas.py +144 -0
- decnet/web/router/realism/api_synthetic_files.py +100 -0
- decnet/web/router/stats/api_get_stats.py +51 -0
- decnet/web/router/stream/api_stream_events.py +152 -0
- decnet/web/router/swarm/__init__.py +48 -0
- decnet/web/router/swarm/_mtls.py +75 -0
- decnet/web/router/swarm/api_check_hosts.py +76 -0
- decnet/web/router/swarm/api_decommission_host.py +75 -0
- decnet/web/router/swarm/api_deploy_swarm.py +173 -0
- decnet/web/router/swarm/api_enroll_host.py +113 -0
- decnet/web/router/swarm/api_get_host.py +34 -0
- decnet/web/router/swarm/api_get_swarm_health.py +12 -0
- decnet/web/router/swarm/api_heartbeat.py +276 -0
- decnet/web/router/swarm/api_list_deckies.py +67 -0
- decnet/web/router/swarm/api_list_hosts.py +33 -0
- decnet/web/router/swarm/api_teardown_swarm.py +66 -0
- decnet/web/router/swarm_mgmt/__init__.py +27 -0
- decnet/web/router/swarm_mgmt/api_decommission_host.py +72 -0
- decnet/web/router/swarm_mgmt/api_enroll_bundle.py +272 -0
- decnet/web/router/swarm_mgmt/api_list_deckies.py +59 -0
- decnet/web/router/swarm_mgmt/api_list_hosts.py +61 -0
- decnet/web/router/swarm_mgmt/api_teardown_host.py +151 -0
- decnet/web/router/swarm_updates/__init__.py +24 -0
- decnet/web/router/swarm_updates/api_list_host_releases.py +88 -0
- decnet/web/router/swarm_updates/api_push_update.py +168 -0
- decnet/web/router/swarm_updates/api_push_update_self.py +102 -0
- decnet/web/router/swarm_updates/api_rollback_host.py +78 -0
- decnet/web/router/system/__init__.py +7 -0
- decnet/web/router/system/api_deployment_mode.py +48 -0
- decnet/web/router/topology/__init__.py +58 -0
- decnet/web/router/topology/_guards.py +53 -0
- decnet/web/router/topology/_target_host.py +67 -0
- decnet/web/router/topology/api_catalog.py +184 -0
- decnet/web/router/topology/api_create_blank_topology.py +130 -0
- decnet/web/router/topology/api_create_topology.py +80 -0
- decnet/web/router/topology/api_decky_crud.py +137 -0
- decnet/web/router/topology/api_delete_topology.py +52 -0
- decnet/web/router/topology/api_deploy_topology.py +77 -0
- decnet/web/router/topology/api_edge_crud.py +111 -0
- decnet/web/router/topology/api_events.py +201 -0
- decnet/web/router/topology/api_get_topology.py +69 -0
- decnet/web/router/topology/api_lan_crud.py +153 -0
- decnet/web/router/topology/api_list_topologies.py +40 -0
- decnet/web/router/topology/api_mutations.py +128 -0
- decnet/web/router/topology/api_personas.py +132 -0
- decnet/web/router/topology/api_reap_orphans.py +49 -0
- decnet/web/router/topology/api_tarpit.py +210 -0
- decnet/web/router/topology/api_teardown_topology.py +80 -0
- decnet/web/router/transcripts/__init__.py +7 -0
- decnet/web/router/transcripts/api_get_transcript.py +181 -0
- decnet/web/router/ttp/__init__.py +7 -0
- decnet/web/router/ttp/api_export_navigator.py +71 -0
- decnet/web/router/ttp/api_get_by_attacker.py +36 -0
- decnet/web/router/ttp/api_get_by_campaign.py +32 -0
- decnet/web/router/ttp/api_get_by_identity.py +36 -0
- decnet/web/router/ttp/api_get_by_session.py +32 -0
- decnet/web/router/ttp/api_get_groups_for_technique.py +51 -0
- decnet/web/router/ttp/api_get_rules.py +155 -0
- decnet/web/router/ttp/api_get_tag_details.py +83 -0
- decnet/web/router/ttp/api_get_techniques.py +35 -0
- decnet/web/router/webhooks/__init__.py +19 -0
- decnet/web/router/webhooks/api_manage_webhooks.py +255 -0
- decnet/web/router/webhooks/api_test_webhook.py +61 -0
- decnet/web/router/workers/__init__.py +1 -0
- decnet/web/router/workers/api_control_worker.py +77 -0
- decnet/web/router/workers/api_list_workers.py +36 -0
- decnet/web/router/workers/api_start_all_workers.py +104 -0
- decnet/web/router/workers/api_start_worker.py +73 -0
- decnet/web/services/__init__.py +1 -0
- decnet/web/services/systemd_control.py +137 -0
- decnet/web/sse_limits.py +66 -0
- decnet/web/swarm_api.py +68 -0
- decnet/web/worker_registry.py +212 -0
- decnet/webhook/__init__.py +5 -0
- decnet/webhook/client.py +249 -0
- decnet/webhook/enums.py +55 -0
- decnet/webhook/ssrf.py +151 -0
- decnet/webhook/worker.py +313 -0
- decnet-1.0.0.dist-info/METADATA +935 -0
- decnet-1.0.0.dist-info/RECORD +786 -0
- decnet-1.0.0.dist-info/WHEEL +5 -0
- decnet-1.0.0.dist-info/entry_points.txt +2 -0
- decnet-1.0.0.dist-info/licenses/LICENSE +661 -0
- decnet-1.0.0.dist-info/top_level.txt +1 -0
decnet/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
"""DECNET — honeypot deception-network framework.
|
|
3
|
+
|
|
4
|
+
This __init__ runs once, on the first `import decnet.*`. It seeds
|
|
5
|
+
os.environ from /etc/decnet/decnet.ini (if present) so that later
|
|
6
|
+
module-level reads in decnet.env pick up the INI values as if they had
|
|
7
|
+
been exported by the shell. Real env vars always win via setdefault().
|
|
8
|
+
|
|
9
|
+
Kept minimal on purpose — any heavier work belongs in a submodule.
|
|
10
|
+
"""
|
|
11
|
+
from decnet.config_ini import load_ini_config as _load_ini_config
|
|
12
|
+
|
|
13
|
+
_load_ini_config()
|
decnet/agent/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
"""DECNET worker agent — runs on every SWARM worker host.
|
|
3
|
+
|
|
4
|
+
Exposes an mTLS-protected FastAPI service the master's SWARM controller
|
|
5
|
+
calls to deploy, mutate, and tear down deckies locally. The agent reuses
|
|
6
|
+
the existing `decnet.engine.deployer` code path unchanged, so a worker runs
|
|
7
|
+
deckies the same way `decnet deploy --mode unihost` does today.
|
|
8
|
+
"""
|
decnet/agent/app.py
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
"""Worker-side FastAPI app.
|
|
3
|
+
|
|
4
|
+
Protected by mTLS at the ASGI/uvicorn transport layer: uvicorn is started
|
|
5
|
+
with ``--ssl-ca-certs`` + ``--ssl-cert-reqs 2`` (CERT_REQUIRED), so any
|
|
6
|
+
client that cannot prove a cert signed by the DECNET CA is rejected before
|
|
7
|
+
reaching a handler. Once past the TLS handshake, all peers are trusted
|
|
8
|
+
equally (the only entity holding a CA-signed cert is the master
|
|
9
|
+
controller).
|
|
10
|
+
|
|
11
|
+
Endpoints mirror the existing unihost CLI verbs:
|
|
12
|
+
|
|
13
|
+
* ``POST /deploy`` — body: serialized ``DecnetConfig``
|
|
14
|
+
* ``POST /teardown`` — body: optional ``{"decky_id": "..."}``
|
|
15
|
+
* ``POST /mutate`` — body: ``{"decky_id": "...", "services": [...]}``
|
|
16
|
+
* ``GET /status`` — deployment snapshot
|
|
17
|
+
* ``GET /health`` — liveness probe, does NOT require mTLS? No — mTLS
|
|
18
|
+
still required; master pings it with its cert.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import asyncio
|
|
23
|
+
import os
|
|
24
|
+
import pathlib
|
|
25
|
+
from contextlib import asynccontextmanager
|
|
26
|
+
from typing import Any, Optional
|
|
27
|
+
|
|
28
|
+
from fastapi import FastAPI, HTTPException
|
|
29
|
+
from fastapi.responses import JSONResponse
|
|
30
|
+
from pydantic import BaseModel, Field
|
|
31
|
+
|
|
32
|
+
import contextlib
|
|
33
|
+
|
|
34
|
+
from decnet.agent import executor as _exec
|
|
35
|
+
from decnet.agent import heartbeat as _heartbeat
|
|
36
|
+
from decnet.agent import topology_ops as _topology_ops
|
|
37
|
+
from decnet.bus.factory import get_bus
|
|
38
|
+
from decnet.bus.publish import run_health_heartbeat
|
|
39
|
+
from decnet.swarm.pki import DEFAULT_AGENT_DIR
|
|
40
|
+
from decnet.agent.topology_store import AlreadyApplied, TopologyStore
|
|
41
|
+
from decnet.config import DecnetConfig
|
|
42
|
+
from decnet.logging import get_logger
|
|
43
|
+
from decnet.topology.validate import ValidationError
|
|
44
|
+
|
|
45
|
+
log = get_logger("agent.app")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _resolve_agent_dir() -> pathlib.Path:
|
|
49
|
+
env = os.environ.get("DECNET_AGENT_DIR")
|
|
50
|
+
if env:
|
|
51
|
+
return pathlib.Path(env)
|
|
52
|
+
system = pathlib.Path("/etc/decnet/agent")
|
|
53
|
+
if system.exists():
|
|
54
|
+
return system
|
|
55
|
+
return DEFAULT_AGENT_DIR
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Module-level singleton. Created lazily on first use so tests can
|
|
59
|
+
# monkeypatch DECNET_AGENT_DIR before the store binds to a path.
|
|
60
|
+
_topology_store: Optional[TopologyStore] = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _store() -> TopologyStore:
|
|
64
|
+
global _topology_store
|
|
65
|
+
if _topology_store is None:
|
|
66
|
+
_topology_store = TopologyStore(_resolve_agent_dir() / "topology.db")
|
|
67
|
+
return _topology_store
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_collector_task: Optional[asyncio.Task] = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _ensure_collector_started() -> None:
|
|
74
|
+
"""Spawn the log collector on demand — called from /topology/apply
|
|
75
|
+
after a successful materialise. We must NOT start this in the
|
|
76
|
+
lifespan hook: the agent's boot invariant is "never touch docker
|
|
77
|
+
until master tells us to" (see tests/swarm/test_agent_no_auto_restore.py).
|
|
78
|
+
|
|
79
|
+
The collector watches ``decnet.topology.service=true`` labels via
|
|
80
|
+
docker events, writing RFC 5424 lines to ``DECNET_AGENT_LOG_FILE``
|
|
81
|
+
which the forwarder ships to the master over syslog-TLS. Idempotent:
|
|
82
|
+
subsequent calls while the task is still running are no-ops.
|
|
83
|
+
"""
|
|
84
|
+
global _collector_task
|
|
85
|
+
if _collector_task is not None and not _collector_task.done():
|
|
86
|
+
return
|
|
87
|
+
from decnet.env import DECNET_AGENT_LOG_FILE
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
from decnet.collector.worker import log_collector_worker
|
|
91
|
+
except Exception: # noqa: BLE001 — docker may be unavailable on dev
|
|
92
|
+
log.warning(
|
|
93
|
+
"agent log collector not starting — collector worker import failed",
|
|
94
|
+
exc_info=True,
|
|
95
|
+
)
|
|
96
|
+
return
|
|
97
|
+
_collector_task = asyncio.create_task(
|
|
98
|
+
log_collector_worker(DECNET_AGENT_LOG_FILE),
|
|
99
|
+
name="agent-log-collector",
|
|
100
|
+
)
|
|
101
|
+
log.info("agent log collector started log_file=%s", DECNET_AGENT_LOG_FILE)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
_bus_heartbeat_task: Optional[asyncio.Task] = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@asynccontextmanager
|
|
108
|
+
async def _lifespan(app: FastAPI):
|
|
109
|
+
# Best-effort: if identity/bundle plumbing isn't configured (e.g. dev
|
|
110
|
+
# runs or non-enrolled hosts), heartbeat.start() is a silent no-op.
|
|
111
|
+
_heartbeat.start()
|
|
112
|
+
|
|
113
|
+
# Host-local bus heartbeat (system.agent.health). Separate channel
|
|
114
|
+
# from the mTLS master-facing heartbeat above; this one lets peers on
|
|
115
|
+
# the same host (dashboard, updater) see the agent is alive without
|
|
116
|
+
# hitting its HTTPS endpoint. Bus-disabled path is a no-op loop.
|
|
117
|
+
bus = None
|
|
118
|
+
try:
|
|
119
|
+
bus = get_bus(client_name="agent")
|
|
120
|
+
await bus.connect()
|
|
121
|
+
except Exception as exc: # noqa: BLE001
|
|
122
|
+
log.warning("agent: bus unavailable, skipping health heartbeat: %s", exc)
|
|
123
|
+
bus = None
|
|
124
|
+
|
|
125
|
+
global _bus_heartbeat_task
|
|
126
|
+
_bus_heartbeat_task = asyncio.create_task(
|
|
127
|
+
run_health_heartbeat(bus, "agent"),
|
|
128
|
+
name="agent-bus-heartbeat",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
yield
|
|
133
|
+
finally:
|
|
134
|
+
await _heartbeat.stop()
|
|
135
|
+
if _bus_heartbeat_task is not None:
|
|
136
|
+
_bus_heartbeat_task.cancel()
|
|
137
|
+
with contextlib.suppress(asyncio.CancelledError, Exception):
|
|
138
|
+
await _bus_heartbeat_task
|
|
139
|
+
_bus_heartbeat_task = None
|
|
140
|
+
if bus is not None:
|
|
141
|
+
with contextlib.suppress(Exception):
|
|
142
|
+
await bus.close()
|
|
143
|
+
global _collector_task
|
|
144
|
+
if _collector_task is not None and not _collector_task.done():
|
|
145
|
+
_collector_task.cancel()
|
|
146
|
+
try:
|
|
147
|
+
await _collector_task
|
|
148
|
+
except (asyncio.CancelledError, Exception): # noqa: BLE001
|
|
149
|
+
pass
|
|
150
|
+
_collector_task = None
|
|
151
|
+
global _topology_store
|
|
152
|
+
if _topology_store is not None:
|
|
153
|
+
_topology_store.close()
|
|
154
|
+
_topology_store = None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
app = FastAPI(
|
|
158
|
+
title="DECNET SWARM Agent",
|
|
159
|
+
version="0.1.0",
|
|
160
|
+
docs_url=None, # no interactive docs on worker — narrow attack surface
|
|
161
|
+
redoc_url=None,
|
|
162
|
+
openapi_url=None,
|
|
163
|
+
lifespan=_lifespan,
|
|
164
|
+
responses={
|
|
165
|
+
400: {"description": "Malformed request body"},
|
|
166
|
+
500: {"description": "Executor error"},
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ------------------------------------------------------------------ schemas
|
|
172
|
+
|
|
173
|
+
class DeployRequest(BaseModel):
|
|
174
|
+
config: DecnetConfig = Field(..., description="Full DecnetConfig to materialise on this worker")
|
|
175
|
+
dry_run: bool = False
|
|
176
|
+
no_cache: bool = False
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class TeardownRequest(BaseModel):
|
|
180
|
+
decky_id: Optional[str] = None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class MutateRequest(BaseModel):
|
|
184
|
+
decky_id: str
|
|
185
|
+
services: list[str]
|
|
186
|
+
dry_run: bool = False
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# ------------------------------------------------------------------ routes
|
|
190
|
+
|
|
191
|
+
@app.get("/health")
|
|
192
|
+
async def health() -> dict[str, str]:
|
|
193
|
+
return {"status": "ok"}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@app.get("/status")
|
|
197
|
+
async def status() -> dict:
|
|
198
|
+
return await _exec.status()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@app.post(
|
|
202
|
+
"/deploy",
|
|
203
|
+
status_code=202,
|
|
204
|
+
responses={202: {"description": "Deploy accepted; runs in background; lifecycle deltas pushed via heartbeat"}},
|
|
205
|
+
)
|
|
206
|
+
async def deploy(req: DeployRequest) -> dict:
|
|
207
|
+
"""Spawn the deploy in the background and return 202 immediately.
|
|
208
|
+
|
|
209
|
+
The master tracks per-decky completion via lifecycle deltas pushed on
|
|
210
|
+
the next heartbeat (one immediate push on completion, plus the
|
|
211
|
+
scheduled 30 s ticks as a fallback). Holding the request open across
|
|
212
|
+
a multi-minute compose build was the previous source of the wizard
|
|
213
|
+
API-hang."""
|
|
214
|
+
asyncio.create_task(
|
|
215
|
+
_exec.deploy_async(req.config, dry_run=req.dry_run, no_cache=req.no_cache),
|
|
216
|
+
name=f"deploy-{id(req)}",
|
|
217
|
+
)
|
|
218
|
+
return {"status": "accepted", "deckies": [d.name for d in req.config.deckies]}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@app.post(
|
|
222
|
+
"/teardown",
|
|
223
|
+
responses={500: {"description": "Teardown raised an exception"}},
|
|
224
|
+
)
|
|
225
|
+
async def teardown(req: TeardownRequest) -> dict:
|
|
226
|
+
try:
|
|
227
|
+
await _exec.teardown(req.decky_id)
|
|
228
|
+
except Exception as exc:
|
|
229
|
+
log.exception("agent.teardown failed")
|
|
230
|
+
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
231
|
+
return {"status": "torn_down", "decky_id": req.decky_id}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@app.post(
|
|
235
|
+
"/self-destruct",
|
|
236
|
+
responses={500: {"description": "Reaper could not be scheduled"}},
|
|
237
|
+
)
|
|
238
|
+
async def self_destruct() -> dict:
|
|
239
|
+
"""Stop all DECNET services on this worker and delete the install
|
|
240
|
+
footprint. Called by the master during decommission. Logs under
|
|
241
|
+
/var/log/decnet* are preserved. Fire-and-forget — returns 202 before
|
|
242
|
+
the reaper starts deleting files."""
|
|
243
|
+
try:
|
|
244
|
+
await _exec.self_destruct()
|
|
245
|
+
except Exception as exc:
|
|
246
|
+
log.exception("agent.self_destruct failed")
|
|
247
|
+
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
248
|
+
return {"status": "self_destruct_scheduled"}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# ------------------------------------------------------- topology endpoints
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class ApplyTopologyRequest(BaseModel):
|
|
255
|
+
hydrated: dict[str, Any] = Field(
|
|
256
|
+
..., description="Hydrated topology dict from master.persistence.hydrate()"
|
|
257
|
+
)
|
|
258
|
+
version_hash: str = Field(
|
|
259
|
+
..., description="Master's canonical_hash(hydrated); must match ours"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class TeardownTopologyRequest(BaseModel):
|
|
264
|
+
topology_id: str = Field(..., description="Topology UUID to dismantle")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@app.post(
|
|
268
|
+
"/topology/apply",
|
|
269
|
+
responses={
|
|
270
|
+
400: {"description": "Malformed hydrated topology or hash mismatch"},
|
|
271
|
+
409: {"description": "A different topology is already applied"},
|
|
272
|
+
500: {"description": "Docker or compose raised while applying"},
|
|
273
|
+
},
|
|
274
|
+
)
|
|
275
|
+
async def topology_apply(req: ApplyTopologyRequest) -> dict:
|
|
276
|
+
store = _store()
|
|
277
|
+
try:
|
|
278
|
+
await _topology_ops.apply(req.hydrated, req.version_hash, store)
|
|
279
|
+
except _topology_ops.HashMismatch as exc:
|
|
280
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
281
|
+
except ValidationError as exc:
|
|
282
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
283
|
+
except AlreadyApplied as exc:
|
|
284
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
285
|
+
except Exception as exc:
|
|
286
|
+
log.exception("agent.topology_apply failed")
|
|
287
|
+
topology_id = (req.hydrated.get("topology") or {}).get("id")
|
|
288
|
+
if topology_id:
|
|
289
|
+
try:
|
|
290
|
+
store.record_error(
|
|
291
|
+
str(topology_id), str(exc)[:500], hydrated=req.hydrated,
|
|
292
|
+
)
|
|
293
|
+
except Exception: # noqa: BLE001 — don't mask original failure
|
|
294
|
+
log.exception("failed to record apply error")
|
|
295
|
+
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
296
|
+
_ensure_collector_started()
|
|
297
|
+
return {"status": "applied", "version_hash": req.version_hash}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@app.post(
|
|
301
|
+
"/topology/teardown",
|
|
302
|
+
responses={500: {"description": "Docker or compose raised while tearing down"}},
|
|
303
|
+
)
|
|
304
|
+
async def topology_teardown(req: TeardownTopologyRequest) -> dict:
|
|
305
|
+
try:
|
|
306
|
+
await _topology_ops.teardown(req.topology_id, _store())
|
|
307
|
+
except Exception as exc:
|
|
308
|
+
log.exception("agent.topology_teardown failed")
|
|
309
|
+
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
310
|
+
return {"status": "torn_down", "topology_id": req.topology_id}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@app.get("/topology/state")
|
|
314
|
+
async def topology_state() -> dict:
|
|
315
|
+
return _topology_ops.state(_store())
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@app.post(
|
|
319
|
+
"/mutate",
|
|
320
|
+
status_code=202,
|
|
321
|
+
responses={
|
|
322
|
+
202: {"description": "Mutate accepted; runs in background; lifecycle delta pushed via heartbeat"},
|
|
323
|
+
404: {"description": "No active deployment, or unknown decky_id (dry_run validation only)"},
|
|
324
|
+
},
|
|
325
|
+
)
|
|
326
|
+
async def mutate(req: MutateRequest) -> Any:
|
|
327
|
+
"""Spawn the mutate in the background and return 202 immediately.
|
|
328
|
+
|
|
329
|
+
Master tracks completion via a lifecycle delta pushed on the next
|
|
330
|
+
heartbeat (immediate push on completion). ``dry_run`` is still
|
|
331
|
+
synchronous — it validates against the worker's current state and
|
|
332
|
+
returns the would-be services without spawning a task or touching
|
|
333
|
+
docker, so the wizard's preview path stays cheap."""
|
|
334
|
+
if req.dry_run:
|
|
335
|
+
from decnet.config import load_state
|
|
336
|
+
state = load_state()
|
|
337
|
+
if state is None:
|
|
338
|
+
raise HTTPException(
|
|
339
|
+
status_code=404,
|
|
340
|
+
detail="no active deployment on this worker",
|
|
341
|
+
)
|
|
342
|
+
cfg, _ = state
|
|
343
|
+
decky = next((d for d in cfg.deckies if d.name == req.decky_id), None)
|
|
344
|
+
if decky is None:
|
|
345
|
+
raise HTTPException(
|
|
346
|
+
status_code=404,
|
|
347
|
+
detail=f"decky {req.decky_id!r} not found in worker state",
|
|
348
|
+
)
|
|
349
|
+
return JSONResponse(
|
|
350
|
+
status_code=200,
|
|
351
|
+
content={
|
|
352
|
+
"status": "dry_run",
|
|
353
|
+
"decky_id": req.decky_id,
|
|
354
|
+
"services": list(req.services),
|
|
355
|
+
},
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
asyncio.create_task(
|
|
359
|
+
_exec.mutate_async(req.decky_id, list(req.services)),
|
|
360
|
+
name=f"mutate-{req.decky_id}",
|
|
361
|
+
)
|
|
362
|
+
return {
|
|
363
|
+
"status": "accepted",
|
|
364
|
+
"decky_id": req.decky_id,
|
|
365
|
+
"services": list(req.services),
|
|
366
|
+
}
|