@underpostnet/cyberia 3.2.90 → 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/.env.example +16 -0
- package/.github/workflows/coverall.cyberia.ci.yml +107 -0
- package/.github/workflows/cyberia-client.cd.yml +6 -18
- package/.github/workflows/cyberia-server.cd.yml +6 -18
- 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 +6 -78
- package/.github/workflows/engine-cyberia.ci.yml +4 -5
- package/.github/workflows/ghpkg.ci.yml +42 -21
- package/.github/workflows/hardhat.ci.yml +12 -11
- package/.github/workflows/npmpkg.ci.yml +17 -15
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
- package/.github/workflows/release.cd.yml +2 -9
- package/.prettierignore +1 -0
- package/AGENTS.md +47 -0
- package/CHANGELOG.md +581 -2417
- package/CLI-HELP.md +416 -127
- package/Dockerfile +25 -9
- package/Dockerfile.dev +25 -9
- package/Dockerfile.test +4 -4
- package/README.md +21 -1
- package/bin/build.js +70 -43
- package/bin/build.template.js +8 -3
- package/bin/cyberia.js +372 -135
- package/bin/deploy.js +18 -104
- package/bin/index.js +372 -135
- package/compose.env +16 -0
- package/conf.js +93 -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 +22 -0
- package/deploy/dd-cyberia/init.sh +55 -0
- package/deploy/dd-cyberia/package.sh +17 -0
- package/deploy/dd-cyberia/state.sh +14 -0
- package/deploy/dd-cyberia/sync-deploy.sh +116 -0
- 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 +45 -0
- package/deploy/release/deploy.sh +32 -0
- package/deployment.yaml +7 -2
- package/docker-compose.yml +31 -37
- 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 +104 -43
- package/hardhat/package.json +3 -3
- package/manifests/alertmanager/deployment.yaml +81 -0
- package/manifests/blackbox-exporter/deployment.yaml +75 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +36 -7
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +77 -0
- 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/audit-selinux.sh +64 -0
- package/scripts/event-monitor.sh +56 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +30 -14
- package/scripts/kubeadm-node-setup.sh +40 -31
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/maas-setup.sh +8 -8
- package/scripts/nat-iptables.sh +2 -0
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- 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-default-items.js +63 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +16 -1
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
- package/src/api/cyberia-instance/cyberia-instance.controller.js +4 -1
- package/src/api/cyberia-instance/cyberia-instance.router.js +15 -2
- package/src/api/cyberia-instance/cyberia-instance.service.js +45 -2
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
- 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 +16 -29
- 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.model.js +204 -3
- package/src/api/object-layer/object-layer.router.js +2 -2
- package/src/api/object-layer/object-layer.service.js +4 -17
- 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 -36
- package/src/cli/client.js +306 -0
- package/src/cli/cloud-init.js +3 -3
- package/src/cli/cluster.js +242 -135
- package/src/cli/db.js +126 -67
- package/src/cli/deploy.js +158 -60
- 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 -7
- package/src/cli/index.js +426 -134
- 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 +73 -34
- package/src/cli/repository.js +564 -367
- package/src/cli/run.js +362 -654
- package/src/cli/secrets.js +2158 -964
- package/src/cli/ssh.js +651 -331
- package/src/cli/state.js +394 -0
- package/src/cli/static.js +2 -2
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +199 -125
- package/src/cli/vultr.js +623 -0
- package/src/cli/wireguard.js +2930 -0
- 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/FallbackWorldEngineCyberia.js +368 -0
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +35 -1
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
- 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/ARCHITECTURE.md +17 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
- 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/services/cyberia-instance/cyberia-instance.service.js +40 -0
- package/src/client/services/object-layer/object-layer.management.js +4 -4
- 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 +58 -44
- 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 +11 -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 +103 -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 +54 -6
- package/src/projects/cyberia/gemini-client.js +1 -1
- package/src/projects/cyberia/generate-saga.js +15 -24
- package/src/projects/cyberia/hot-reload-trigger.js +1 -1
- package/src/projects/cyberia/instance-data.js +22 -3
- 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 +12 -22
- package/src/projects/cyberia/semantic-layer-generator.js +1 -1
- package/src/projects/underpost/catalog-underpost.js +13 -2
- 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/compose.env +16 -0
- package/src/runtime/engine-cyberia/docker-compose.yml +31 -37
- 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} +241 -14
- package/src/server/network/forward-proxy.js +541 -0
- package/src/server/{middlewares.js → network/middlewares.js} +59 -4
- 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/network/underpost-compression.js +186 -0
- package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +26 -16
- package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +19 -3
- package/src/server/{cri.js → ops/cri.js} +2 -2
- package/src/server/ops/cron.js +949 -0
- 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/ops/systemd.js +259 -0
- package/src/server/{conf.js → runtime/conf.js} +521 -520
- 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 -13
- 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/security/selinux.js +185 -0
- 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} +2 -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/integration/infra/1-security/selinux.test.js +69 -0
- 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/integration/infra/2-network/wireguard-edge.test.js +1731 -0
- 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} +47 -6
- package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +56 -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/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
- package/manifests/mongodb/pv-pvc.yaml +0 -59
- 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/cron.js +0 -476
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Staged SELinux recovery and normalization for a live Underpost / Kubernetes host.
|
|
4
|
+
#
|
|
5
|
+
# inspect -> validate -> normalize -> verify -> enforce
|
|
6
|
+
#
|
|
7
|
+
# Never reboots, never restarts Kubernetes, kubelet, the container runtime, the CNI or any
|
|
8
|
+
# workload, never touches PVs/PVCs or the contents of a data directory, and never disables
|
|
9
|
+
# SELinux. The only mutations it can make are:
|
|
10
|
+
#
|
|
11
|
+
# * creating a node storage directory that a PersistentVolume references and that is missing;
|
|
12
|
+
# * adding a `semanage fcontext` mapping for a discovered storage root;
|
|
13
|
+
# * running `restorecon` over those roots;
|
|
14
|
+
# * flipping SELinux to Enforcing, and only in the `enforce` stage, and only after `verify`
|
|
15
|
+
# passes on this same run.
|
|
16
|
+
#
|
|
17
|
+
# Every mutating stage requires --apply. Without it the script reports what it would do.
|
|
18
|
+
#
|
|
19
|
+
# Rocky Linux 9 / RHEL 9.
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
SHARED_TYPE='container_file_t'
|
|
23
|
+
# Overridable so the rollback round-trip can be exercised without writing under /var/lib.
|
|
24
|
+
STATE_DIR="${UNDERPOST_SELINUX_STATE_DIR:-/var/lib/underpost/selinux-normalize}"
|
|
25
|
+
KUBECTL_TIMEOUT='20s'
|
|
26
|
+
STAGE='inspect'
|
|
27
|
+
APPLY=0
|
|
28
|
+
ASSUME_YES=0
|
|
29
|
+
AVC_SINCE=''
|
|
30
|
+
AVC_SINCE_EXPLICIT=0
|
|
31
|
+
ALLOW_UNLABELED_MOUNTS=0
|
|
32
|
+
declare -a EXTRA_PATHS=()
|
|
33
|
+
|
|
34
|
+
RED=''; YELLOW=''; GREEN=''; BOLD=''; RESET=''
|
|
35
|
+
if [ -t 1 ]; then RED=$'\033[31m'; YELLOW=$'\033[33m'; GREEN=$'\033[32m'; BOLD=$'\033[1m'; RESET=$'\033[0m'; fi
|
|
36
|
+
|
|
37
|
+
say() { printf '%s\n' "$*"; }
|
|
38
|
+
head1() { printf '\n%s==> %s%s\n' "$BOLD" "$*" "$RESET"; }
|
|
39
|
+
ok() { printf ' %s[ ok ]%s %s\n' "$GREEN" "$RESET" "$*"; }
|
|
40
|
+
warn() { printf ' %s[warn]%s %s\n' "$YELLOW" "$RESET" "$*"; }
|
|
41
|
+
bad() { printf ' %s[fail]%s %s\n' "$RED" "$RESET" "$*"; }
|
|
42
|
+
info() { printf ' %s\n' "$*"; }
|
|
43
|
+
|
|
44
|
+
usage() {
|
|
45
|
+
cat <<'USAGE'
|
|
46
|
+
Usage: selinux-normalize.sh [STAGE] [options]
|
|
47
|
+
|
|
48
|
+
Stages (each one re-runs the ones before it that are read-only):
|
|
49
|
+
inspect Report SELinux state, Kubernetes state, discovered storage and current labels.
|
|
50
|
+
validate inspect + assert every precondition for a safe normalization. Read-only.
|
|
51
|
+
normalize Register the persistent container_file_t mappings and restore the discovered
|
|
52
|
+
storage roots. Requires --apply to mutate.
|
|
53
|
+
verify Re-check labels, workload health and fresh AVC denials since the last normalize.
|
|
54
|
+
enforce Transition Permissive -> Enforcing, only if verify passes. Requires --apply.
|
|
55
|
+
rollback Undo the fcontext mappings this script added, and restore the affected trees.
|
|
56
|
+
Requires --apply.
|
|
57
|
+
mark Move the denial window to now, without touching labels or SELinux mode. Use after
|
|
58
|
+
suspending or fixing a workload, so verify measures the state you just created
|
|
59
|
+
rather than the denials that led you to change it. Requires --apply.
|
|
60
|
+
|
|
61
|
+
Options:
|
|
62
|
+
--apply Perform mutations. Without it, mutating stages only report.
|
|
63
|
+
--yes Do not prompt before the Enforcing transition.
|
|
64
|
+
--allow-unlabeled-mounts
|
|
65
|
+
Let verify pass with workload hostPath mounts still unresolved. Those
|
|
66
|
+
mounts deny under Enforcing; only pass this once you have decided each
|
|
67
|
+
one is acceptable to break.
|
|
68
|
+
--path PATH Additional node storage root to include (repeatable).
|
|
69
|
+
--since WHEN ausearch window for the AVC scan (default: recent).
|
|
70
|
+
-h, --help This text.
|
|
71
|
+
|
|
72
|
+
Exit codes: 0 success, 1 stage failed / unsafe to proceed, 2 usage or missing tooling.
|
|
73
|
+
USAGE
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
while [ "$#" -gt 0 ]; do
|
|
77
|
+
case "$1" in
|
|
78
|
+
inspect|validate|normalize|verify|enforce|rollback|mark) STAGE="$1"; shift ;;
|
|
79
|
+
--apply) APPLY=1; shift ;;
|
|
80
|
+
--yes) ASSUME_YES=1; shift ;;
|
|
81
|
+
--allow-unlabeled-mounts) ALLOW_UNLABELED_MOUNTS=1; shift ;;
|
|
82
|
+
--path) [ "$#" -ge 2 ] || { echo "--path requires a value" >&2; exit 2; }; EXTRA_PATHS+=("$2"); shift 2 ;;
|
|
83
|
+
--since) [ "$#" -ge 2 ] || { echo "--since requires a value" >&2; exit 2; }; AVC_SINCE="$2"; AVC_SINCE_EXPLICIT=1; shift 2 ;;
|
|
84
|
+
-h|--help) usage; exit 0 ;;
|
|
85
|
+
*) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
|
86
|
+
esac
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
SUDO=''
|
|
90
|
+
SUDO_RO=''
|
|
91
|
+
if [ "$(id -u)" -ne 0 ]; then
|
|
92
|
+
SUDO='sudo'
|
|
93
|
+
# Read-only probes never prompt: an inspect/validate/verify run must not block on a password,
|
|
94
|
+
# and a probe that cannot elevate degrades to "unknown" rather than hanging the operator.
|
|
95
|
+
SUDO_RO='sudo -n'
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
privileged_read() {
|
|
99
|
+
if [ -z "$SUDO_RO" ]; then "$@"; else $SUDO_RO "$@"; fi
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
run() {
|
|
103
|
+
if [ "$APPLY" -eq 1 ]; then
|
|
104
|
+
info "+ $*"
|
|
105
|
+
eval "$@"
|
|
106
|
+
else
|
|
107
|
+
info "would run: $*"
|
|
108
|
+
fi
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------- inspect: SELinux
|
|
112
|
+
|
|
113
|
+
SELINUX_ENABLED=0
|
|
114
|
+
SELINUX_MODE='unknown'
|
|
115
|
+
SELINUX_CONFIG_MODE='unknown'
|
|
116
|
+
SELINUX_POLICY='unknown'
|
|
117
|
+
HAVE_SEMANAGE=0
|
|
118
|
+
HAVE_RESTORECON=0
|
|
119
|
+
HAVE_AUSEARCH=0
|
|
120
|
+
|
|
121
|
+
inspect_selinux() {
|
|
122
|
+
head1 'SELinux state'
|
|
123
|
+
if ! command -v getenforce >/dev/null 2>&1; then
|
|
124
|
+
bad 'getenforce is missing: SELinux userspace is not installed.'
|
|
125
|
+
info 'Remediation: dnf install -y policycoreutils policycoreutils-python-utils selinux-policy-targeted audit'
|
|
126
|
+
return
|
|
127
|
+
fi
|
|
128
|
+
SELINUX_MODE="$(getenforce)"
|
|
129
|
+
command -v selinuxenabled >/dev/null 2>&1 && selinuxenabled && SELINUX_ENABLED=1
|
|
130
|
+
[ -r /etc/selinux/config ] && SELINUX_CONFIG_MODE="$(awk -F= '/^SELINUX=/{print $2}' /etc/selinux/config)"
|
|
131
|
+
[ -r /etc/selinux/config ] && SELINUX_POLICY="$(awk -F= '/^SELINUXTYPE=/{print $2}' /etc/selinux/config)"
|
|
132
|
+
command -v semanage >/dev/null 2>&1 && HAVE_SEMANAGE=1
|
|
133
|
+
command -v restorecon >/dev/null 2>&1 && HAVE_RESTORECON=1
|
|
134
|
+
command -v ausearch >/dev/null 2>&1 && HAVE_AUSEARCH=1
|
|
135
|
+
|
|
136
|
+
info "runtime mode : $SELINUX_MODE"
|
|
137
|
+
info "config mode : $SELINUX_CONFIG_MODE"
|
|
138
|
+
info "policy : $SELINUX_POLICY"
|
|
139
|
+
info "semanage present : $([ "$HAVE_SEMANAGE" -eq 1 ] && echo yes || echo 'no <-- policycoreutils-python-utils')"
|
|
140
|
+
info "restorecon present: $([ "$HAVE_RESTORECON" -eq 1 ] && echo yes || echo 'no <-- policycoreutils')"
|
|
141
|
+
info "ausearch present : $([ "$HAVE_AUSEARCH" -eq 1 ] && echo yes || echo 'no <-- audit')"
|
|
142
|
+
[ -e /.autorelabel ] && warn '/.autorelabel is present: the next boot will relabel the whole filesystem.'
|
|
143
|
+
return 0
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# ------------------------------------------------------------------------- inspect: Kubernetes
|
|
147
|
+
|
|
148
|
+
KUBE_AVAILABLE=0
|
|
149
|
+
NODES_TOTAL=0
|
|
150
|
+
NODES_READY=0
|
|
151
|
+
declare -a STORAGE_PATHS=()
|
|
152
|
+
declare -a MANAGED_ROOTS=()
|
|
153
|
+
declare -a PV_SUBTREES=()
|
|
154
|
+
declare -a ROOT_REASON=()
|
|
155
|
+
declare -a REFUSED=()
|
|
156
|
+
declare -a UNCOVERED=()
|
|
157
|
+
declare -a POD_HOSTPATHS=()
|
|
158
|
+
declare -a RESIDUAL=()
|
|
159
|
+
declare -a AUDIT_SINCE_ARGS=(-ts recent)
|
|
160
|
+
|
|
161
|
+
kubectl_q() { kubectl --request-timeout="$KUBECTL_TIMEOUT" "$@" 2>/dev/null; }
|
|
162
|
+
|
|
163
|
+
inspect_kubernetes() {
|
|
164
|
+
head1 'Kubernetes state'
|
|
165
|
+
if ! command -v kubectl >/dev/null 2>&1; then
|
|
166
|
+
warn 'kubectl is not on PATH; storage discovery falls back to the declared platform roots.'
|
|
167
|
+
return 0
|
|
168
|
+
fi
|
|
169
|
+
if ! kubectl_q version -o json >/dev/null; then
|
|
170
|
+
warn 'No reachable API server; storage discovery falls back to the declared platform roots.'
|
|
171
|
+
return 0
|
|
172
|
+
fi
|
|
173
|
+
KUBE_AVAILABLE=1
|
|
174
|
+
local nodes
|
|
175
|
+
nodes="$(kubectl_q get nodes --no-headers || true)"
|
|
176
|
+
NODES_TOTAL="$(printf '%s' "$nodes" | grep -c . || true)"
|
|
177
|
+
NODES_READY="$(printf '%s' "$nodes" | awk '$2 ~ /(^|,)Ready(,|$)/' | grep -c . || true)"
|
|
178
|
+
info "nodes ready : ${NODES_READY}/${NODES_TOTAL}"
|
|
179
|
+
info "this host : $(hostname)"
|
|
180
|
+
return 0
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Paths that must never receive the shared container label, whatever discovery turns up.
|
|
184
|
+
#
|
|
185
|
+
# A pod hostPath is not evidence that a directory is workload data: the CNI plugin directory, the
|
|
186
|
+
# container runtime socket, /proc and the kubelet tree are all mounted into pods and every one of
|
|
187
|
+
# them has a policy type that exists for a reason. Relabeling /opt/cni/bin would strip `bin_t`
|
|
188
|
+
# from the calico executables the kubelet runs.
|
|
189
|
+
PROTECTED_PATHS_REGEX='^(/|/usr|/etc|/bin|/sbin|/lib|/lib64|/boot|/proc|/sys|/dev|/run|/var|/var/lib|/var/run|/var/log|/var/lib/kubelet|/var/lib/containerd|/var/lib/docker|/var/lib/etcd|/var/lib/calico|/var/lib/cni|/opt|/opt/cni|/opt/cni/bin|/home|/root|/srv|/mnt|/tmp)$'
|
|
190
|
+
|
|
191
|
+
# Types whose whole purpose is something other than container data. `usr_t` is deliberately absent:
|
|
192
|
+
# it is the policy default under /opt and is exactly what the local-path provisioner root wrongly
|
|
193
|
+
# carries, so refusing it would refuse the fix.
|
|
194
|
+
PROTECTED_TYPES='bin_t lib_t shell_exec_t etc_t kubernetes_file_t container_runtime_exec_t container_var_lib_t cni_var_lib_t systemd_unit_file_t passwd_file_t shadow_t admin_home_t'
|
|
195
|
+
|
|
196
|
+
# The node directory backing every hostPath PersistentVolume the deploy flow materializes. Read
|
|
197
|
+
# from the source of truth the CLI itself uses, so the script and the code can never disagree.
|
|
198
|
+
resolve_host_volume_root() {
|
|
199
|
+
local repo_env script_dir
|
|
200
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
201
|
+
repo_env="$script_dir/../src/server/runtime/environment.js"
|
|
202
|
+
if [ -r "$repo_env" ]; then
|
|
203
|
+
local value
|
|
204
|
+
value="$(sed -nE "s/^const HOST_VOLUME_ROOT = '([^']+)';.*/\1/p" "$repo_env" | head -1)"
|
|
205
|
+
[ -n "$value" ] && { printf '%s' "$value"; return 0; }
|
|
206
|
+
fi
|
|
207
|
+
printf '%s' "${UNDERPOST_HOST_VOLUME_ROOT:-/home/dd/engine/volume}"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
is_protected_path() {
|
|
211
|
+
printf '%s' "$1" | grep -qE "$PROTECTED_PATHS_REGEX"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
is_protected_type() {
|
|
215
|
+
local type; type="$(path_type "$1")"
|
|
216
|
+
[ -n "$type" ] || return 1
|
|
217
|
+
printf '%s' " $PROTECTED_TYPES " | grep -q " $type "
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Classifies a pod hostPath mount so the report separates the mounts that need an operator
|
|
221
|
+
# decision from the ones whose current type is already correct.
|
|
222
|
+
#
|
|
223
|
+
# system the path is inside a protected tree, or already carries a type the container policy
|
|
224
|
+
# defines for that role (`container_var_lib_t`, `cert_t`, `proc_t`, `kubernetes_file_t`
|
|
225
|
+
# …). These are mounted by privileged system DaemonSets and are not workload data.
|
|
226
|
+
# ok already container-accessible.
|
|
227
|
+
# review neither — the only class worth acting on.
|
|
228
|
+
classify_pod_mount() {
|
|
229
|
+
local path="$1" type="$2"
|
|
230
|
+
[ -e "$path" ] || { printf 'absent'; return 0; }
|
|
231
|
+
[ "$type" = "$SHARED_TYPE" ] && { printf 'ok'; return 0; }
|
|
232
|
+
is_protected_path "$path" && { printf 'system'; return 0; }
|
|
233
|
+
case "$type" in
|
|
234
|
+
container_*|proc_t|sysfs_t|cert_t|kubernetes_file_t|modules_object_t|iptables_var_run_t|var_run_t|var_lib_t|root_t|etc_t|bin_t|lib_t|shell_exec_t)
|
|
235
|
+
printf 'system'; return 0 ;;
|
|
236
|
+
esac
|
|
237
|
+
printf 'review'
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Storage discovery, in three tiers with different authority:
|
|
241
|
+
#
|
|
242
|
+
# managed roots mapped with `semanage fcontext` and relabeled recursively. Only directories
|
|
243
|
+
# this platform owns end up here: its declared data roots, the local-path
|
|
244
|
+
# provisioner's configured node roots, and anything the operator named.
|
|
245
|
+
# PV subtrees node-local paths a PersistentVolume actually references. Used to prove the
|
|
246
|
+
# managed roots cover the live storage; one that is not covered is reported
|
|
247
|
+
# and skipped, never silently relabeled or promoted to a root of its own.
|
|
248
|
+
# pod hostPaths reported only. A pod mounting a host directory says nothing about whether
|
|
249
|
+
# that directory is workload data.
|
|
250
|
+
discover_pv_paths() {
|
|
251
|
+
head1 'PersistentVolume / PersistentVolumeClaim storage discovery'
|
|
252
|
+
local host_volume_root; host_volume_root="$(resolve_host_volume_root)"
|
|
253
|
+
|
|
254
|
+
if [ "$KUBE_AVAILABLE" -eq 1 ]; then
|
|
255
|
+
local rows
|
|
256
|
+
rows="$(kubectl_q get pv -o custom-columns=\
|
|
257
|
+
NAME:.metadata.name,CLASS:.spec.storageClassName,PHASE:.status.phase,\
|
|
258
|
+
CLAIM:.spec.claimRef.name,HOSTPATH:.spec.hostPath.path,LOCALPATH:.spec.local.path || true)"
|
|
259
|
+
if [ -n "$rows" ]; then
|
|
260
|
+
printf '%s\n' "$rows" | sed 's/^/ /'
|
|
261
|
+
local name class phase claim hostpath localpath path
|
|
262
|
+
while read -r name class phase claim hostpath localpath; do
|
|
263
|
+
[ -n "$name" ] && [ "$name" != 'NAME' ] || continue
|
|
264
|
+
path=''
|
|
265
|
+
[ "$hostpath" != '<none>' ] && path="$hostpath"
|
|
266
|
+
[ -z "$path" ] && [ "$localpath" != '<none>' ] && path="$localpath"
|
|
267
|
+
[ -n "$path" ] && PV_SUBTREES+=("$path") || true
|
|
268
|
+
done <<< "$rows"
|
|
269
|
+
else
|
|
270
|
+
info 'no PersistentVolumes found'
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
# Dynamically provisioned claims have no path of their own: the data is a subdirectory of
|
|
274
|
+
# the provisioner's node root, so the root is what must carry the label. Read it from the
|
|
275
|
+
# live ConfigMap rather than assuming the upstream default.
|
|
276
|
+
local ns cfg roots root
|
|
277
|
+
for ns in local-path-storage kube-system; do
|
|
278
|
+
cfg="$(kubectl_q get configmap local-path-config -n "$ns" -o jsonpath='{.data.config\.json}' || true)"
|
|
279
|
+
[ -n "$cfg" ] || continue
|
|
280
|
+
roots="$(printf '%s' "$cfg" | tr -d ' \n' | grep -oE '"paths":\[[^]]*\]|"sharedFileSystemPath":"[^"]*"' | grep -oE '/[^",]+' || true)"
|
|
281
|
+
if [ -n "$roots" ]; then
|
|
282
|
+
say ''
|
|
283
|
+
info "local-path provisioner (ns/$ns) node roots:"
|
|
284
|
+
while read -r root; do
|
|
285
|
+
[ -n "$root" ] || continue
|
|
286
|
+
info " $root"
|
|
287
|
+
MANAGED_ROOTS+=("$root")
|
|
288
|
+
ROOT_REASON+=("$root|local-path provisioner node root (ns/$ns)")
|
|
289
|
+
done <<< "$roots"
|
|
290
|
+
fi
|
|
291
|
+
break
|
|
292
|
+
done
|
|
293
|
+
|
|
294
|
+
# Reported, never labeled. A host directory being mounted into a pod says nothing about
|
|
295
|
+
# whether it holds workload data, so the decision is left to the operator — but each mount
|
|
296
|
+
# is classified so the handful that actually need a decision are not buried under the
|
|
297
|
+
# couple of dozen host-integration mounts every CNI and kubelet DaemonSet carries.
|
|
298
|
+
#
|
|
299
|
+
# `go-template` rather than `jsonpath`: inside a nested range, jsonpath's `$` resolves to
|
|
300
|
+
# the document root, not the enclosing item, so the pod's own namespace and name came back
|
|
301
|
+
# empty and every row read as a bare "/".
|
|
302
|
+
local pod_rows p pods current class width
|
|
303
|
+
#
|
|
304
|
+
# Three sources, not one. A pod only exists while it runs, so reading pods alone reports a
|
|
305
|
+
# clean host for a CronJob that is merely scheduled — its mount reappears at the next fire.
|
|
306
|
+
# The workload templates are the durable declaration and are what must be audited.
|
|
307
|
+
# Terminal pods are excluded for the mirror-image reason: a finished Job cannot produce
|
|
308
|
+
# another denial, and its own template already speaks for the next run.
|
|
309
|
+
pod_rows="$(
|
|
310
|
+
{
|
|
311
|
+
kubectl_q get pods -A --field-selector=status.phase!=Succeeded,status.phase!=Failed \
|
|
312
|
+
-o go-template='{{range .items}}{{$ns := .metadata.namespace}}{{$n := .metadata.name}}{{range .spec.volumes}}{{if .hostPath}}{{.hostPath.path}}{{"\t"}}{{$ns}}/{{$n}}{{"\n"}}{{end}}{{end}}{{end}}' || true
|
|
313
|
+
kubectl_q get deployments,statefulsets,daemonsets,jobs,replicasets -A \
|
|
314
|
+
-o go-template='{{range .items}}{{$ns := .metadata.namespace}}{{$k := .kind}}{{$n := .metadata.name}}{{range .spec.template.spec.volumes}}{{if .hostPath}}{{.hostPath.path}}{{"\t"}}{{$ns}}/{{$k}}/{{$n}}{{"\n"}}{{end}}{{end}}{{end}}' || true
|
|
315
|
+
kubectl_q get cronjobs -A \
|
|
316
|
+
-o go-template='{{range .items}}{{$ns := .metadata.namespace}}{{$n := .metadata.name}}{{if not .spec.suspend}}{{range .spec.jobTemplate.spec.template.spec.volumes}}{{if .hostPath}}{{.hostPath.path}}{{"\t"}}{{$ns}}/CronJob/{{$n}}{{"\n"}}{{end}}{{end}}{{end}}{{end}}' || true
|
|
317
|
+
} | grep -E '^/' | sort -u || true
|
|
318
|
+
)"
|
|
319
|
+
if [ -n "$pod_rows" ]; then
|
|
320
|
+
say ''
|
|
321
|
+
info 'hostPath mounts declared by live pods and workload templates (never relabeled here):'
|
|
322
|
+
width="$(printf '%s\n' "$pod_rows" | cut -f1 | awk '{ if (length($0) > m) m = length($0) } END { print (m > 58 ? 58 : (m < 24 ? 24 : m)) }')"
|
|
323
|
+
printf " %-${width}s %-22s %-8s %s\n" PATH 'CURRENT TYPE' CLASS 'MOUNTED BY'
|
|
324
|
+
while read -r p; do
|
|
325
|
+
[ -n "$p" ] || continue
|
|
326
|
+
pods="$(printf '%s\n' "$pod_rows" | awk -F'\t' -v k="$p" '$1 == k {print $2}' | paste -sd, - | cut -c1-56)"
|
|
327
|
+
current="$(path_type "$p")"
|
|
328
|
+
class="$(classify_pod_mount "$p" "$current")"
|
|
329
|
+
printf " %-${width}s %-22s %-8s %s\n" "$p" "${current:-absent}" "$class" "$pods"
|
|
330
|
+
[ "$class" = 'review' ] && POD_HOSTPATHS+=("$p") || true
|
|
331
|
+
done <<< "$(printf '%s\n' "$pod_rows" | cut -f1 | sort -u)"
|
|
332
|
+
info 'system = host-integration mount (CNI, kubelet, runtime, /proc); its type is correct as-is.'
|
|
333
|
+
info 'ok = already container-accessible.'
|
|
334
|
+
info 'absent = not present on this node (a mount belonging to a pod scheduled elsewhere).'
|
|
335
|
+
info 'review = none of the above, and outside every managed root — the rows needing a decision.'
|
|
336
|
+
fi
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
# Declared platform roots. Kept even when the API is reachable: a root can exist on this node
|
|
340
|
+
# with no object currently referencing it (a Retained volume between deploys).
|
|
341
|
+
MANAGED_ROOTS+=('/data')
|
|
342
|
+
ROOT_REASON+=('/data|declared platform data root')
|
|
343
|
+
MANAGED_ROOTS+=("$host_volume_root")
|
|
344
|
+
ROOT_REASON+=("$host_volume_root|declared hostPath volume root (HOST_VOLUME_ROOT)")
|
|
345
|
+
if [ "$KUBE_AVAILABLE" -ne 1 ]; then
|
|
346
|
+
MANAGED_ROOTS+=('/opt/local-path-provisioner')
|
|
347
|
+
ROOT_REASON+=('/opt/local-path-provisioner|local-path provisioner default root (no API to query)')
|
|
348
|
+
fi
|
|
349
|
+
local extra
|
|
350
|
+
for extra in ${EXTRA_PATHS[@]+"${EXTRA_PATHS[@]}"}; do
|
|
351
|
+
MANAGED_ROOTS+=("$extra")
|
|
352
|
+
ROOT_REASON+=("$extra|named by the operator with --path")
|
|
353
|
+
done
|
|
354
|
+
|
|
355
|
+
# Collapse to non-overlapping roots: an fcontext expression is `<root>(/.*)?`, so a nested
|
|
356
|
+
# entry is a redundant rule and a second relabel pass over the same tree. Only managed roots
|
|
357
|
+
# take part — a discovered subtree can never absorb or widen one.
|
|
358
|
+
local sorted candidate covered root
|
|
359
|
+
sorted="$(printf '%s\n' "${MANAGED_ROOTS[@]}" | sed 's:/*$::' | grep -E '^/.+' | sort -u)"
|
|
360
|
+
MANAGED_ROOTS=()
|
|
361
|
+
while read -r candidate; do
|
|
362
|
+
[ -n "$candidate" ] || continue
|
|
363
|
+
covered=0
|
|
364
|
+
while read -r root; do
|
|
365
|
+
{ [ -n "$root" ] && [ "$root" != "$candidate" ] && case "$candidate" in "$root"/*) covered=1 ;; esac; } || true
|
|
366
|
+
done <<< "$sorted"
|
|
367
|
+
[ "$covered" -eq 0 ] && MANAGED_ROOTS+=("$candidate") || true
|
|
368
|
+
done <<< "$sorted"
|
|
369
|
+
|
|
370
|
+
# Refuse anything whose type or path says it is not workload data, before it can be reported
|
|
371
|
+
# as an intended change. A refusal is fatal in `validate`, so it can never reach `--apply`.
|
|
372
|
+
STORAGE_PATHS=()
|
|
373
|
+
for candidate in "${MANAGED_ROOTS[@]}"; do
|
|
374
|
+
if is_protected_path "$candidate"; then
|
|
375
|
+
REFUSED+=("$candidate|system directory; relabeling it would change the policy type of host-owned files")
|
|
376
|
+
continue
|
|
377
|
+
fi
|
|
378
|
+
if is_protected_type "$candidate"; then
|
|
379
|
+
REFUSED+=("$candidate|carries the protected type '$(path_type "$candidate")'; that type is not container data")
|
|
380
|
+
continue
|
|
381
|
+
fi
|
|
382
|
+
STORAGE_PATHS+=("$candidate")
|
|
383
|
+
done
|
|
384
|
+
|
|
385
|
+
head1 'Storage roots selected for labeling'
|
|
386
|
+
if [ "${#STORAGE_PATHS[@]}" -eq 0 ]; then
|
|
387
|
+
warn 'none'
|
|
388
|
+
else
|
|
389
|
+
local reason entry
|
|
390
|
+
for candidate in "${STORAGE_PATHS[@]}"; do
|
|
391
|
+
reason=''
|
|
392
|
+
for entry in ${ROOT_REASON[@]+"${ROOT_REASON[@]}"}; do
|
|
393
|
+
[ "${entry%%|*}" = "$candidate" ] && reason="${entry#*|}"
|
|
394
|
+
done
|
|
395
|
+
printf ' %-42s %s\n' "$candidate" "${reason:-discovered}"
|
|
396
|
+
done
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
if [ "${#REFUSED[@]}" -gt 0 ]; then
|
|
400
|
+
head1 'Refused (never relabeled)'
|
|
401
|
+
for candidate in "${REFUSED[@]}"; do
|
|
402
|
+
printf ' %-42s %s\n' "${candidate%%|*}" "${candidate#*|}"
|
|
403
|
+
done
|
|
404
|
+
fi
|
|
405
|
+
|
|
406
|
+
# Every live PersistentVolume path must fall inside a managed root, otherwise its data is not
|
|
407
|
+
# actually being fixed by this run and the operator has to say so explicitly.
|
|
408
|
+
UNCOVERED=()
|
|
409
|
+
local subtree
|
|
410
|
+
for subtree in ${PV_SUBTREES[@]+"${PV_SUBTREES[@]}"}; do
|
|
411
|
+
covered=0
|
|
412
|
+
for root in ${STORAGE_PATHS[@]+"${STORAGE_PATHS[@]}"}; do
|
|
413
|
+
{ [ "$subtree" = "$root" ] || case "$subtree" in "$root"/*) covered=1 ;; esac; } || true
|
|
414
|
+
[ "$subtree" = "$root" ] && covered=1 || true
|
|
415
|
+
done
|
|
416
|
+
[ "$covered" -eq 0 ] && UNCOVERED+=("$subtree") || true
|
|
417
|
+
done
|
|
418
|
+
if [ "${#UNCOVERED[@]}" -gt 0 ]; then
|
|
419
|
+
head1 'PersistentVolume paths NOT covered by any managed root'
|
|
420
|
+
printf '%s\n' "${UNCOVERED[@]}" | sort -u | sed 's/^/ /'
|
|
421
|
+
warn 'These are not labeled by this run. Add one with --path if it is genuinely workload data.'
|
|
422
|
+
fi
|
|
423
|
+
|
|
424
|
+
# POD_HOSTPATHS now holds only the mounts classified `review`: not a system path, not already
|
|
425
|
+
# container-accessible. Those still inside a managed root are fine — the relabel covers them.
|
|
426
|
+
# Whatever is left is the complete set of places a denial can still come from after this run.
|
|
427
|
+
local mount current
|
|
428
|
+
RESIDUAL=()
|
|
429
|
+
for mount in ${POD_HOSTPATHS[@]+"${POD_HOSTPATHS[@]}"}; do
|
|
430
|
+
[ -e "$mount" ] || continue
|
|
431
|
+
covered=0
|
|
432
|
+
for root in ${STORAGE_PATHS[@]+"${STORAGE_PATHS[@]}"}; do
|
|
433
|
+
{ [ "$mount" = "$root" ] && covered=1; } || case "$mount" in "$root"/*) covered=1 ;; esac
|
|
434
|
+
done
|
|
435
|
+
[ "$covered" -eq 0 ] && RESIDUAL+=("$mount|$(path_type "$mount")") || true
|
|
436
|
+
done
|
|
437
|
+
if [ "${#RESIDUAL[@]}" -gt 0 ]; then
|
|
438
|
+
head1 'Workload hostPath mounts this run does not fix'
|
|
439
|
+
for mount in "${RESIDUAL[@]}"; do
|
|
440
|
+
printf ' %-52s %s\n' "${mount%%|*}" "${mount#*|}"
|
|
441
|
+
done
|
|
442
|
+
info 'Deliberate exclusions, and the only remaining source of a container denial. If a'
|
|
443
|
+
info ' workload genuinely needs to read or write one, move that data under a storage root,'
|
|
444
|
+
info ' or name the specific subdirectory with --path — never the whole tree.'
|
|
445
|
+
fi
|
|
446
|
+
return 0
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
# Prints the SELinux type of a path, or nothing when it cannot be read.
|
|
450
|
+
#
|
|
451
|
+
# Never fails. `set -o pipefail` makes `ls -Zd <missing> | awk ...` a failing pipeline, and a
|
|
452
|
+
# `current="$(path_type "$p")"` assignment takes that status — which under `set -e` aborted the
|
|
453
|
+
# whole run on the first path that does not exist on this node (a mount belonging to a pod on
|
|
454
|
+
# another node, or a PV pinned elsewhere).
|
|
455
|
+
path_type() {
|
|
456
|
+
[ -e "$1" ] || return 0
|
|
457
|
+
{ ls -Zd "$1" 2>/dev/null || true; } | awk '{print $1}' | awk -F: '{print $3}' || true
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
report_labels() {
|
|
461
|
+
head1 'Current labels vs intended'
|
|
462
|
+
printf ' %-46s %-22s %-22s %s\n' PATH CURRENT INTENDED ACTION
|
|
463
|
+
local path current mismatched=0 missing=0
|
|
464
|
+
for path in "${STORAGE_PATHS[@]}"; do
|
|
465
|
+
if [ ! -e "$path" ]; then
|
|
466
|
+
printf ' %-46s %-22s %-22s %s\n' "$path" '(absent)' "$SHARED_TYPE" 'create + label'
|
|
467
|
+
missing=$((missing + 1))
|
|
468
|
+
continue
|
|
469
|
+
fi
|
|
470
|
+
current="$(path_type "$path")"
|
|
471
|
+
if [ "$current" = "$SHARED_TYPE" ]; then
|
|
472
|
+
printf ' %-46s %-22s %-22s %s\n' "$path" "$current" "$SHARED_TYPE" 'none'
|
|
473
|
+
else
|
|
474
|
+
printf ' %-46s %-22s %-22s %s\n' "$path" "${current:-unknown}" "$SHARED_TYPE" 'relabel'
|
|
475
|
+
mismatched=$((mismatched + 1))
|
|
476
|
+
fi
|
|
477
|
+
done
|
|
478
|
+
# Nested entries carrying a stale label under an already-correct root: `restorecon -R` fixes
|
|
479
|
+
# them, and this is what proves the recursion is actually needed.
|
|
480
|
+
local stale
|
|
481
|
+
for path in "${STORAGE_PATHS[@]}"; do
|
|
482
|
+
[ -d "$path" ] || continue
|
|
483
|
+
stale="$(privileged_read find "$path" -mindepth 1 -maxdepth 3 -exec ls -Zd {} + 2>/dev/null \
|
|
484
|
+
| awk -F: -v t="$SHARED_TYPE" '$3 != t' | head -5 || true)"
|
|
485
|
+
[ -n "$stale" ] && { warn "entries under $path carrying a non-$SHARED_TYPE type:"; printf '%s\n' "$stale" | sed 's/^/ /'; }
|
|
486
|
+
done
|
|
487
|
+
MISMATCHED_COUNT="$mismatched"
|
|
488
|
+
MISSING_COUNT="$missing"
|
|
489
|
+
return 0
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
# Timestamp of the last applied normalization, in ausearch's own format.
|
|
493
|
+
normalize_marker_file() { printf '%s/last-normalize.txt' "$STATE_DIR"; }
|
|
494
|
+
|
|
495
|
+
# Resolves the audit window into ausearch arguments.
|
|
496
|
+
#
|
|
497
|
+
# Defaults to the moment the last `normalize --apply` finished rather than to `recent`: the
|
|
498
|
+
# denials this run just fixed are still inside a ten-minute `recent` window, so verifying against
|
|
499
|
+
# it would count the very records the relabel eliminated and never report a clean host. An
|
|
500
|
+
# explicit `--since` always wins.
|
|
501
|
+
resolve_avc_window() {
|
|
502
|
+
AUDIT_SINCE_ARGS=()
|
|
503
|
+
local marker; marker="$(normalize_marker_file)"
|
|
504
|
+
if [ "$AVC_SINCE_EXPLICIT" -eq 0 ] && [ -r "$marker" ]; then
|
|
505
|
+
local stamp; stamp="$(head -1 "$marker")"
|
|
506
|
+
if [ -n "$stamp" ]; then
|
|
507
|
+
# shellcheck disable=SC2206 -- deliberate split: ausearch takes date and time as two args.
|
|
508
|
+
AUDIT_SINCE_ARGS=(-ts $stamp)
|
|
509
|
+
AVC_SINCE="$stamp (last applied normalization)"
|
|
510
|
+
return 0
|
|
511
|
+
fi
|
|
512
|
+
fi
|
|
513
|
+
[ -n "$AVC_SINCE" ] || AVC_SINCE='recent'
|
|
514
|
+
AUDIT_SINCE_ARGS=(-ts "$AVC_SINCE")
|
|
515
|
+
return 0
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
report_avc() {
|
|
519
|
+
resolve_avc_window
|
|
520
|
+
head1 "AVC denials since: $AVC_SINCE"
|
|
521
|
+
if [ "$HAVE_AUSEARCH" -ne 1 ]; then
|
|
522
|
+
warn 'ausearch is unavailable; install the audit package to see denials.'
|
|
523
|
+
AVC_COUNT=-1
|
|
524
|
+
return 0
|
|
525
|
+
fi
|
|
526
|
+
local raw
|
|
527
|
+
if ! privileged_read ausearch -m AVC,USER_AVC,SELINUX_ERR "${AUDIT_SINCE_ARGS[@]}" --raw >/dev/null 2>&1; then
|
|
528
|
+
if [ "$(id -u)" -ne 0 ] && ! sudo -n true 2>/dev/null; then
|
|
529
|
+
warn 'the audit log needs root and passwordless sudo is unavailable; re-run this stage as root.'
|
|
530
|
+
AVC_COUNT=-1
|
|
531
|
+
return 0
|
|
532
|
+
fi
|
|
533
|
+
fi
|
|
534
|
+
raw="$(privileged_read ausearch -m AVC,USER_AVC,SELINUX_ERR "${AUDIT_SINCE_ARGS[@]}" --raw 2>/dev/null || true)"
|
|
535
|
+
AVC_COUNT="$(printf '%s' "$raw" | grep -c 'avc: denied' || true)"
|
|
536
|
+
if [ "$AVC_COUNT" -eq 0 ]; then
|
|
537
|
+
ok 'no denials in the window'
|
|
538
|
+
return 0
|
|
539
|
+
fi
|
|
540
|
+
warn "$AVC_COUNT denial record(s); grouped by source domain, target type and target class:"
|
|
541
|
+
local denials
|
|
542
|
+
denials="$(printf '%s' "$raw" | grep 'avc: denied' || true)"
|
|
543
|
+
printf '%s' "$denials" \
|
|
544
|
+
| sed -E 's/.*comm="?([^" ]+)"?.*scontext=([^ ]+).*tcontext=([^ ]+).*tclass=([^ ]+).*/\1 \2 \3 \4/' \
|
|
545
|
+
| awk '{ sub(/:c[0-9,c]+$/, "", $2); print }' \
|
|
546
|
+
| sort | uniq -c | sort -rn | head -20 | sed 's/^/ /'
|
|
547
|
+
|
|
548
|
+
# Not every denial is a labeling problem, and telling an operator to relabel something that
|
|
549
|
+
# carries no file label at all sends them after the wrong fix. A denial whose target class is
|
|
550
|
+
# a filesystem object is answered by `normalize`; one whose target is another domain — MCS
|
|
551
|
+
# category separation on a keyring, a socket, a process — is not, and stays after any relabel.
|
|
552
|
+
AVC_FILE_COUNT="$(printf '%s' "$denials" | grep -cE 'tclass=(file|dir|lnk_file|chr_file|blk_file|fifo_file|sock_file)' || true)"
|
|
553
|
+
AVC_OTHER_COUNT=$((AVC_COUNT - AVC_FILE_COUNT))
|
|
554
|
+
say ''
|
|
555
|
+
info "filesystem-object denials : $AVC_FILE_COUNT (resolved by labeling — see the mount report above)"
|
|
556
|
+
info "other-class denials : $AVC_OTHER_COUNT (NOT resolved by labeling)"
|
|
557
|
+
if [ "$AVC_OTHER_COUNT" -gt 0 ]; then
|
|
558
|
+
printf '%s' "$denials" \
|
|
559
|
+
| grep -vE 'tclass=(file|dir|lnk_file|chr_file|blk_file|fifo_file|sock_file)' \
|
|
560
|
+
| sed -E 's/.*comm="?([^" ]+)"?.*tclass=([^ ]+).*/\1 tclass=\2/' \
|
|
561
|
+
| sort | uniq -c | sort -rn | head -10 | sed 's/^/ /'
|
|
562
|
+
info 'Relabeling will not clear these. Confirm each is tolerated by the workload before'
|
|
563
|
+
info ' enforcing — a keyring or socket probe an application already ignores is harmless,'
|
|
564
|
+
info ' a denial it depends on is not.'
|
|
565
|
+
fi
|
|
566
|
+
return 0
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
# ---------------------------------------------------------------------------------- validate
|
|
570
|
+
|
|
571
|
+
validate() {
|
|
572
|
+
head1 'Preconditions'
|
|
573
|
+
local failures=0
|
|
574
|
+
|
|
575
|
+
if [ "$SELINUX_ENABLED" -ne 1 ]; then
|
|
576
|
+
bad 'SELinux is disabled. A relabel is meaningless until it is enabled and the filesystem'
|
|
577
|
+
info ' has been relabeled at boot. Set SELINUX=permissive in /etc/selinux/config, touch'
|
|
578
|
+
info ' /.autorelabel, and reboot during a maintenance window before running this script.'
|
|
579
|
+
failures=$((failures + 1))
|
|
580
|
+
else
|
|
581
|
+
ok "SELinux enabled, mode $SELINUX_MODE, policy $SELINUX_POLICY"
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
if [ "$SELINUX_POLICY" != 'targeted' ]; then
|
|
585
|
+
bad "policy is '$SELINUX_POLICY'; this script only reasons about the targeted policy."
|
|
586
|
+
failures=$((failures + 1))
|
|
587
|
+
fi
|
|
588
|
+
|
|
589
|
+
if [ "$HAVE_SEMANAGE" -ne 1 ]; then
|
|
590
|
+
bad 'semanage is missing. Persistent mappings cannot be registered, and chcon alone would'
|
|
591
|
+
info ' be undone by the next relabel. Remediation:'
|
|
592
|
+
info " $SUDO dnf install -y policycoreutils-python-utils"
|
|
593
|
+
failures=$((failures + 1))
|
|
594
|
+
else
|
|
595
|
+
ok 'semanage available'
|
|
596
|
+
fi
|
|
597
|
+
|
|
598
|
+
if [ "$HAVE_RESTORECON" -ne 1 ]; then
|
|
599
|
+
bad "restorecon is missing. Remediation: $SUDO dnf install -y policycoreutils"
|
|
600
|
+
failures=$((failures + 1))
|
|
601
|
+
else
|
|
602
|
+
ok 'restorecon available'
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
if [ "$KUBE_AVAILABLE" -eq 1 ]; then
|
|
606
|
+
if [ "$NODES_TOTAL" -gt 0 ] && [ "$NODES_READY" -eq "$NODES_TOTAL" ]; then
|
|
607
|
+
ok "all $NODES_TOTAL node(s) Ready"
|
|
608
|
+
else
|
|
609
|
+
warn "$NODES_READY/$NODES_TOTAL nodes Ready — normalization is still safe (it touches no"
|
|
610
|
+
info ' workload), but do not proceed to the Enforcing stage on a degraded cluster.'
|
|
611
|
+
fi
|
|
612
|
+
local notrunning
|
|
613
|
+
notrunning="$(kubectl_q get pods -A --no-headers | awk '$4 !~ /^(Running|Completed|Succeeded)$/' | grep -c . || true)"
|
|
614
|
+
[ "$notrunning" -eq 0 ] && ok 'every pod is Running/Completed' \
|
|
615
|
+
|| warn "$notrunning pod(s) are neither Running nor Completed"
|
|
616
|
+
else
|
|
617
|
+
warn 'Kubernetes is unreachable; discovery used the declared platform roots only.'
|
|
618
|
+
fi
|
|
619
|
+
|
|
620
|
+
if [ "${#STORAGE_PATHS[@]}" -eq 0 ]; then
|
|
621
|
+
bad 'discovery selected no storage root; there is nothing this run could normalize.'
|
|
622
|
+
failures=$((failures + 1))
|
|
623
|
+
fi
|
|
624
|
+
|
|
625
|
+
# Belt and braces: the same guard discovery already applied, re-checked here so a future
|
|
626
|
+
# change to discovery cannot route around it on the way to `--apply`.
|
|
627
|
+
local path
|
|
628
|
+
for path in ${STORAGE_PATHS[@]+"${STORAGE_PATHS[@]}"}; do
|
|
629
|
+
if is_protected_path "$path"; then
|
|
630
|
+
bad "refusing to operate on system directory '$path'"
|
|
631
|
+
failures=$((failures + 1))
|
|
632
|
+
elif is_protected_type "$path"; then
|
|
633
|
+
bad "refusing to relabel '$path': it carries the protected type '$(path_type "$path")'"
|
|
634
|
+
failures=$((failures + 1))
|
|
635
|
+
fi
|
|
636
|
+
done
|
|
637
|
+
|
|
638
|
+
if [ "${#REFUSED[@]}" -gt 0 ]; then
|
|
639
|
+
ok "${#REFUSED[@]} discovered path(s) excluded by the protection rules (listed above)"
|
|
640
|
+
fi
|
|
641
|
+
if [ "${#UNCOVERED[@]}" -gt 0 ]; then
|
|
642
|
+
warn "${#UNCOVERED[@]} PersistentVolume path(s) fall outside every managed root and will not be"
|
|
643
|
+
info ' labeled by this run. That is correct for a volume pinned to another node; for one on'
|
|
644
|
+
info ' this node it means the fix is incomplete — add it with --path.'
|
|
645
|
+
fi
|
|
646
|
+
|
|
647
|
+
if [ "$failures" -eq 0 ]; then
|
|
648
|
+
ok 'all preconditions met'
|
|
649
|
+
return 0
|
|
650
|
+
fi
|
|
651
|
+
bad "$failures precondition(s) failed; no mutation will be attempted."
|
|
652
|
+
return 1
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
# --------------------------------------------------------------------------------- normalize
|
|
656
|
+
|
|
657
|
+
state_file() { printf '%s/fcontext-added.txt' "$STATE_DIR"; }
|
|
658
|
+
|
|
659
|
+
record_state() {
|
|
660
|
+
[ "$APPLY" -eq 1 ] || return 0
|
|
661
|
+
$SUDO install -d -m 0750 "$STATE_DIR"
|
|
662
|
+
printf '%s\n' "$1" | $SUDO tee -a "$(state_file)" >/dev/null
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
fcontext_registered() {
|
|
666
|
+
privileged_read semanage fcontext -l 2>/dev/null | grep -qE "^$(printf '%s' "$1" | sed 's/[][\\.^$*+?(){}|]/\\&/g')\(/\.\*\)\? " || return 1
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
normalize() {
|
|
670
|
+
head1 'Normalizing labels'
|
|
671
|
+
[ "$APPLY" -eq 1 ] || warn 'dry run: pass --apply to perform these changes'
|
|
672
|
+
local path expr
|
|
673
|
+
for path in "${STORAGE_PATHS[@]}"; do
|
|
674
|
+
expr="${path}(/.*)?"
|
|
675
|
+
if [ ! -e "$path" ]; then
|
|
676
|
+
# Only ever created, never re-moded: an existing tree keeps the ownership and mode the
|
|
677
|
+
# workload's own init container set.
|
|
678
|
+
run "$SUDO install -d -m 0755 '$path'"
|
|
679
|
+
fi
|
|
680
|
+
if fcontext_registered "$path"; then
|
|
681
|
+
ok "mapping already registered: $expr -> $SHARED_TYPE"
|
|
682
|
+
else
|
|
683
|
+
# `-a` falls back to `-m`: some semanage versions treat an entry that already exists
|
|
684
|
+
# as an error rather than a warning, and under `set -e` that would abort the run
|
|
685
|
+
# partway through, leaving some roots relabeled and others not.
|
|
686
|
+
run "$SUDO semanage fcontext -a -t $SHARED_TYPE '$expr' 2>/dev/null || $SUDO semanage fcontext -m -t $SHARED_TYPE '$expr'"
|
|
687
|
+
# Recorded only when the mapping was absent beforehand, so a rollback never deletes an
|
|
688
|
+
# entry that a previous `underpost cluster` run legitimately owns.
|
|
689
|
+
record_state "$path"
|
|
690
|
+
fi
|
|
691
|
+
done
|
|
692
|
+
for path in "${STORAGE_PATHS[@]}"; do
|
|
693
|
+
[ -e "$path" ] || continue
|
|
694
|
+
# -R descends into the volume trees; -F resets the SELinux user as well, because entries
|
|
695
|
+
# created by the CLI carry unconfined_u where the runtime's carry system_u. Contents,
|
|
696
|
+
# ownership, mode, ACLs and other xattrs are untouched.
|
|
697
|
+
#
|
|
698
|
+
# `|| true` on the pipeline: restorecon exits non-zero when it cannot relabel a single
|
|
699
|
+
# entry (a file deleted mid-walk by a running database is routine), and one such entry
|
|
700
|
+
# must not abandon the remaining roots. A leftover mislabel is caught by `verify`.
|
|
701
|
+
run "{ $SUDO restorecon -RFv '$path' || true; } | tail -40"
|
|
702
|
+
done
|
|
703
|
+
if [ "$APPLY" -eq 1 ]; then
|
|
704
|
+
# Written last: `verify` measures denials from here, so it must mark the point at which
|
|
705
|
+
# every label was already correct.
|
|
706
|
+
$SUDO install -d -m 0750 "$STATE_DIR"
|
|
707
|
+
date '+%m/%d/%Y %H:%M:%S' | $SUDO tee "$(normalize_marker_file)" >/dev/null
|
|
708
|
+
ok 'normalization applied'
|
|
709
|
+
info "verify will count denials from $(cat "$(normalize_marker_file)" 2>/dev/null || echo now)"
|
|
710
|
+
else
|
|
711
|
+
warn 'nothing was changed'
|
|
712
|
+
fi
|
|
713
|
+
return 0
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
# ------------------------------------------------------------------------------------ verify
|
|
717
|
+
|
|
718
|
+
verify() {
|
|
719
|
+
head1 'Verification'
|
|
720
|
+
local failures=0
|
|
721
|
+
|
|
722
|
+
report_labels
|
|
723
|
+
if [ "${MISMATCHED_COUNT:-0}" -eq 0 ] && [ "${MISSING_COUNT:-0}" -eq 0 ]; then
|
|
724
|
+
ok 'every discovered storage root carries the shared container type'
|
|
725
|
+
else
|
|
726
|
+
bad "${MISMATCHED_COUNT:-0} root(s) mislabeled, ${MISSING_COUNT:-0} absent"
|
|
727
|
+
failures=$((failures + 1))
|
|
728
|
+
fi
|
|
729
|
+
|
|
730
|
+
if [ "$KUBE_AVAILABLE" -eq 1 ]; then
|
|
731
|
+
head1 'Workload health'
|
|
732
|
+
local bound unbound
|
|
733
|
+
bound="$(kubectl_q get pvc -A --no-headers | awk '$3 == "Bound"' | grep -c . || true)"
|
|
734
|
+
unbound="$(kubectl_q get pvc -A --no-headers | awk '$3 != "Bound"' | grep -c . || true)"
|
|
735
|
+
info "PVCs bound: $bound, not bound: $unbound"
|
|
736
|
+
[ "$unbound" -eq 0 ] || { bad 'some PVCs are not Bound'; failures=$((failures + 1)); }
|
|
737
|
+
|
|
738
|
+
local unhealthy
|
|
739
|
+
unhealthy="$(kubectl_q get pods -A --no-headers | awk '$4 !~ /^(Running|Completed|Succeeded)$/' || true)"
|
|
740
|
+
if [ -n "$unhealthy" ]; then
|
|
741
|
+
bad 'pods not in a healthy phase:'
|
|
742
|
+
printf '%s\n' "$unhealthy" | sed 's/^/ /'
|
|
743
|
+
failures=$((failures + 1))
|
|
744
|
+
else
|
|
745
|
+
ok 'every pod is Running/Completed'
|
|
746
|
+
fi
|
|
747
|
+
|
|
748
|
+
local restarts
|
|
749
|
+
restarts="$(kubectl_q get pods -A --no-headers | awk '$5 > 0 {print " " $0}' | head -10 || true)"
|
|
750
|
+
[ -n "$restarts" ] && { warn 'pods with restarts (check whether they predate this run):'; printf '%s\n' "$restarts"; }
|
|
751
|
+
else
|
|
752
|
+
warn 'Kubernetes unreachable; workload health could not be verified.'
|
|
753
|
+
failures=$((failures + 1))
|
|
754
|
+
fi
|
|
755
|
+
|
|
756
|
+
# A scheduled workload declaring a mount no container can use is a denial that has not
|
|
757
|
+
# happened yet, not an absent one — and it is invisible in the audit log until that workload
|
|
758
|
+
# next runs. Failing here is what keeps `verify` from blessing a host whose nightly CronJob
|
|
759
|
+
# breaks the moment Enforcing is active.
|
|
760
|
+
if [ "${#RESIDUAL[@]}" -gt 0 ]; then
|
|
761
|
+
if [ "$ALLOW_UNLABELED_MOUNTS" -eq 1 ]; then
|
|
762
|
+
warn "${#RESIDUAL[@]} unresolved workload hostPath mount(s), accepted via --allow-unlabeled-mounts"
|
|
763
|
+
else
|
|
764
|
+
bad "${#RESIDUAL[@]} workload hostPath mount(s) are still unresolved (listed above). Each one"
|
|
765
|
+
info ' denies under Enforcing the next time its workload runs, whether or not a pod exists now.'
|
|
766
|
+
info ' Resolve them, or re-run with --allow-unlabeled-mounts to accept the breakage knowingly.'
|
|
767
|
+
failures=$((failures + 1))
|
|
768
|
+
fi
|
|
769
|
+
fi
|
|
770
|
+
|
|
771
|
+
local uncovered
|
|
772
|
+
for uncovered in ${UNCOVERED[@]+"${UNCOVERED[@]}"}; do
|
|
773
|
+
[ -e "$uncovered" ] || continue
|
|
774
|
+
if [ "$(path_type "$uncovered")" != "$SHARED_TYPE" ]; then
|
|
775
|
+
bad "$uncovered exists on this host, is not covered by any managed root, and carries"
|
|
776
|
+
info " '$(path_type "$uncovered")'. Re-run with --path '$uncovered' if it is workload data."
|
|
777
|
+
failures=$((failures + 1))
|
|
778
|
+
fi
|
|
779
|
+
done
|
|
780
|
+
|
|
781
|
+
report_avc
|
|
782
|
+
if [ "${AVC_COUNT:-0}" -gt 0 ]; then
|
|
783
|
+
bad "denials are still being recorded; do not transition to Enforcing yet."
|
|
784
|
+
failures=$((failures + 1))
|
|
785
|
+
elif [ "${AVC_COUNT:-0}" -lt 0 ]; then
|
|
786
|
+
warn 'AVC state unknown (no ausearch); the Enforcing stage will refuse to run.'
|
|
787
|
+
failures=$((failures + 1))
|
|
788
|
+
fi
|
|
789
|
+
|
|
790
|
+
head1 'Verdict'
|
|
791
|
+
if [ "$failures" -eq 0 ]; then
|
|
792
|
+
ok 'safe to proceed toward Enforcing'
|
|
793
|
+
return 0
|
|
794
|
+
fi
|
|
795
|
+
bad "$failures check(s) failed; NOT safe to proceed toward Enforcing"
|
|
796
|
+
return 1
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
# ----------------------------------------------------------------------------------- enforce
|
|
800
|
+
|
|
801
|
+
enforce() {
|
|
802
|
+
head1 'Controlled Enforcing transition'
|
|
803
|
+
if [ "$SELINUX_MODE" = 'Enforcing' ]; then
|
|
804
|
+
ok 'already Enforcing; only the persisted config is checked'
|
|
805
|
+
fi
|
|
806
|
+
if [ "$APPLY" -ne 1 ]; then
|
|
807
|
+
warn 'dry run: pass --apply to perform the transition'
|
|
808
|
+
elif [ "$ASSUME_YES" -ne 1 ] && [ "$SELINUX_MODE" != 'Enforcing' ]; then
|
|
809
|
+
printf ' Transition this host to Enforcing now? [y/N] '
|
|
810
|
+
local answer; read -r answer
|
|
811
|
+
case "$answer" in [yY]|[yY][eE][sS]) ;; *) warn 'aborted by operator'; return 1 ;; esac
|
|
812
|
+
fi
|
|
813
|
+
# setenforce first: it is instantly reversible with `setenforce 0` and needs no reboot. The
|
|
814
|
+
# config file is only persisted after the runtime switch has been taken and re-verified, so a
|
|
815
|
+
# host that misbehaves under Enforcing never reboots into it.
|
|
816
|
+
run "$SUDO setenforce 1"
|
|
817
|
+
if [ "$APPLY" -eq 1 ]; then
|
|
818
|
+
say ''
|
|
819
|
+
info 'Runtime mode is now Enforcing. Re-verifying before persisting the config...'
|
|
820
|
+
AVC_SINCE="$(date '+%m/%d/%Y %H:%M:%S')"
|
|
821
|
+
AVC_SINCE_EXPLICIT=1
|
|
822
|
+
if ! verify; then
|
|
823
|
+
bad 'post-transition verification failed. Reverting the runtime mode; config untouched.'
|
|
824
|
+
$SUDO setenforce 0
|
|
825
|
+
return 1
|
|
826
|
+
fi
|
|
827
|
+
run "$SUDO sed -i -E 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config"
|
|
828
|
+
ok 'Enforcing is now both active and persisted.'
|
|
829
|
+
info "Revert at any time with: $SUDO setenforce 0"
|
|
830
|
+
info "Revert persistently with: $SUDO sed -i -E 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config"
|
|
831
|
+
fi
|
|
832
|
+
return 0
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
# ---------------------------------------------------------------------------------- rollback
|
|
836
|
+
|
|
837
|
+
rollback() {
|
|
838
|
+
head1 'Rollback'
|
|
839
|
+
local file; file="$(state_file)"
|
|
840
|
+
if [ ! -r "$file" ]; then
|
|
841
|
+
warn "no recorded changes at $file; nothing to roll back"
|
|
842
|
+
return 0
|
|
843
|
+
fi
|
|
844
|
+
[ "$APPLY" -eq 1 ] || warn 'dry run: pass --apply to perform the rollback'
|
|
845
|
+
local path
|
|
846
|
+
while read -r path; do
|
|
847
|
+
[ -n "$path" ] || continue
|
|
848
|
+
run "$SUDO semanage fcontext -d '${path}(/.*)?' || true"
|
|
849
|
+
[ -e "$path" ] && run "$SUDO restorecon -RF '$path'" || true
|
|
850
|
+
done < "$file"
|
|
851
|
+
# Directories this script created and files it wrote are left in place: removing a node
|
|
852
|
+
# storage root would destroy live PersistentVolume data.
|
|
853
|
+
run "$SUDO rm -f '$file'"
|
|
854
|
+
ok 'mappings this script added have been removed; storage contents were not touched.'
|
|
855
|
+
return 0
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
# -------------------------------------------------------------------------------------- main
|
|
859
|
+
|
|
860
|
+
inspect_selinux
|
|
861
|
+
inspect_kubernetes
|
|
862
|
+
discover_pv_paths
|
|
863
|
+
|
|
864
|
+
case "$STAGE" in
|
|
865
|
+
inspect)
|
|
866
|
+
report_labels
|
|
867
|
+
report_avc
|
|
868
|
+
say ''
|
|
869
|
+
info "Next: $0 validate"
|
|
870
|
+
;;
|
|
871
|
+
validate)
|
|
872
|
+
report_labels
|
|
873
|
+
report_avc
|
|
874
|
+
validate
|
|
875
|
+
;;
|
|
876
|
+
normalize)
|
|
877
|
+
validate || exit 1
|
|
878
|
+
report_labels
|
|
879
|
+
normalize
|
|
880
|
+
say ''
|
|
881
|
+
info "Next: $0 verify --since recent"
|
|
882
|
+
;;
|
|
883
|
+
verify)
|
|
884
|
+
verify
|
|
885
|
+
;;
|
|
886
|
+
enforce)
|
|
887
|
+
validate || exit 1
|
|
888
|
+
verify || { bad 'refusing to transition to Enforcing while verification fails'; exit 1; }
|
|
889
|
+
enforce
|
|
890
|
+
;;
|
|
891
|
+
mark)
|
|
892
|
+
head1 'Denial window'
|
|
893
|
+
if [ "$APPLY" -eq 1 ]; then
|
|
894
|
+
$SUDO install -d -m 0750 "$STATE_DIR"
|
|
895
|
+
date '+%m/%d/%Y %H:%M:%S' | $SUDO tee "$(normalize_marker_file)" >/dev/null
|
|
896
|
+
ok "verify will now count denials from $(cat "$(normalize_marker_file)")"
|
|
897
|
+
info 'Labels and SELinux mode were not touched.'
|
|
898
|
+
else
|
|
899
|
+
warn 'dry run: pass --apply to move the window to now'
|
|
900
|
+
fi
|
|
901
|
+
;;
|
|
902
|
+
rollback)
|
|
903
|
+
rollback
|
|
904
|
+
;;
|
|
905
|
+
*)
|
|
906
|
+
usage >&2; exit 2 ;;
|
|
907
|
+
esac
|