@underpostnet/cyberia 3.3.0 → 3.3.73
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/.dockerignore +1 -0
- package/.github/workflows/coverall.cyberia.ci.yml +34 -14
- package/.github/workflows/cyberia-client.cd.yml +5 -0
- package/.github/workflows/cyberia-server.cd.yml +5 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
- package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
- package/.github/workflows/engine-cyberia.cd.yml +3 -0
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +35 -20
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -0
- package/.prettierignore +1 -0
- package/AGENTS.md +47 -0
- package/CHANGELOG.md +565 -2510
- package/CLI-HELP.md +306 -147
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +4 -4
- package/README.md +21 -1
- package/bin/build.js +66 -41
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +308 -91
- package/bin/deploy.js +18 -104
- package/bin/index.js +308 -91
- package/conf.js +91 -4
- package/deploy/cyberia-client/deploy.sh +47 -0
- package/deploy/cyberia-client/package.sh +18 -0
- package/deploy/cyberia-client/state.sh +14 -0
- package/deploy/cyberia-server/deploy.sh +47 -0
- package/deploy/cyberia-server/package.sh +18 -0
- package/deploy/cyberia-server/state.sh +14 -0
- package/deploy/dd-cyberia/deploy.sh +6 -26
- package/deploy/dd-cyberia/init.sh +35 -43
- package/deploy/dd-cyberia/package.sh +17 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +92 -170
- package/deploy/lib/github-actions-logging.sh +594 -0
- package/deploy/lib/host.sh +185 -0
- package/deploy/lib/state.sh +62 -0
- package/deploy/pwa-microservices-template/deploy.sh +12 -39
- package/deploy/release/deploy.sh +9 -39
- package/deployment.yaml +7 -2
- package/docker-compose.yml +7 -7
- package/examples/static-page/README.md +19 -19
- package/examples/static-page/ssr-components/CustomPage.js +2 -2
- package/hardhat/hardhat.config.js +1 -6
- package/hardhat/package-lock.json +101 -40
- package/hardhat/package.json +2 -2
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/grafana/deployment.yaml +26 -7
- package/manifests/grafana/kustomization.yaml +1 -0
- package/manifests/grafana/service.yaml +6 -1
- package/manifests/mariadb/pv.yaml +1 -0
- package/manifests/mongodb/statefulset.yaml +9 -2
- package/manifests/mysql/pv-pvc.yaml +14 -1
- package/manifests/postgresql/pv.yaml +12 -1
- package/manifests/postgresql/pvc.yaml +1 -0
- package/manifests/prometheus/deployment.yaml +18 -22
- package/package.json +83 -10
- package/scripts/event-monitor.sh +56 -0
- package/scripts/k3s-node-setup.sh +27 -14
- package/scripts/kubeadm-node-setup.sh +31 -11
- package/scripts/maas-setup.sh +8 -8
- package/scripts/selinux-normalize.sh +907 -0
- package/scripts/shutdown-machine.sh +843 -0
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
- package/src/api/core/core.controller.js +1 -1
- package/src/api/core/core.router.js +2 -2
- package/src/api/core/core.service.js +2 -2
- package/src/api/crypto/crypto.controller.js +1 -1
- package/src/api/crypto/crypto.service.js +1 -1
- package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +1 -1
- package/src/api/cyberia-action/cyberia-action.service.js +2 -2
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
- package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +8 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.router.js +2 -2
- package/src/api/cyberia-instance/cyberia-instance.service.js +2 -2
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
- package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
- package/src/api/cyberia-map/cyberia-map.router.js +1 -1
- package/src/api/cyberia-map/cyberia-map.service.js +2 -2
- package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
- package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
- package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +0 -26
- package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
- package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
- package/src/api/default/default.controller.js +1 -1
- package/src/api/default/default.router.js +1 -1
- package/src/api/default/default.service.js +2 -2
- package/src/api/document/document.controller.js +1 -1
- package/src/api/document/document.router.js +1 -1
- package/src/api/document/document.service.js +2 -2
- package/src/api/file/file.controller.js +1 -1
- package/src/api/file/file.router.js +1 -1
- package/src/api/file/file.service.js +2 -2
- package/src/api/instance/instance.controller.js +1 -1
- package/src/api/instance/instance.router.js +1 -1
- package/src/api/instance/instance.service.js +2 -2
- package/src/api/ipfs/ipfs.controller.js +1 -1
- package/src/api/ipfs/ipfs.router.js +2 -2
- package/src/api/ipfs/ipfs.service.js +2 -2
- package/src/api/object-layer/object-layer.controller.js +1 -1
- package/src/api/object-layer/object-layer.router.js +2 -2
- package/src/api/object-layer/object-layer.service.js +3 -3
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
- package/src/api/test/test.controller.js +1 -1
- package/src/api/test/test.service.js +1 -1
- package/src/api/user/guest.service.js +3 -3
- package/src/api/user/user.controller.js +1 -1
- package/src/api/user/user.router.js +2 -2
- package/src/api/user/user.service.js +4 -4
- package/src/api.js +5 -9
- package/src/cli/app.js +391 -0
- package/src/cli/baremetal.js +30 -35
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +184 -82
- package/src/cli/db.js +120 -68
- package/src/cli/deploy.js +153 -71
- package/src/cli/docker-compose.js +5 -5
- package/src/cli/domains.js +184 -0
- package/src/cli/dotenv-store.js +143 -0
- package/src/cli/event.js +2281 -0
- package/src/cli/fs.js +3 -3
- package/src/cli/host.js +672 -0
- package/src/cli/image.js +4 -6
- package/src/cli/index.js +322 -142
- package/src/cli/ipfs.js +34 -30
- package/src/cli/kickstart.js +1 -1
- package/src/cli/kubectl.js +218 -41
- package/src/cli/lxd.js +3 -3
- package/src/cli/monitor.js +1359 -26
- package/src/cli/package.js +90 -0
- package/src/cli/release.js +16 -12
- package/src/cli/repository.js +558 -363
- package/src/cli/run.js +331 -650
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +478 -279
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +1 -1
- package/src/cli/test.js +199 -125
- package/src/cli/vultr.js +61 -21
- package/src/cli/wireguard.js +1111 -306
- package/src/client/components/core/CalendarCore.js +53 -96
- package/src/client/components/core/Docs.js +258 -211
- package/src/client/components/core/FileExplorer.js +0 -21
- package/src/client/components/core/PanelForm.js +4 -4
- package/src/client/components/core/Repository.js +56 -0
- package/src/client/components/core/Translate.js +7 -0
- package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
- package/src/client/components/core/Wallet.js +0 -11
- package/src/client/components/core/Worker.js +7 -1
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -1
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/default/AppShellDefault.js +1 -1
- package/src/client/components/default/SettingsDefault.js +2 -2
- package/src/client/components/itemledger/SettingsItemledger.js +2 -2
- package/src/client/components/underpost/AppShellUnderpost.js +3 -2
- package/src/client/components/underpost/SettingsUnderpost.js +2 -2
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
- package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/public/default/android-chrome-144x144.png +0 -0
- package/src/client/public/default/android-chrome-192x192.png +0 -0
- package/src/client/public/default/android-chrome-256x256.png +0 -0
- package/src/client/public/default/android-chrome-36x36.png +0 -0
- package/src/client/public/default/android-chrome-384x384.png +0 -0
- package/src/client/public/default/android-chrome-48x48.png +0 -0
- package/src/client/public/default/android-chrome-512x512.png +0 -0
- package/src/client/public/default/android-chrome-72x72.png +0 -0
- package/src/client/public/default/android-chrome-96x96.png +0 -0
- package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
- package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
- package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
- package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
- package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
- package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
- package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
- package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
- package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
- package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
- package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
- package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
- package/src/client/public/default/apple-touch-icon.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
- package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
- package/src/client/public/default/assets/background/white.jpg +0 -0
- package/src/client/public/default/browserconfig.xml +1 -1
- package/src/client/public/default/favicon-16x16.png +0 -0
- package/src/client/public/default/favicon-32x32.png +0 -0
- package/src/client/public/default/favicon-48x48.png +0 -0
- package/src/client/public/default/favicon.ico +0 -0
- package/src/client/public/default/manifest.webmanifest +5 -5
- package/src/client/public/default/mstile-144x144.png +0 -0
- package/src/client/public/default/mstile-150x150.png +0 -0
- package/src/client/public/default/mstile-310x150.png +0 -0
- package/src/client/public/default/mstile-310x310.png +0 -0
- package/src/client/public/default/mstile-70x70.png +0 -0
- package/src/client/public/default/yandex-browser-50x50.png +0 -0
- package/src/client/public/default/yandex-browser-manifest.json +1 -1
- package/src/client/ssr/RootDocument.js +1 -1
- package/src/client/ssr/body/404.js +1 -1
- package/src/client/ssr/body/500.js +1 -1
- package/src/client/ssr/body/CacheControl.js +1 -1
- package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
- package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
- package/src/client/ssr/head/CryptokoynScripts.js +1 -1
- package/src/client/ssr/head/Css.js +1 -1
- package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +1 -1
- package/src/client/ssr/head/ItemledgerScripts.js +1 -1
- package/src/client/ssr/head/Microdata.js +1 -1
- package/src/client/ssr/head/Production.js +1 -1
- package/src/client/ssr/head/Pwa.js +1 -1
- package/src/client/ssr/head/PwaItemledger.js +1 -1
- package/src/client/ssr/head/Seo.js +1 -1
- package/src/client/ssr/head/UnderpostScripts.js +1 -1
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
- package/src/client/ssr/views/Cyberia404.js +7 -5
- package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
- package/src/client/ssr/views/Maintenance.js +1 -1
- package/src/client/ssr/views/NoNetworkConnection.js +1 -1
- package/src/client/ssr/views/Test.js +1 -1
- package/src/client/sw/core.sw.js +20 -14
- package/src/client-builder/client-build-docs.js +61 -46
- package/src/client-builder/client-build-live.js +2 -2
- package/src/client-builder/client-build.js +38 -30
- package/src/client-builder/client-dev-server.js +3 -3
- package/src/client-builder/client-icons.js +2 -2
- package/src/client-builder/ssr.js +3 -3
- package/src/client.build.js +3 -6
- package/src/client.dev.js +3 -6
- package/src/db/DataBaseProvider.js +186 -3
- package/src/db/mariadb/MariaDB.js +3 -2
- package/src/db/mongo/MongoBootstrap.js +273 -101
- package/src/db/mongo/MongooseDB.js +7 -8
- package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
- package/src/grpc/cyberia/grpc-server.js +1 -1
- package/src/index.js +79 -23
- package/src/mailer/EmailRender.js +2 -2
- package/src/mailer/MailerInterceptor.js +87 -0
- package/src/mailer/MailerProvider.js +50 -6
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +1 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -3
- package/src/projects/cyberia/catalog-cyberia.js +53 -10
- package/src/projects/cyberia/gemini-client.js +1 -1
- package/src/projects/cyberia/generate-saga.js +1 -1
- package/src/projects/cyberia/hot-reload-trigger.js +1 -1
- package/src/projects/cyberia/instance-data.js +1 -1
- package/src/projects/cyberia/ipfs-client.js +4 -4
- package/src/projects/cyberia/map-preview-generator.js +1 -1
- package/src/projects/cyberia/object-layer.js +1 -1
- package/src/projects/cyberia/semantic-layer-generator.js +1 -1
- package/src/projects/underpost/catalog-underpost.js +9 -1
- package/src/proxy.js +4 -7
- package/src/runtime/cyberia-client/Dockerfile +9 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -4
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
- package/src/runtime/engine-cyberia/Dockerfile +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
- package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
- package/src/runtime/engine-cyberia/docker-compose.yml +7 -7
- package/src/runtime/express/Express.js +32 -27
- package/src/runtime/lampp/Lampp.js +22 -70
- package/src/runtime/nginx/Nginx.js +1 -1
- package/src/runtime/wp/Wp.js +16 -48
- package/src/server/{catalog.js → build/catalog.js} +15 -5
- package/src/server/build/coverage.js +117 -0
- package/src/server/build/execution.js +402 -0
- package/src/server/build/package.js +465 -0
- package/src/server/build/testing.js +528 -0
- package/src/server/{dns.js → network/dns.js} +144 -11
- package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
- package/src/server/{middlewares.js → network/middlewares.js} +3 -3
- package/src/server/network/node-capability.js +98 -0
- package/src/server/{peer.js → network/peer.js} +3 -3
- package/src/server/{proxy.js → network/proxy.js} +7 -13
- package/src/server/network/router.js +356 -0
- package/src/server/{tls.js → network/tls.js} +2 -2
- package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/{cron.js → ops/cron.js} +349 -74
- package/src/server/ops/event-notification.js +284 -0
- package/src/server/{logger.js → ops/logger.js} +108 -23
- package/src/server/ops/monitoring.js +1724 -0
- package/src/server/{systemd.js → ops/systemd.js} +55 -1
- package/src/server/{conf.js → runtime/conf.js} +513 -422
- package/src/server/runtime/config-scope.js +215 -0
- package/src/server/runtime/environment.js +149 -0
- package/src/server/{process.js → runtime/process.js} +153 -12
- package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
- package/src/server/{runtime.js → runtime/runtime.js} +12 -8
- package/src/server/{start.js → runtime/start.js} +128 -39
- package/src/server/{auth.js → security/auth.js} +7 -8
- package/src/server/security/container-storage.js +382 -0
- package/src/server/{crypto.js → security/crypto.js} +1 -1
- package/src/server/{selinux.js → security/selinux.js} +1 -1
- package/src/server/{backup.js → storage/backup.js} +17 -8
- package/src/server/{data-query.js → storage/data-query.js} +1 -1
- package/src/server/{downloader.js → storage/downloader.js} +2 -2
- package/src/server/storage/repository.js +68 -0
- package/src/server.js +4 -7
- package/src/ws/IoInterface.js +1 -1
- package/src/ws/IoServer.js +1 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/src/ws/default/default.ws.connection.js +1 -1
- package/src/ws/default/default.ws.emit.js +1 -1
- package/src/ws/default/default.ws.server.js +1 -1
- package/test/e2e/event-e2e-public-ingress-down.js +45 -0
- package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
- package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
- package/test/{api.test.js → integration/app/api.test.js} +33 -14
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +30 -0
- package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +1 -1
- package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
- package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
- package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
- package/test/integration/infra/1-security/config-scope.test.js +305 -0
- package/test/integration/infra/1-security/container-storage.test.js +356 -0
- package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
- package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
- package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
- package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
- package/test/integration/infra/1-security/systemd-service.test.js +70 -0
- package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
- package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
- package/test/integration/infra/2-network/node-capability.test.js +182 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +1163 -0
- package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +609 -55
- package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
- package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
- package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
- package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
- package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
- package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
- package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
- package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
- package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
- package/test/integration/infra/5-observability/event-notification.test.js +197 -0
- package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
- package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
- package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
- package/test/support/shell-harness.js +73 -0
- package/test/unit/build-template.test.js +97 -0
- package/test/unit/build-workflow-offline.test.js +132 -0
- package/test/unit/catalog.test.js +71 -0
- package/test/unit/client-build-docs.test.js +334 -0
- package/test/unit/conf-loading.test.js +303 -0
- package/test/unit/conf-resolution.test.js +979 -0
- package/test/unit/coverage-artifact.test.js +130 -0
- package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
- package/test/unit/deploy-env-resolution.test.js +38 -0
- package/test/unit/deploy-log-table.test.js +447 -0
- package/test/unit/execution-profiles.test.js +219 -0
- package/test/unit/fleet-sync-source.test.js +149 -0
- package/test/unit/logger-redaction.test.js +112 -0
- package/test/unit/package.test.js +403 -0
- package/test/unit/prepare-host.test.js +299 -0
- package/test/unit/process-environment.test.js +18 -0
- package/test/unit/propagation-message.test.js +78 -0
- package/test/unit/redeploy-plan.test.js +24 -0
- package/test/unit/release-bump.test.js +474 -0
- package/test/unit/ssh-output-redaction.test.js +28 -0
- package/test/unit/start-options.test.js +46 -0
- package/test/unit/test-tiers.test.js +193 -0
- package/vitest.config.js +58 -0
- package/.nycrc +0 -9
- package/deploy/lib/logging.sh +0 -96
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- package/scripts/coverall-test-cyberia.sh +0 -24
- package/scripts/coverall-test.sh +0 -24
- package/scripts/link-local-underpost-cli.sh +0 -6
- package/src/api/user/user.build.js +0 -16
- package/src/cli/env.js +0 -177
- package/src/client/components/core/Blockchain.js +0 -41
- package/src/client/components/core/Blog.js +0 -9
- package/src/client/components/core/KeyboardAvoidance.js +0 -145
- package/src/client/public/default/assets/background/dark.jpg +0 -0
- package/src/client/public/default/assets/background/white0-min.jpg +0 -0
- package/src/client/public/default/assets/background/white0.jpg +0 -0
- package/src/client/public/doc/favicon.ico +0 -0
- package/src/client/public/doc/sitemap +0 -148
- package/src/server/environment.js +0 -98
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'chai';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import { Dns, UnderpostDns, getLocalIPv4Address, isInternetConnection } from '../../../../src/server/network/dns.js';
|
|
8
|
+
import UnderpostHost from '../../../../src/cli/host.js';
|
|
9
|
+
import { shellHarness } from '../../../support/shell-harness.js';
|
|
10
|
+
|
|
11
|
+
// Every nftables static shells out and every DDNS provider talks HTTP, so the
|
|
12
|
+
// suite replaces exactly those two boundaries: what is asserted is the command
|
|
13
|
+
// vector and the branch that produced it, never the host's real firewall.
|
|
14
|
+
const IFACE = 'eth-fixture';
|
|
15
|
+
const ROUTE_ROUTE = { match: 'ip route', stdout: `${IFACE}\n` };
|
|
16
|
+
|
|
17
|
+
const CONF_DIR = (deployId) => `./engine-private/conf/${deployId}`;
|
|
18
|
+
|
|
19
|
+
describe('dns edge firewall and dynamic DNS', () => {
|
|
20
|
+
let harness;
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
harness?.restore();
|
|
24
|
+
harness = undefined;
|
|
25
|
+
vi.restoreAllMocks();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('host interface discovery', () => {
|
|
29
|
+
it('reads the default route interface with logging suppressed', () => {
|
|
30
|
+
harness = shellHarness([ROUTE_ROUTE]);
|
|
31
|
+
expect(Dns.getDefaultNetworkInterface()).to.equal(IFACE);
|
|
32
|
+
expect(harness.ran('grep default')).to.equal(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('returns the IPv4 address bound to the default interface', () => {
|
|
36
|
+
harness = shellHarness([ROUTE_ROUTE]);
|
|
37
|
+
vi.spyOn(os, 'networkInterfaces').mockReturnValue({
|
|
38
|
+
[IFACE]: [
|
|
39
|
+
{ family: 'IPv6', address: 'fe80::1', mac: 'aa:bb:cc:dd:ee:ff' },
|
|
40
|
+
{ family: 'IPv4', address: '10.0.0.8', mac: 'aa:bb:cc:dd:ee:ff' },
|
|
41
|
+
],
|
|
42
|
+
});
|
|
43
|
+
expect(Dns.getLocalIPv4Address()).to.equal('10.0.0.8');
|
|
44
|
+
expect(getLocalIPv4Address).to.equal(Dns.getLocalIPv4Address);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('returns null when the default interface is not present', () => {
|
|
48
|
+
harness = shellHarness([ROUTE_ROUTE]);
|
|
49
|
+
vi.spyOn(os, 'networkInterfaces').mockReturnValue({});
|
|
50
|
+
expect(Dns.getLocalIPv4Address()).to.equal(null);
|
|
51
|
+
expect(Dns.getMainInterfaceMac()).to.equal(null);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns null when the interface carries no IPv4 address', () => {
|
|
55
|
+
harness = shellHarness([ROUTE_ROUTE]);
|
|
56
|
+
vi.spyOn(os, 'networkInterfaces').mockReturnValue({
|
|
57
|
+
[IFACE]: [{ family: 'IPv6', address: 'fe80::1', mac: '00:11:22:33:44:55' }],
|
|
58
|
+
});
|
|
59
|
+
expect(Dns.getLocalIPv4Address()).to.equal(null);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('reads the MAC off the first entry of the default interface', () => {
|
|
63
|
+
harness = shellHarness([ROUTE_ROUTE]);
|
|
64
|
+
vi.spyOn(os, 'networkInterfaces').mockReturnValue({
|
|
65
|
+
[IFACE]: [{ family: 'IPv4', address: '10.0.0.8', mac: '00:11:22:33:44:55' }],
|
|
66
|
+
});
|
|
67
|
+
expect(Dns.getMainInterfaceMac()).to.equal('00:11:22:33:44:55');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('resolves the public IP from the configured plain-IP endpoint', async () => {
|
|
71
|
+
const get = vi.spyOn(axios, 'get').mockResolvedValue({ data: '203.0.113.9' });
|
|
72
|
+
process.env.HTTP_PLAIN_IP_URL = 'https://ip.fixture.test';
|
|
73
|
+
try {
|
|
74
|
+
expect(await Dns.getPublicIp()).to.equal('203.0.113.9');
|
|
75
|
+
expect(get.mock.calls[0][0]).to.equal('https://ip.fixture.test');
|
|
76
|
+
} finally {
|
|
77
|
+
delete process.env.HTTP_PLAIN_IP_URL;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('falls back to ipify when no endpoint is configured', async () => {
|
|
82
|
+
const get = vi.spyOn(axios, 'get').mockResolvedValue({ data: '198.51.100.4' });
|
|
83
|
+
expect(await Dns.getPublicIp()).to.equal('198.51.100.4');
|
|
84
|
+
expect(get.mock.calls[0][0]).to.equal('https://api.ipify.org');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('resolves null instead of throwing when the lookup fails synchronously', async () => {
|
|
88
|
+
vi.spyOn(axios, 'get').mockImplementation(() => {
|
|
89
|
+
throw new Error('offline');
|
|
90
|
+
});
|
|
91
|
+
expect(await Dns.getPublicIp()).to.equal(null);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('reports connectivity from a DNS lookup', async () => {
|
|
95
|
+
expect(await Dns.isInternetConnection('localhost')).to.equal(true);
|
|
96
|
+
expect(await isInternetConnection('invalid.host.that.does.not.resolve.fixture')).to.equal(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('per-IP bans', () => {
|
|
101
|
+
it('creates the base table and chains before installing an ingress drop', () => {
|
|
102
|
+
harness = shellHarness();
|
|
103
|
+
Dns.banIngress('192.0.2.10');
|
|
104
|
+
expect(harness.ran('nft add table inet filter')).to.equal(true);
|
|
105
|
+
expect(harness.ran('nft add rule inet filter input ip saddr 192.0.2.10 counter drop')).to.equal(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('installs egress drops on both the output and forward chains', () => {
|
|
109
|
+
harness = shellHarness();
|
|
110
|
+
Dns.banEgress('192.0.2.11');
|
|
111
|
+
expect(harness.ran('output ip daddr 192.0.2.11 counter drop')).to.equal(true);
|
|
112
|
+
expect(harness.ran('forward ip daddr 192.0.2.11 counter drop')).to.equal(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('installs no rule for a malformed address', () => {
|
|
116
|
+
harness = shellHarness();
|
|
117
|
+
Dns.banIngress('not-an-ip');
|
|
118
|
+
Dns.banEgress('999.999.999.999');
|
|
119
|
+
expect(harness.ran('counter drop')).to.equal(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('parses every handle out of an annotated chain listing', () => {
|
|
123
|
+
harness = shellHarness([
|
|
124
|
+
{
|
|
125
|
+
match: 'nft -a list chain inet filter input',
|
|
126
|
+
stdout: [
|
|
127
|
+
'table inet filter {',
|
|
128
|
+
' chain input {',
|
|
129
|
+
' ip saddr 192.0.2.10 counter packets 0 bytes 0 drop # handle 5',
|
|
130
|
+
' ip saddr 192.0.2.10 counter packets 0 bytes 0 drop # handle 9',
|
|
131
|
+
' ip saddr 198.51.100.1 counter packets 0 bytes 0 drop # handle 12',
|
|
132
|
+
' }',
|
|
133
|
+
'}',
|
|
134
|
+
].join('\n'),
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
expect(Dns.getNftHandles('input', '192.0.2.10', 'saddr')).to.deep.equal(['5', '9']);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('deletes every matching ingress handle', () => {
|
|
141
|
+
harness = shellHarness([
|
|
142
|
+
{
|
|
143
|
+
match: 'nft -a list chain inet filter input',
|
|
144
|
+
stdout: 'ip saddr 192.0.2.10 counter drop # handle 5\nip saddr 192.0.2.10 counter drop # handle 6\n',
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
Dns.unbanIngress('192.0.2.10');
|
|
148
|
+
expect(harness.ran('delete rule inet filter input handle 5')).to.equal(true);
|
|
149
|
+
expect(harness.ran('delete rule inet filter input handle 6')).to.equal(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('deletes egress handles from the output and forward chains', () => {
|
|
153
|
+
harness = shellHarness([
|
|
154
|
+
{ match: 'nft -a list chain inet filter output', stdout: 'ip daddr 192.0.2.11 counter drop # handle 3\n' },
|
|
155
|
+
{ match: 'nft -a list chain inet filter forward', stdout: 'ip daddr 192.0.2.11 counter drop # handle 4\n' },
|
|
156
|
+
]);
|
|
157
|
+
Dns.unbanEgress('192.0.2.11');
|
|
158
|
+
expect(harness.ran('delete rule inet filter output handle 3')).to.equal(true);
|
|
159
|
+
expect(harness.ran('delete rule inet filter forward handle 4')).to.equal(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('lists and flushes the ban chains', () => {
|
|
163
|
+
harness = shellHarness([{ match: 'nft list chain', stdout: 'chain listing\n' }]);
|
|
164
|
+
Dns.listBannedIngress();
|
|
165
|
+
Dns.listBannedEgress();
|
|
166
|
+
Dns.clearBannedIngress();
|
|
167
|
+
Dns.clearBannedEgress();
|
|
168
|
+
expect(harness.count('nft list chain')).to.equal(3);
|
|
169
|
+
expect(harness.ran('flush chain inet filter input')).to.equal(true);
|
|
170
|
+
expect(harness.ran('flush chain inet filter output')).to.equal(true);
|
|
171
|
+
expect(harness.ran('flush chain inet filter forward')).to.equal(true);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('filter chain provisioning', () => {
|
|
176
|
+
it('creates nothing when both chains already exist', () => {
|
|
177
|
+
harness = shellHarness([{ match: 'nft list chain inet filter', code: 0 }]);
|
|
178
|
+
expect(Dns.ensureFilterChains()).to.equal(true);
|
|
179
|
+
expect(harness.ran('nft add chain')).to.equal(false);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('adds the missing chains on a firewalld host and re-reads them back', () => {
|
|
183
|
+
// The probe answers "missing" until the chain has been created, so the
|
|
184
|
+
// final `chains.every(chainExists)` re-read is the one that reports true.
|
|
185
|
+
const present = new Set();
|
|
186
|
+
harness = shellHarness([
|
|
187
|
+
{ match: /nft list chain inet filter (\w+)/, code: 1 },
|
|
188
|
+
{ match: 'nft add chain inet filter', code: 0 },
|
|
189
|
+
]);
|
|
190
|
+
harness.route({
|
|
191
|
+
match: (command) => {
|
|
192
|
+
const chain = /nft list chain inet filter (\w+)/.exec(command)?.[1];
|
|
193
|
+
return chain !== undefined && present.has(chain);
|
|
194
|
+
},
|
|
195
|
+
code: 0,
|
|
196
|
+
});
|
|
197
|
+
harness.route({
|
|
198
|
+
match: (command) => {
|
|
199
|
+
const chain = /nft add chain inet filter (\w+)/.exec(command)?.[1];
|
|
200
|
+
if (chain) present.add(chain);
|
|
201
|
+
return false;
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(Dns.ensureFilterChains(['output', 'forward'])).to.equal(true);
|
|
206
|
+
expect(harness.ran('nft add table inet filter')).to.equal(true);
|
|
207
|
+
expect(harness.ran('hook output priority 0')).to.equal(true);
|
|
208
|
+
expect(harness.ran('hook forward priority 0')).to.equal(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('reports failure when a created chain still does not read back', () => {
|
|
212
|
+
harness = shellHarness([
|
|
213
|
+
{ match: 'nft list chain', code: 1 },
|
|
214
|
+
{ match: 'nft add', code: 0 },
|
|
215
|
+
]);
|
|
216
|
+
expect(Dns.ensureFilterChains(['output'])).to.equal(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('reports failure when the base table cannot be created', () => {
|
|
220
|
+
harness = shellHarness([
|
|
221
|
+
{ match: 'nft list chain', code: 1 },
|
|
222
|
+
{ match: 'nft add table inet filter', code: 1 },
|
|
223
|
+
]);
|
|
224
|
+
expect(Dns.ensureFilterChains()).to.equal(false);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('reports failure when a chain cannot be created', () => {
|
|
228
|
+
harness = shellHarness([
|
|
229
|
+
{ match: 'nft list chain', code: 1 },
|
|
230
|
+
{ match: 'nft add table inet filter', code: 0 },
|
|
231
|
+
{ match: 'nft add chain', code: 1 },
|
|
232
|
+
]);
|
|
233
|
+
expect(Dns.ensureFilterChains(['input'])).to.equal(false);
|
|
234
|
+
expect(harness.ran('hook input priority 0')).to.equal(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('reads the policy currently latched on a chain', () => {
|
|
238
|
+
harness = shellHarness([{ match: 'nft list chain inet filter output', stdout: 'policy drop;\n' }]);
|
|
239
|
+
expect(Dns.chainPolicyIs('output', 'drop')).to.equal(true);
|
|
240
|
+
expect(Dns.chainPolicyIs('output', 'accept')).to.equal(false);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('whole-host egress control', () => {
|
|
245
|
+
it('drops both egress chains and reads the policy back', () => {
|
|
246
|
+
harness = shellHarness([{ match: 'nft list chain', code: 0, stdout: 'policy drop;\n' }]);
|
|
247
|
+
Dns.blockAllEgress();
|
|
248
|
+
expect(harness.ran('ct state established,related counter accept')).to.equal(true);
|
|
249
|
+
expect(harness.ran(`nft chain inet filter output '{ policy drop; }'`)).to.equal(true);
|
|
250
|
+
expect(harness.ran(`nft chain inet filter forward '{ policy drop; }'`)).to.equal(true);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('refuses to report a block the host never applied', () => {
|
|
254
|
+
harness = shellHarness([{ match: 'nft list chain', code: 0, stdout: 'policy accept;\n' }]);
|
|
255
|
+
expect(() => Dns.blockAllEgress()).to.throw('is not');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('refuses to report a block when the chains could not be created', () => {
|
|
259
|
+
harness = shellHarness([
|
|
260
|
+
{ match: 'nft list chain', code: 1 },
|
|
261
|
+
{ match: 'nft add table', code: 1 },
|
|
262
|
+
]);
|
|
263
|
+
expect(() => Dns.blockAllEgress()).to.throw('egress is NOT blocked');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('restores both egress chains to accept', () => {
|
|
267
|
+
harness = shellHarness([{ match: 'nft list chain', code: 0, stdout: 'policy accept;\n' }]);
|
|
268
|
+
Dns.unblockAllEgress();
|
|
269
|
+
expect(harness.ran(`nft chain inet filter output '{ policy accept; }'`)).to.equal(true);
|
|
270
|
+
expect(harness.ran(`nft chain inet filter forward '{ policy accept; }'`)).to.equal(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('reports a host that was never blocked instead of failing on a missing table', () => {
|
|
274
|
+
harness = shellHarness([
|
|
275
|
+
{ match: 'nft list chain', code: 1 },
|
|
276
|
+
{ match: 'nft add table', code: 1 },
|
|
277
|
+
]);
|
|
278
|
+
expect(() => Dns.unblockAllEgress()).not.to.throw();
|
|
279
|
+
expect(harness.ran(`policy accept; }'`)).to.equal(false);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('throws when the restore did not take', () => {
|
|
283
|
+
harness = shellHarness([{ match: 'nft list chain', code: 0, stdout: 'policy drop;\n' }]);
|
|
284
|
+
expect(() => Dns.unblockAllEgress()).to.throw('egress remains blocked');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('ingress control', () => {
|
|
289
|
+
it('creates the input chain before every ingress command', () => {
|
|
290
|
+
harness = shellHarness();
|
|
291
|
+
Dns.ensureIngressChain();
|
|
292
|
+
expect(harness.ran('nft add table inet filter')).to.equal(true);
|
|
293
|
+
expect(harness.ran('hook input priority 0')).to.equal(true);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('drops new inbound traffic while keeping established sessions', () => {
|
|
297
|
+
harness = shellHarness();
|
|
298
|
+
Dns.blockAllIngress();
|
|
299
|
+
expect(harness.ran('flush chain inet filter input')).to.equal(true);
|
|
300
|
+
expect(harness.ran('input ct state established,related counter accept')).to.equal(true);
|
|
301
|
+
expect(harness.ran(`nft chain inet filter input '{ policy drop; }'`)).to.equal(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('restores the input chain policy', () => {
|
|
305
|
+
harness = shellHarness();
|
|
306
|
+
Dns.unblockAllIngress();
|
|
307
|
+
expect(harness.ran(`nft chain inet filter input '{ policy accept; }'`)).to.equal(true);
|
|
308
|
+
expect(harness.ran('flush chain inet filter input')).to.equal(true);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('keeps only valid TCP ports out of a comma separated list', () => {
|
|
312
|
+
expect(Dns.portListFactory('80, 443,0,65536,abc,8080')).to.deep.equal([80, 443, 8080]);
|
|
313
|
+
expect(Dns.portListFactory()).to.deep.equal([]);
|
|
314
|
+
expect(Dns.portListFactory('')).to.deep.equal([]);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('inserts one drop per blocked port so the management path survives', () => {
|
|
318
|
+
harness = shellHarness();
|
|
319
|
+
Dns.blockIngressPort('80,443');
|
|
320
|
+
expect(harness.ran('insert rule inet filter input tcp dport 80 counter drop')).to.equal(true);
|
|
321
|
+
expect(harness.ran('insert rule inet filter input tcp dport 443 counter drop')).to.equal(true);
|
|
322
|
+
expect(harness.ran('policy drop')).to.equal(false);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('withdraws the port rules by handle', () => {
|
|
326
|
+
harness = shellHarness();
|
|
327
|
+
Dns.unblockIngressPort('443');
|
|
328
|
+
expect(harness.ran('tcp dport 443 .*drop')).to.equal(true);
|
|
329
|
+
expect(harness.ran('xargs -r -n1 nft delete rule inet filter input handle')).to.equal(true);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe('dondominio record updates', () => {
|
|
334
|
+
const RECORD = {
|
|
335
|
+
user: 'ddns-user',
|
|
336
|
+
api_key: 'ddns-key',
|
|
337
|
+
host: 'fixture.test',
|
|
338
|
+
dns: 'dondominio',
|
|
339
|
+
ip: '203.0.113.9',
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
it('aborts without credentials', async () => {
|
|
343
|
+
expect(await Dns.services.updateIp.dondominio({ ...RECORD, user: '', api_key: '' })).to.equal(false);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('aborts without a host', async () => {
|
|
347
|
+
expect(await Dns.services.updateIp.dondominio({ ...RECORD, host: '' })).to.equal(false);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('never reaches the provider outside production', async () => {
|
|
351
|
+
const get = vi.spyOn(axios, 'get');
|
|
352
|
+
expect(await Dns.services.updateIp.dondominio(RECORD)).to.equal(false);
|
|
353
|
+
expect(get.mock.calls.length).to.equal(0);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('sends the update in production and reports success', async () => {
|
|
357
|
+
const get = vi.spyOn(axios, 'get').mockResolvedValue({ data: { success: true } });
|
|
358
|
+
const previous = process.env.NODE_ENV;
|
|
359
|
+
process.env.NODE_ENV = 'production';
|
|
360
|
+
try {
|
|
361
|
+
expect(await Dns.services.updateIp.dondominio(RECORD)).to.equal(true);
|
|
362
|
+
expect(get.mock.calls[0][0]).to.include('dondns.dondominio.com');
|
|
363
|
+
expect(get.mock.calls[0][0]).to.include('ip=203.0.113.9');
|
|
364
|
+
} finally {
|
|
365
|
+
process.env.NODE_ENV = previous;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('reports a provider failure without surfacing the credentialed URL', async () => {
|
|
370
|
+
vi.spyOn(axios, 'get').mockRejectedValue(new Error('502 bad gateway'));
|
|
371
|
+
const previous = process.env.NODE_ENV;
|
|
372
|
+
process.env.NODE_ENV = 'production';
|
|
373
|
+
try {
|
|
374
|
+
expect(await Dns.services.updateIp.dondominio(RECORD)).to.equal(false);
|
|
375
|
+
} finally {
|
|
376
|
+
process.env.NODE_ENV = previous;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe('dynamic DNS callback', () => {
|
|
382
|
+
const DEPLOY_ID = 'dd-fixture-dns';
|
|
383
|
+
const dir = CONF_DIR(DEPLOY_ID);
|
|
384
|
+
|
|
385
|
+
beforeEach(() => {
|
|
386
|
+
if (fs.existsSync(dir)) throw new Error(`Refusing to write fixtures into an existing deploy: ${dir}`);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
afterEach(() => fs.removeSync(dir));
|
|
390
|
+
|
|
391
|
+
const stubEnv = (ip) => {
|
|
392
|
+
vi.spyOn(UnderpostHost.API.store, 'get').mockImplementation((key) => (key === 'ip' ? ip : ''));
|
|
393
|
+
vi.spyOn(UnderpostHost.API.store, 'set').mockImplementation(() => undefined);
|
|
394
|
+
vi.spyOn(UnderpostHost.API.store, 'delete').mockImplementation(() => undefined);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
it('does nothing while the host is offline', async () => {
|
|
398
|
+
vi.spyOn(Dns, 'isInternetConnection').mockResolvedValue(false);
|
|
399
|
+
const publicIp = vi.spyOn(Dns, 'getPublicIp');
|
|
400
|
+
await Dns.callback(DEPLOY_ID);
|
|
401
|
+
expect(publicIp.mock.calls.length).to.equal(0);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('does nothing when the public IP is unchanged', async () => {
|
|
405
|
+
vi.spyOn(Dns, 'isInternetConnection').mockResolvedValue(true);
|
|
406
|
+
vi.spyOn(Dns, 'getPublicIp').mockResolvedValue('203.0.113.9');
|
|
407
|
+
stubEnv('203.0.113.9');
|
|
408
|
+
const set = vi.spyOn(UnderpostHost.API.store, 'set');
|
|
409
|
+
await Dns.callback(DEPLOY_ID);
|
|
410
|
+
expect(set.mock.calls.length).to.equal(0);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('skips a deploy with no cron conf instead of aborting the run', async () => {
|
|
414
|
+
vi.spyOn(Dns, 'isInternetConnection').mockResolvedValue(true);
|
|
415
|
+
vi.spyOn(Dns, 'getPublicIp').mockResolvedValue('203.0.113.9');
|
|
416
|
+
stubEnv('198.51.100.1');
|
|
417
|
+
await Dns.callback(`${DEPLOY_ID}, ${DEPLOY_ID}-missing`);
|
|
418
|
+
expect(fs.existsSync(dir)).to.equal(false);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('skips a cron conf carrying no records', async () => {
|
|
422
|
+
fs.outputJsonSync(`${dir}/conf.cron.json`, { jobs: {} });
|
|
423
|
+
vi.spyOn(Dns, 'isInternetConnection').mockResolvedValue(true);
|
|
424
|
+
vi.spyOn(Dns, 'getPublicIp').mockResolvedValue('203.0.113.9');
|
|
425
|
+
stubEnv('198.51.100.1');
|
|
426
|
+
const update = vi.spyOn(Dns.services.updateIp, 'dondominio');
|
|
427
|
+
await Dns.callback(DEPLOY_ID);
|
|
428
|
+
expect(update.mock.calls.length).to.equal(0);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('drives every A record provider and latches the verified address', async () => {
|
|
432
|
+
fs.outputJsonSync(`${dir}/conf.cron.json`, {
|
|
433
|
+
records: {
|
|
434
|
+
A: [{ dns: 'dondominio', user: 'u', api_key: 'k', host: 'fixture.test' }],
|
|
435
|
+
AAAA: [{ dns: 'dondominio' }],
|
|
436
|
+
TXT: [{ dns: 'unknown-provider' }],
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
vi.spyOn(Dns, 'isInternetConnection').mockResolvedValue(true);
|
|
440
|
+
vi.spyOn(Dns, 'getPublicIp').mockResolvedValue('203.0.113.9');
|
|
441
|
+
stubEnv('198.51.100.1');
|
|
442
|
+
const update = vi.spyOn(Dns.services.updateIp, 'dondominio').mockResolvedValue(true);
|
|
443
|
+
vi.spyOn(axios, 'get').mockResolvedValue({ request: { socket: { remoteAddress: '203.0.113.9' } } });
|
|
444
|
+
const set = vi.spyOn(UnderpostHost.API.store, 'set');
|
|
445
|
+
|
|
446
|
+
await Dns.callback(DEPLOY_ID);
|
|
447
|
+
|
|
448
|
+
expect(update.mock.calls.length).to.equal(1);
|
|
449
|
+
expect(update.mock.calls[0][0].ip).to.equal('203.0.113.9');
|
|
450
|
+
expect(set.mock.calls.some(([key, value]) => key === 'ip' && value === '203.0.113.9')).to.equal(true);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('leaves the address unlatched when the external check disagrees', async () => {
|
|
454
|
+
fs.outputJsonSync(`${dir}/conf.cron.json`, { records: { A: [] } });
|
|
455
|
+
vi.spyOn(Dns, 'isInternetConnection').mockResolvedValue(true);
|
|
456
|
+
vi.spyOn(Dns, 'getPublicIp').mockResolvedValue('203.0.113.9');
|
|
457
|
+
stubEnv('198.51.100.1');
|
|
458
|
+
vi.spyOn(axios, 'get').mockResolvedValue({ request: { socket: { remoteAddress: '198.51.100.1' } } });
|
|
459
|
+
const set = vi.spyOn(UnderpostHost.API.store, 'set');
|
|
460
|
+
await Dns.callback(DEPLOY_ID);
|
|
461
|
+
expect(set.mock.calls.some(([key]) => key === 'ip')).to.equal(false);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('survives a failed verification request', async () => {
|
|
465
|
+
fs.outputJsonSync(`${dir}/conf.cron.json`, { records: { A: [] } });
|
|
466
|
+
vi.spyOn(Dns, 'isInternetConnection').mockResolvedValue(true);
|
|
467
|
+
vi.spyOn(Dns, 'getPublicIp').mockResolvedValue('203.0.113.9');
|
|
468
|
+
stubEnv('198.51.100.1');
|
|
469
|
+
vi.spyOn(axios, 'get').mockRejectedValue(new Error('unreachable'));
|
|
470
|
+
await Dns.callback(DEPLOY_ID);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('recovers when the public IP lookup itself rejects', async () => {
|
|
474
|
+
vi.spyOn(Dns, 'isInternetConnection').mockResolvedValue(true);
|
|
475
|
+
vi.spyOn(Dns, 'getPublicIp').mockRejectedValue(new Error('lookup failed'));
|
|
476
|
+
stubEnv('198.51.100.1');
|
|
477
|
+
await Dns.callback(DEPLOY_ID);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe('ip dispatcher', () => {
|
|
482
|
+
const dispatch = (options, ips = '192.0.2.10,192.0.2.11') => Dns.ipDispatcher(ips, options);
|
|
483
|
+
|
|
484
|
+
it('exposes the Dns API on the CLI class', () => {
|
|
485
|
+
expect(UnderpostDns.API).to.equal(Dns);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('routes each per-IP ban action to its chain', async () => {
|
|
489
|
+
for (const [option, expected] of [
|
|
490
|
+
['banIngressAdd', 'input ip saddr 192.0.2.10'],
|
|
491
|
+
['banEgressAdd', 'output ip daddr 192.0.2.10'],
|
|
492
|
+
]) {
|
|
493
|
+
harness = shellHarness();
|
|
494
|
+
await dispatch({ [option]: true });
|
|
495
|
+
expect(harness.ran(expected), option).to.equal(true);
|
|
496
|
+
harness.restore();
|
|
497
|
+
}
|
|
498
|
+
harness = undefined;
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('routes each unban action through the handle lookup', async () => {
|
|
502
|
+
for (const option of ['banIngressRemove', 'banEgressRemove']) {
|
|
503
|
+
harness = shellHarness();
|
|
504
|
+
await dispatch({ [option]: true });
|
|
505
|
+
expect(harness.ran('nft -a list chain'), option).to.equal(true);
|
|
506
|
+
harness.restore();
|
|
507
|
+
}
|
|
508
|
+
harness = undefined;
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('bans and unbans both directions in one call', async () => {
|
|
512
|
+
harness = shellHarness();
|
|
513
|
+
await dispatch({ banBothAdd: true }, '192.0.2.10');
|
|
514
|
+
expect(harness.ran('input ip saddr 192.0.2.10')).to.equal(true);
|
|
515
|
+
expect(harness.ran('output ip daddr 192.0.2.10')).to.equal(true);
|
|
516
|
+
harness.restore();
|
|
517
|
+
|
|
518
|
+
harness = shellHarness();
|
|
519
|
+
await dispatch({ banBothRemove: true }, '192.0.2.10');
|
|
520
|
+
expect(harness.count('nft -a list chain')).to.equal(3);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it('routes the list and clear actions', async () => {
|
|
524
|
+
harness = shellHarness([{ match: 'nft list chain', stdout: 'listing\n' }]);
|
|
525
|
+
await dispatch({ banIngressList: true }, '');
|
|
526
|
+
await dispatch({ banEgressList: true }, '');
|
|
527
|
+
await dispatch({ banIngressClear: true }, '');
|
|
528
|
+
await dispatch({ banEgressClear: true }, '');
|
|
529
|
+
expect(harness.count('nft list chain')).to.equal(3);
|
|
530
|
+
expect(harness.count('flush chain')).to.equal(3);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('routes the whole-host and per-port controls', async () => {
|
|
534
|
+
harness = shellHarness([{ match: 'nft list chain', code: 0, stdout: 'policy drop;\npolicy accept;\n' }]);
|
|
535
|
+
await dispatch({ blockAllEgress: true }, '');
|
|
536
|
+
await dispatch({ unblockAllEgress: true }, '');
|
|
537
|
+
await dispatch({ blockAllIngress: true }, '');
|
|
538
|
+
await dispatch({ unblockAllIngress: true }, '');
|
|
539
|
+
await dispatch({ blockIngressPort: '80' }, '');
|
|
540
|
+
await dispatch({ unblockIngressPort: '80' }, '');
|
|
541
|
+
expect(harness.ran('tcp dport 80 counter drop')).to.equal(true);
|
|
542
|
+
expect(harness.ran('xargs -r -n1 nft delete rule')).to.equal(true);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('prints the MAC of the main interface', async () => {
|
|
546
|
+
harness = shellHarness([ROUTE_ROUTE]);
|
|
547
|
+
vi.spyOn(os, 'networkInterfaces').mockReturnValue({
|
|
548
|
+
[IFACE]: [{ family: 'IPv4', address: '10.0.0.8', mac: '00:11:22:33:44:55' }],
|
|
549
|
+
});
|
|
550
|
+
expect(await dispatch({ mac: true }, '')).to.equal('00:11:22:33:44:55');
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('prints the DHCP address when asked for the local one', async () => {
|
|
554
|
+
harness = shellHarness([ROUTE_ROUTE]);
|
|
555
|
+
vi.spyOn(os, 'networkInterfaces').mockReturnValue({
|
|
556
|
+
[IFACE]: [{ family: 'IPv4', address: '10.0.0.8', mac: '00:11:22:33:44:55' }],
|
|
557
|
+
});
|
|
558
|
+
expect(await dispatch({ dhcp: true }, '')).to.equal('10.0.0.8');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('prints the public address by default', async () => {
|
|
562
|
+
vi.spyOn(axios, 'get').mockResolvedValue({ data: '203.0.113.9' });
|
|
563
|
+
expect(await dispatch({}, '')).to.equal('203.0.113.9');
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
});
|