@aifabrix/builder 2.42.1 → 2.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/anchor-docs.mdc +15 -0
- package/README.md +2 -2
- package/anchor-docs/README.md +10 -0
- package/anchor-docs/_TEMPLATE +24 -0
- package/bin/aifabrix.js +13 -4
- package/integration/hubspot-test/README.md +157 -0
- package/integration/{hubspot → hubspot-test}/application.json +6 -6
- package/integration/{hubspot → hubspot-test}/create-hubspot.js +10 -10
- package/integration/hubspot-test/env.template +4 -0
- package/integration/hubspot-test/hubspot-test-datasource-company.json +138 -0
- package/integration/hubspot-test/hubspot-test-datasource-contact.json +146 -0
- package/integration/hubspot-test/hubspot-test-datasource-deal.json +146 -0
- package/integration/hubspot-test/hubspot-test-datasource-users.json +76 -0
- package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +201 -24
- package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
- package/integration/hubspot-test/rbac.json +166 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +1 -7
- package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +3 -3
- package/integration/{hubspot → hubspot-test}/test.js +137 -102
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
- package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +23 -0
- package/integration/roundtrip-test-local/README.md +144 -0
- package/integration/roundtrip-test-local/application.yaml +13 -0
- package/integration/roundtrip-test-local/env.template +15 -0
- package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +14 -0
- package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +61 -0
- package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +25 -0
- package/integration/roundtrip-test-local2/README.md +144 -0
- package/integration/roundtrip-test-local2/application.yaml +13 -0
- package/integration/roundtrip-test-local2/env.template +15 -0
- package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +31 -0
- package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +86 -0
- package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +25 -0
- package/integration/test/wizard.yaml +8 -0
- package/jest.config.default.js +10 -0
- package/jest.config.integration.fixtures.js +22 -0
- package/jest.config.integration.js +21 -18
- package/jest.config.isolated.js +10 -0
- package/jest.projects.js +288 -0
- package/lib/api/datasources-core.api.js +3 -3
- package/lib/api/dev-mtls-request.js +110 -0
- package/lib/api/dev-server-https.js +145 -0
- package/lib/api/dev.api.js +133 -144
- package/lib/api/index.js +0 -1
- package/lib/api/pipeline.api.js +67 -20
- package/lib/api/service-users.api.js +111 -2
- package/lib/api/types/dev.types.js +4 -3
- package/lib/api/types/pipeline.types.js +8 -5
- package/lib/api/types/service-users.types.js +41 -0
- package/lib/api/types/validation-run.types.js +56 -0
- package/lib/api/validation-run.api.js +99 -0
- package/lib/api/validation-runner.js +99 -0
- package/lib/app/config.js +1 -1
- package/lib/app/deploy-status-display.js +2 -2
- package/lib/app/deploy.js +7 -6
- package/lib/app/display.js +2 -1
- package/lib/app/dockerfile.js +3 -2
- package/lib/app/down.js +2 -1
- package/lib/app/helpers.js +6 -5
- package/lib/app/index.js +27 -8
- package/lib/app/list.js +7 -6
- package/lib/app/push.js +4 -3
- package/lib/app/register.js +19 -8
- package/lib/app/rotate-secret.js +17 -13
- package/lib/app/run-container-start.js +184 -0
- package/lib/app/run-docker-fallback.js +108 -0
- package/lib/app/run-env-compose.js +30 -42
- package/lib/app/run-helpers.js +49 -126
- package/lib/app/run-infra-requirements.js +30 -0
- package/lib/app/run-resolve-image.js +21 -0
- package/lib/app/run.js +74 -21
- package/lib/app/show-display.js +1 -1
- package/lib/app/show.js +1 -1
- package/lib/build/index.js +13 -10
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-app.help.js +67 -0
- package/lib/cli/setup-app.js +59 -123
- package/lib/cli/setup-app.test-commands.js +179 -0
- package/lib/cli/setup-auth.js +36 -14
- package/lib/cli/setup-credential-deployment.js +22 -8
- package/lib/cli/setup-dev-path-commands.js +124 -0
- package/lib/cli/setup-dev.js +190 -103
- package/lib/cli/setup-environment.js +11 -20
- package/lib/cli/setup-external-system.js +62 -22
- package/lib/cli/setup-infra.js +139 -47
- package/lib/cli/setup-parameters.js +32 -0
- package/lib/cli/setup-secrets.js +147 -10
- package/lib/cli/setup-service-user.js +146 -20
- package/lib/cli/setup-utility.js +47 -19
- package/lib/commands/app-down.js +5 -7
- package/lib/commands/app-install.js +14 -7
- package/lib/commands/app-logs.js +13 -10
- package/lib/commands/app-shell.js +4 -1
- package/lib/commands/app-test.js +25 -19
- package/lib/commands/app.js +22 -10
- package/lib/commands/auth-config.js +10 -14
- package/lib/commands/auth-status.js +4 -3
- package/lib/commands/credential-env.js +4 -3
- package/lib/commands/credential-list.js +5 -4
- package/lib/commands/credential-push.js +4 -3
- package/lib/commands/datasource-unified-test-cli.js +495 -0
- package/lib/commands/datasource-unified-test-cli.options.js +149 -0
- package/lib/commands/datasource-validation-cli.js +129 -0
- package/lib/commands/datasource.js +123 -71
- package/lib/commands/deployment-list.js +6 -5
- package/lib/commands/dev-cli-handlers.js +122 -18
- package/lib/commands/dev-down.js +4 -3
- package/lib/commands/dev-init.js +231 -116
- package/lib/commands/dev-show-display.js +473 -0
- package/lib/commands/login-credentials.js +3 -2
- package/lib/commands/login-device.js +4 -3
- package/lib/commands/login.js +5 -4
- package/lib/commands/logout.js +8 -7
- package/lib/commands/parameters-validate.js +54 -0
- package/lib/commands/repair-datasource.js +314 -68
- package/lib/commands/repair-env-template.js +16 -10
- package/lib/commands/repair-rbac.js +25 -19
- package/lib/commands/repair.js +116 -32
- package/lib/commands/secrets-list.js +23 -12
- package/lib/commands/secrets-remove-all.js +220 -0
- package/lib/commands/secrets-remove.js +22 -13
- package/lib/commands/secrets-set.js +21 -12
- package/lib/commands/secrets-validate.js +20 -7
- package/lib/commands/secure.js +10 -9
- package/lib/commands/service-user.js +243 -13
- package/lib/commands/test-e2e-external.js +27 -1
- package/lib/commands/up-common.js +28 -2
- package/lib/commands/up-dataplane.js +31 -18
- package/lib/commands/up-miso.js +19 -29
- package/lib/commands/upload.js +138 -39
- package/lib/commands/wizard-core-helpers.js +1 -1
- package/lib/commands/wizard-dataplane.js +4 -3
- package/lib/commands/wizard-helpers.js +3 -3
- package/lib/commands/wizard.js +2 -2
- package/lib/core/admin-secrets.js +16 -5
- package/lib/core/audit-logger.js +12 -4
- package/lib/core/config-attach-extensions.js +46 -0
- package/lib/core/config-runtime-paths.js +29 -0
- package/lib/core/config.js +59 -58
- package/lib/core/diff.js +3 -2
- package/lib/core/ensure-encryption-key.js +2 -4
- package/lib/core/secrets-ensure-infra.js +77 -0
- package/lib/core/secrets-ensure.js +120 -64
- package/lib/core/secrets-env-write.js +35 -7
- package/lib/core/secrets-infra-placeholder-sync.js +61 -0
- package/lib/core/secrets.js +228 -42
- package/lib/core/templates-env.js +4 -3
- package/lib/core/templates.js +1 -1
- package/lib/datasource/abac-validator.js +148 -0
- package/lib/datasource/deploy.js +75 -53
- package/lib/datasource/field-reference-validator.js +77 -36
- package/lib/datasource/integration-context.js +63 -0
- package/lib/datasource/list.js +8 -7
- package/lib/datasource/log-viewer.js +252 -0
- package/lib/datasource/resolve-app.js +109 -0
- package/lib/datasource/test-e2e.js +95 -155
- package/lib/datasource/test-integration.js +121 -109
- package/lib/datasource/unified-validation-run-body.js +65 -0
- package/lib/datasource/unified-validation-run-post.js +23 -0
- package/lib/datasource/unified-validation-run-resolve.js +43 -0
- package/lib/datasource/unified-validation-run.js +92 -0
- package/lib/datasource/validate.js +162 -15
- package/lib/deployment/deployer.js +4 -3
- package/lib/deployment/environment.js +7 -6
- package/lib/deployment/push.js +17 -8
- package/lib/external-system/delete.js +4 -3
- package/lib/external-system/deploy.js +131 -53
- package/lib/external-system/download-helpers.js +1 -1
- package/lib/external-system/download.js +7 -6
- package/lib/external-system/generator.js +104 -14
- package/lib/external-system/integration-test-dispatch.js +26 -0
- package/lib/external-system/test-execution.js +5 -1
- package/lib/external-system/test-helpers.js +0 -4
- package/lib/external-system/test-system-level-helpers.js +110 -0
- package/lib/external-system/test-system-level.js +83 -44
- package/lib/external-system/test.js +59 -8
- package/lib/generator/builders.js +23 -11
- package/lib/generator/deploy-manifest-azure-kv.js +81 -0
- package/lib/generator/external-controller-manifest.js +3 -3
- package/lib/generator/external.js +23 -11
- package/lib/generator/helpers.js +71 -12
- package/lib/generator/index.js +8 -4
- package/lib/generator/split-readme.js +12 -7
- package/lib/generator/split-variables.js +2 -1
- package/lib/generator/split.js +46 -11
- package/lib/generator/wizard-readme.js +3 -3
- package/lib/generator/wizard.js +16 -13
- package/lib/infrastructure/compose.js +60 -6
- package/lib/infrastructure/helpers.js +238 -51
- package/lib/infrastructure/index.js +64 -37
- package/lib/infrastructure/services.js +21 -15
- package/lib/internal/fs-real-sync.js +104 -0
- package/lib/internal/node-fs.js +98 -0
- package/lib/parameters/database-secret-values.js +173 -0
- package/lib/parameters/infra-kv-discovery.js +121 -0
- package/lib/parameters/infra-parameter-catalog.js +458 -0
- package/lib/parameters/infra-parameter-validate.js +64 -0
- package/lib/schema/application-schema.json +37 -17
- package/lib/schema/datasource-test-run.schema.json +493 -0
- package/lib/schema/deployment-rules.yaml +102 -63
- package/lib/schema/external-datasource.schema.json +1201 -433
- package/lib/schema/external-system.schema.json +181 -5
- package/lib/schema/flag-map-validation-run.json +31 -0
- package/lib/schema/infra-parameter.schema.json +106 -0
- package/lib/schema/infra.parameter.yaml +421 -0
- package/lib/schema/type/credential-auth-templates.json +40 -0
- package/lib/schema/type/document-storage.json +213 -0
- package/lib/schema/type/message-service.json +123 -0
- package/lib/schema/type/vector-store.json +88 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +132 -0
- package/lib/utils/api-error-handler.js +2 -2
- package/lib/utils/api.js +49 -14
- package/lib/utils/app-config-resolver.js +23 -1
- package/lib/utils/app-register-api.js +3 -2
- package/lib/utils/app-register-auth.js +1 -1
- package/lib/utils/app-register-config.js +4 -4
- package/lib/utils/app-register-display.js +3 -2
- package/lib/utils/app-register-validator.js +3 -2
- package/lib/utils/app-run-containers.js +26 -22
- package/lib/utils/app-scoped-config.js +31 -0
- package/lib/utils/app-service-env-from-builder.js +164 -0
- package/lib/utils/build-copy.js +1 -1
- package/lib/utils/build-helpers.js +20 -20
- package/lib/utils/build-resolve-image.js +165 -0
- package/lib/utils/cli-layout-chalk.js +8 -0
- package/lib/utils/cli-test-layout-chalk.js +267 -0
- package/lib/utils/cli-utils.js +88 -11
- package/lib/utils/compose-db-passwords.js +138 -0
- package/lib/utils/compose-generate-docker-compose.js +216 -0
- package/lib/utils/compose-generator.js +197 -291
- package/lib/utils/compose-miso-env.js +18 -0
- package/lib/utils/compose-traefik-ingress-base.js +158 -0
- package/lib/utils/config-paths.js +209 -6
- package/lib/utils/config-scoped-resources-preference.js +41 -0
- package/lib/utils/controller-deployment-outcome.js +68 -0
- package/lib/utils/credential-display.js +2 -2
- package/lib/utils/credential-secrets-env.js +16 -1
- package/lib/utils/dataplane-pipeline-warning.js +4 -3
- package/lib/utils/datasource-test-run-capability-scope.js +43 -0
- package/lib/utils/datasource-test-run-debug-display.js +137 -0
- package/lib/utils/datasource-test-run-debug-slice.js +93 -0
- package/lib/utils/datasource-test-run-display.js +442 -0
- package/lib/utils/datasource-test-run-exit.js +58 -0
- package/lib/utils/datasource-test-run-legacy-adapter.js +93 -0
- package/lib/utils/datasource-test-run-report-version.js +51 -0
- package/lib/utils/datasource-test-run-schema-sync.js +59 -0
- package/lib/utils/datasource-test-run-tty-log.js +81 -0
- package/lib/utils/datasource-validation-watch.js +266 -0
- package/lib/utils/declarative-url-ports.js +47 -0
- package/lib/utils/derive-env-key-from-client-id.js +41 -0
- package/lib/utils/dev-ca-install.js +185 -23
- package/lib/utils/dev-cert-helper.js +266 -17
- package/lib/utils/dev-hosts-helper.js +307 -0
- package/lib/utils/dev-init-cert-hints.js +37 -0
- package/lib/utils/dev-init-health-messages.js +52 -0
- package/lib/utils/dev-init-resolve.js +86 -0
- package/lib/utils/dev-init-ssh-merge.js +65 -0
- package/lib/utils/dev-ssh-config-helper.js +196 -0
- package/lib/utils/dev-user-groups.js +93 -0
- package/lib/utils/docker-build.js +42 -17
- package/lib/utils/docker-exec.js +28 -0
- package/lib/utils/docker-manifest-public-port.js +116 -0
- package/lib/utils/docker-not-running-hint.js +52 -0
- package/lib/utils/docker.js +98 -11
- package/lib/utils/ensure-dev-certs-for-remote-docker.js +192 -0
- package/lib/utils/env-config-loader.js +10 -91
- package/lib/utils/env-copy.js +19 -10
- package/lib/utils/env-map.js +42 -11
- package/lib/utils/env-template.js +2 -2
- package/lib/utils/environment-scoped-resources.js +144 -0
- package/lib/utils/error-formatter.js +125 -9
- package/lib/utils/error-formatters/http-status-errors.js +6 -5
- package/lib/utils/error-formatters/network-errors.js +2 -1
- package/lib/utils/error-formatters/permission-errors.js +2 -1
- package/lib/utils/error-formatters/validation-errors.js +2 -1
- package/lib/utils/external-env-template.js +180 -0
- package/lib/utils/external-readme.js +8 -1
- package/lib/utils/external-system-display.js +277 -136
- package/lib/utils/external-system-local-test-tty.js +389 -0
- package/lib/utils/external-system-readiness-core.js +377 -0
- package/lib/utils/external-system-readiness-deploy-display.js +270 -0
- package/lib/utils/external-system-readiness-display-internals.js +150 -0
- package/lib/utils/external-system-readiness-display.js +186 -0
- package/lib/utils/external-system-test-helpers.js +24 -6
- package/lib/utils/external-system-validators.js +32 -14
- package/lib/utils/health-check-url.js +119 -0
- package/lib/utils/health-check.js +59 -25
- package/lib/utils/help-builder.js +14 -13
- package/lib/utils/image-version.js +4 -8
- package/lib/utils/infra-containers.js +4 -7
- package/lib/utils/infra-env-defaults.js +162 -0
- package/lib/utils/infra-status-display.js +167 -0
- package/lib/utils/infra-status.js +16 -8
- package/lib/utils/local-secrets.js +29 -7
- package/lib/utils/paths.js +136 -48
- package/lib/utils/port-resolver.js +10 -23
- package/lib/utils/redis-env-scope.js +62 -0
- package/lib/utils/register-aifabrix-shell-env.js +204 -0
- package/lib/utils/remote-builder-validation.js +99 -0
- package/lib/utils/remote-dev-auth.js +117 -21
- package/lib/utils/remote-docker-env.js +67 -15
- package/lib/utils/remote-secrets-loader.js +13 -4
- package/lib/utils/resolve-docker-image-ref.js +124 -0
- package/lib/utils/schema-loader.js +22 -9
- package/lib/utils/secrets-bash-kv.js +25 -0
- package/lib/utils/secrets-generator.js +171 -51
- package/lib/utils/secrets-helpers.js +70 -59
- package/lib/utils/secrets-kv-scope.js +60 -0
- package/lib/utils/secrets-utils.js +35 -37
- package/lib/utils/secrets-validation.js +3 -1
- package/lib/utils/secrets-yaml-preserve.js +109 -0
- package/lib/utils/secure-file-permissions.js +91 -0
- package/lib/utils/ssh-key-helper.js +4 -2
- package/lib/utils/template-helpers.js +2 -2
- package/lib/utils/test-log-writer.js +3 -3
- package/lib/utils/token-manager.js +37 -5
- package/lib/utils/url-declarative-public-base.js +188 -0
- package/lib/utils/url-declarative-resolve-build.js +493 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +51 -0
- package/lib/utils/url-declarative-resolve.js +220 -0
- package/lib/utils/url-declarative-token-parse.js +74 -0
- package/lib/utils/url-declarative-url-flags.js +50 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +99 -0
- package/lib/utils/url-public-path-prefix.js +34 -0
- package/lib/utils/urls-local-registry.js +220 -0
- package/lib/utils/validation-report-tty-kit.js +77 -0
- package/lib/utils/validation-run-poll.js +89 -0
- package/lib/utils/validation-run-post-retry.js +73 -0
- package/lib/utils/validation-run-request.js +98 -0
- package/lib/utils/variable-transformer.js +21 -4
- package/lib/utils/yaml-preserve.js +78 -1
- package/lib/validation/datasource-warnings.js +56 -0
- package/lib/validation/env-template-auth.js +50 -2
- package/lib/validation/external-manifest-validator.js +35 -7
- package/lib/validation/validate-display.js +37 -31
- package/lib/validation/validate.js +9 -10
- package/lib/validation/validator-unresolved-placeholders.js +98 -0
- package/lib/validation/validator.js +32 -78
- package/lib/validation/wizard-config-validator.js +2 -1
- package/package.json +11 -3
- package/scripts/check-datasource-test-run-schema-sync.js +34 -0
- package/scripts/diagnose-cli.js +150 -0
- package/scripts/install-local.js +304 -55
- package/templates/README.md +15 -2
- package/templates/applications/dataplane/application.yaml +52 -2
- package/templates/applications/dataplane/env.template +80 -18
- package/templates/applications/dataplane/rbac.yaml +8 -0
- package/templates/applications/keycloak/application.yaml +9 -1
- package/templates/applications/keycloak/env.template +15 -6
- package/templates/applications/miso-controller/application.yaml +10 -2
- package/templates/applications/miso-controller/env.template +55 -14
- package/templates/applications/miso-controller/rbac.yaml +5 -0
- package/templates/external-system/README.md.hbs +20 -7
- package/templates/external-system/deploy.js.hbs +5 -5
- package/templates/external-system/env.template.hbs +22 -0
- package/templates/external-system/external-datasource.yaml.hbs +197 -118
- package/templates/infra/compose.yaml.hbs +20 -4
- package/templates/python/docker-compose.hbs +16 -0
- package/templates/typescript/docker-compose.hbs +16 -0
- package/integration/hubspot/README.md +0 -102
- package/integration/hubspot/env.template +0 -4
- package/integration/hubspot/hubspot-datasource-company.json +0 -541
- package/integration/hubspot/hubspot-datasource-contact.json +0 -639
- package/integration/hubspot/hubspot-datasource-deal.json +0 -588
- package/integration/hubspot/hubspot-datasource-users.json +0 -116
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
- package/lib/api/external-test.api.js +0 -111
- package/lib/schema/env-config.yaml +0 -43
- /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
|
@@ -14,29 +14,23 @@ const path = require('path');
|
|
|
14
14
|
const yaml = require('js-yaml');
|
|
15
15
|
const logger = require('./logger');
|
|
16
16
|
const pathsUtil = require('./paths');
|
|
17
|
+
const { ensureSecureFilePermissions } = require('./secure-file-permissions');
|
|
17
18
|
const { getContainerPort } = require('./port-resolver');
|
|
18
19
|
const { loadYamlTolerantOfDuplicateKeys } = require('./secrets-generator');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* Parses secrets YAML content with fallback for duplicate keys
|
|
22
|
+
* Parses secrets YAML content with fallback for duplicate keys and broken concat files
|
|
23
|
+
* (e.g. empty map `{}` then appended key lines — invalid without `---`).
|
|
22
24
|
* @param {string} content - Raw file content
|
|
23
25
|
* @returns {Object} Parsed secrets object
|
|
24
26
|
*/
|
|
25
27
|
function parseSecretsContent(content) {
|
|
26
|
-
|
|
27
|
-
return yaml.load(content);
|
|
28
|
-
} catch (yamlErr) {
|
|
29
|
-
const msg = yamlErr.message || '';
|
|
30
|
-
if (msg.includes('duplicate') || msg.includes('duplicated mapping')) {
|
|
31
|
-
return loadYamlTolerantOfDuplicateKeys(content);
|
|
32
|
-
}
|
|
33
|
-
throw yamlErr;
|
|
34
|
-
}
|
|
28
|
+
return loadYamlTolerantOfDuplicateKeys(content);
|
|
35
29
|
}
|
|
36
30
|
|
|
37
31
|
/**
|
|
38
32
|
* Loads secrets from file with cascading lookup support
|
|
39
|
-
* First checks
|
|
33
|
+
* First checks the primary user secrets file (see getPrimaryUserSecretsLocalPath in paths.js), then aifabrix-secrets from config.yaml
|
|
40
34
|
*
|
|
41
35
|
* @async
|
|
42
36
|
* @function loadSecretsFromFile
|
|
@@ -50,7 +44,7 @@ async function loadSecretsFromFile(filePath) {
|
|
|
50
44
|
|
|
51
45
|
try {
|
|
52
46
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
53
|
-
const secrets =
|
|
47
|
+
const secrets = parseSecretsContent(content);
|
|
54
48
|
|
|
55
49
|
if (!secrets || typeof secrets !== 'object') {
|
|
56
50
|
return {};
|
|
@@ -64,20 +58,19 @@ async function loadSecretsFromFile(filePath) {
|
|
|
64
58
|
}
|
|
65
59
|
|
|
66
60
|
/**
|
|
67
|
-
* Loads user secrets from
|
|
61
|
+
* Loads user secrets from getPrimaryUserSecretsLocalPath() (same dir as config.yaml resolution).
|
|
68
62
|
* Used as the master source when merging with project/public secrets: user values win,
|
|
69
63
|
* missing keys are filled from the public (aifabrix-secrets) file.
|
|
70
|
-
* Does not use config.yaml aifabrix-home so the merge always sees the actual user file.
|
|
71
64
|
*
|
|
72
65
|
* @function loadPrimaryUserSecrets
|
|
73
66
|
* @returns {Object} Loaded secrets object or empty object
|
|
74
67
|
*/
|
|
75
68
|
function loadPrimaryUserSecrets() {
|
|
76
|
-
const
|
|
77
|
-
const userSecretsPath = path.join(primaryDir, 'secrets.local.yaml');
|
|
69
|
+
const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
|
|
78
70
|
if (!fs.existsSync(userSecretsPath)) {
|
|
79
71
|
return {};
|
|
80
72
|
}
|
|
73
|
+
ensureSecureFilePermissions(userSecretsPath);
|
|
81
74
|
|
|
82
75
|
try {
|
|
83
76
|
const content = fs.readFileSync(userSecretsPath, 'utf8');
|
|
@@ -96,31 +89,12 @@ function loadPrimaryUserSecrets() {
|
|
|
96
89
|
}
|
|
97
90
|
|
|
98
91
|
/**
|
|
99
|
-
*
|
|
100
|
-
* Uses paths.getAifabrixHome() to respect config.yaml aifabrix-home override
|
|
92
|
+
* Same as {@link loadPrimaryUserSecrets} (legacy name; kept for callers).
|
|
101
93
|
* @function loadUserSecrets
|
|
102
94
|
* @returns {Object} Loaded secrets object or empty object
|
|
103
95
|
*/
|
|
104
96
|
function loadUserSecrets() {
|
|
105
|
-
|
|
106
|
-
if (!fs.existsSync(userSecretsPath)) {
|
|
107
|
-
return {};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const content = fs.readFileSync(userSecretsPath, 'utf8');
|
|
112
|
-
const secrets = parseSecretsContent(content);
|
|
113
|
-
if (!secrets || typeof secrets !== 'object') {
|
|
114
|
-
throw new Error(`Invalid secrets file format: ${userSecretsPath}`);
|
|
115
|
-
}
|
|
116
|
-
return secrets;
|
|
117
|
-
} catch (error) {
|
|
118
|
-
if (error.message.includes('Invalid secrets file format')) {
|
|
119
|
-
throw error;
|
|
120
|
-
}
|
|
121
|
-
logger.warn(`Warning: Could not read secrets file ${userSecretsPath}: ${error.message}`);
|
|
122
|
-
return {};
|
|
123
|
-
}
|
|
97
|
+
return loadPrimaryUserSecrets();
|
|
124
98
|
}
|
|
125
99
|
|
|
126
100
|
/**
|
|
@@ -134,6 +108,7 @@ function loadDefaultSecrets() {
|
|
|
134
108
|
if (!fs.existsSync(defaultPath)) {
|
|
135
109
|
return {};
|
|
136
110
|
}
|
|
111
|
+
ensureSecureFilePermissions(defaultPath);
|
|
137
112
|
|
|
138
113
|
try {
|
|
139
114
|
const content = fs.readFileSync(defaultPath, 'utf8');
|
|
@@ -151,6 +126,28 @@ function loadDefaultSecrets() {
|
|
|
151
126
|
}
|
|
152
127
|
}
|
|
153
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Creates the primary user secrets file if missing (empty map) for first-run installs.
|
|
131
|
+
* Uses the same directory as {@link loadPrimaryUserSecrets} (config dir / ~/.aifabrix).
|
|
132
|
+
*
|
|
133
|
+
* @function ensurePrimaryUserSecretsFileExists
|
|
134
|
+
*/
|
|
135
|
+
function ensurePrimaryUserSecretsFileExists() {
|
|
136
|
+
const userSecretsPath = pathsUtil.getPrimaryUserSecretsLocalPath();
|
|
137
|
+
const primaryDir = path.dirname(userSecretsPath);
|
|
138
|
+
if (fs.existsSync(userSecretsPath)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!fs.existsSync(primaryDir)) {
|
|
142
|
+
fs.mkdirSync(primaryDir, { recursive: true, mode: 0o700 });
|
|
143
|
+
}
|
|
144
|
+
const header =
|
|
145
|
+
'# Local secrets for AI Fabrix CLI (kv:// references in env.template resolve here)\n' +
|
|
146
|
+
'# Keys are appended by `aifabrix resolve` / ensure — do not prefix with JSON `{}`.\n';
|
|
147
|
+
fs.writeFileSync(userSecretsPath, header, { mode: 0o600 });
|
|
148
|
+
ensureSecureFilePermissions(userSecretsPath);
|
|
149
|
+
}
|
|
150
|
+
|
|
154
151
|
/**
|
|
155
152
|
* Builds a map of hostname to service name from environment config
|
|
156
153
|
* @function buildHostnameToServiceMap
|
|
@@ -210,6 +207,7 @@ module.exports = {
|
|
|
210
207
|
loadPrimaryUserSecrets,
|
|
211
208
|
loadUserSecrets,
|
|
212
209
|
loadDefaultSecrets,
|
|
210
|
+
ensurePrimaryUserSecretsFileExists,
|
|
213
211
|
buildHostnameToServiceMap,
|
|
214
212
|
resolveUrlPort
|
|
215
213
|
};
|
|
@@ -63,7 +63,9 @@ function validateSecretsFile(filePath, options = {}) {
|
|
|
63
63
|
if (!filePath || typeof filePath !== 'string') {
|
|
64
64
|
return { valid: false, errors: ['Path is required'], path: '' };
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
// Always normalize to an OS-resolved absolute path. This makes behavior consistent
|
|
67
|
+
// on Windows when callers pass POSIX-style absolute paths like "/nonexistent/foo".
|
|
68
|
+
const resolvedPath = path.resolve(filePath);
|
|
67
69
|
if (!fs.existsSync(resolvedPath)) {
|
|
68
70
|
return { valid: false, errors: [`File not found: ${resolvedPath}`], path: resolvedPath };
|
|
69
71
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML merge utilities that preserve comments/blank lines for flat secrets files.
|
|
3
|
+
*
|
|
4
|
+
* Intended for `secrets.local.yaml` which is typically a simple `key: value` mapping
|
|
5
|
+
* with user-owned comments. When the file isn't flat (complex YAML), callers should
|
|
6
|
+
* fall back to a full YAML rewrite.
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview Comment-preserving YAML merge helpers
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const keyLineRe = /^(\s*)([^#:\n]+):\s*(.*)$/;
|
|
12
|
+
|
|
13
|
+
function isCommentOrBlank(line) {
|
|
14
|
+
const t = line.trim();
|
|
15
|
+
return !t || t.startsWith('#');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseFlatKeyLine(line) {
|
|
19
|
+
const m = line.match(keyLineRe);
|
|
20
|
+
if (!m) return null;
|
|
21
|
+
const indent = m[1] || '';
|
|
22
|
+
const key = (m[2] || '').trim();
|
|
23
|
+
const rest = m[3] || '';
|
|
24
|
+
if (!key) return null;
|
|
25
|
+
return { indent, key, rest };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function splitInlineComment(rest) {
|
|
29
|
+
const idx = rest.indexOf(' #');
|
|
30
|
+
return { inlineComment: idx >= 0 ? rest.slice(idx) : '' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatYamlScalarForFlatLine(yaml, dumpOpts, value) {
|
|
34
|
+
const dumped = yaml.dump({ _k: value }, dumpOpts);
|
|
35
|
+
const lines = dumped.split(/\r?\n/).filter(Boolean);
|
|
36
|
+
if (lines.length !== 1) return null;
|
|
37
|
+
const idx = lines[0].indexOf(':');
|
|
38
|
+
if (idx < 0) return null;
|
|
39
|
+
return lines[0].slice(idx + 1).trimStart();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function appendMissingKeys(out, seen, desiredSecrets, yaml, dumpOpts) {
|
|
43
|
+
for (const key of Object.keys(desiredSecrets)) {
|
|
44
|
+
if (seen.has(key)) continue;
|
|
45
|
+
const scalar = formatYamlScalarForFlatLine(yaml, dumpOpts, desiredSecrets[key]);
|
|
46
|
+
if (scalar === null) return null;
|
|
47
|
+
out.push(`${key}: ${scalar}`.trimEnd());
|
|
48
|
+
seen.add(key);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function mergeExistingLinesPreservingComments(lines, desiredSecrets, yaml, dumpOpts) {
|
|
54
|
+
const out = [];
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
const encountered = new Set();
|
|
57
|
+
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
if (isCommentOrBlank(line)) {
|
|
60
|
+
out.push(line);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const parsed = parseFlatKeyLine(line);
|
|
64
|
+
if (!parsed) return null;
|
|
65
|
+
const { indent, key, rest } = parsed;
|
|
66
|
+
|
|
67
|
+
if (encountered.has(key)) continue;
|
|
68
|
+
encountered.add(key);
|
|
69
|
+
|
|
70
|
+
if (!Object.prototype.hasOwnProperty.call(desiredSecrets, key)) continue;
|
|
71
|
+
|
|
72
|
+
const { inlineComment } = splitInlineComment(rest);
|
|
73
|
+
const scalar = formatYamlScalarForFlatLine(yaml, dumpOpts, desiredSecrets[key]);
|
|
74
|
+
if (scalar === null) return null;
|
|
75
|
+
seen.add(key);
|
|
76
|
+
out.push(`${indent}${key}: ${scalar}${inlineComment}`.trimEnd());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { out, seen };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Merge a desired flat secrets object into existing file content while preserving comments/blank lines.
|
|
84
|
+
* Supports deletes: keys present in existing content but absent in desired are removed.
|
|
85
|
+
*
|
|
86
|
+
* Returns null when content cannot be treated as a flat key-value file.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} existingContent
|
|
89
|
+
* @param {Record<string, any>} desiredSecrets
|
|
90
|
+
* @param {{ yaml: any, dumpOpts: any }} yamlCtx
|
|
91
|
+
* @returns {string|null}
|
|
92
|
+
*/
|
|
93
|
+
function mergeFlatSecretsYamlPreservingComments(existingContent, desiredSecrets, yamlCtx) {
|
|
94
|
+
if (typeof existingContent !== 'string') return null;
|
|
95
|
+
if (!desiredSecrets || typeof desiredSecrets !== 'object') return null;
|
|
96
|
+
if (!yamlCtx || !yamlCtx.yaml) return null;
|
|
97
|
+
|
|
98
|
+
const { yaml, dumpOpts } = yamlCtx;
|
|
99
|
+
const lines = existingContent.split(/\r?\n/);
|
|
100
|
+
const merged = mergeExistingLinesPreservingComments(lines, desiredSecrets, yaml, dumpOpts);
|
|
101
|
+
if (!merged) return null;
|
|
102
|
+
|
|
103
|
+
const appended = appendMissingKeys(merged.out, merged.seen, desiredSecrets, yaml, dumpOpts);
|
|
104
|
+
if (appended === null) return null;
|
|
105
|
+
return appended.join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { mergeFlatSecretsYamlPreservingComments };
|
|
109
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure file permissions for secrets and config (ISO 27001).
|
|
3
|
+
* Ensures sensitive files are restricted to owner-only (0o600) when read or written.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Enforce restrictive permissions on secrets and config files
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
/** Mode for secrets and admin files: owner read/write only (no group/other). */
|
|
16
|
+
const SECRET_FILE_MODE = 0o600;
|
|
17
|
+
|
|
18
|
+
/** Mode for config file (may contain tokens): owner read/write only. */
|
|
19
|
+
const CONFIG_FILE_MODE = 0o600;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Ensures a file has restrictive permissions (0o600) when it exists.
|
|
23
|
+
* If the file has group or other read/write/execute bits set, chmods to owner-only.
|
|
24
|
+
* Safe to call on every read path; no-op when file is missing or already 0o600.
|
|
25
|
+
* On Windows, chmod restricts write access; mode bits are not fully supported.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} filePath - Absolute or relative path to the file
|
|
28
|
+
* @param {number} [mode=0o600] - Desired mode (default SECRET_FILE_MODE)
|
|
29
|
+
* @returns {boolean} True if file existed and permissions were (or are now) secure
|
|
30
|
+
*/
|
|
31
|
+
function ensureSecureFilePermissions(filePath, mode = SECRET_FILE_MODE) {
|
|
32
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
|
|
36
|
+
try {
|
|
37
|
+
if (!fs.existsSync(resolved)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const stat = fs.statSync(resolved);
|
|
41
|
+
if (!stat.isFile()) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const currentMode = stat.mode & 0o777;
|
|
45
|
+
if ((currentMode & 0o77) === 0) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
fs.chmodSync(resolved, mode);
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Ensures a directory has restrictive permissions (0o700) when it exists.
|
|
57
|
+
* No-op when directory is missing or already 0o700 (no group/other).
|
|
58
|
+
*
|
|
59
|
+
* @param {string} dirPath - Absolute or relative path to the directory
|
|
60
|
+
* @returns {boolean} True if directory existed and permissions were (or are now) secure
|
|
61
|
+
*/
|
|
62
|
+
function ensureSecureDirPermissions(dirPath) {
|
|
63
|
+
if (!dirPath || typeof dirPath !== 'string') {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const resolved = path.isAbsolute(dirPath) ? dirPath : path.resolve(dirPath);
|
|
67
|
+
try {
|
|
68
|
+
if (!fs.existsSync(resolved)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const stat = fs.statSync(resolved);
|
|
72
|
+
if (!stat.isDirectory()) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const currentMode = stat.mode & 0o777;
|
|
76
|
+
if ((currentMode & 0o77) === 0) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
fs.chmodSync(resolved, 0o700);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
ensureSecureFilePermissions,
|
|
88
|
+
ensureSecureDirPermissions,
|
|
89
|
+
SECRET_FILE_MODE,
|
|
90
|
+
CONFIG_FILE_MODE
|
|
91
|
+
};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
|
+
const { nodeFs } = require('../internal/node-fs');
|
|
8
9
|
const path = require('path');
|
|
9
10
|
const os = require('os');
|
|
10
11
|
const { execSync } = require('child_process');
|
|
@@ -40,9 +41,10 @@ function getDefaultEd25519PrivateKeyPath() {
|
|
|
40
41
|
* @returns {string} Resolved SSH dir path
|
|
41
42
|
*/
|
|
42
43
|
function ensureSshDir(sshDir) {
|
|
44
|
+
const syncFs = nodeFs();
|
|
43
45
|
const dir = sshDir || getDefaultSshDir();
|
|
44
|
-
if (!
|
|
45
|
-
|
|
46
|
+
if (!syncFs.existsSync(dir)) {
|
|
47
|
+
syncFs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
46
48
|
}
|
|
47
49
|
return dir;
|
|
48
50
|
}
|
|
@@ -35,7 +35,7 @@ async function loadTemplateVariables(templateName) {
|
|
|
35
35
|
} catch (error) {
|
|
36
36
|
// Template application.yaml not found or invalid, continue without it
|
|
37
37
|
if (error.code !== 'ENOENT') {
|
|
38
|
-
logger.warn(chalk.yellow(
|
|
38
|
+
logger.warn(chalk.yellow(`⚠ Warning: Could not load template application.yaml: ${error.message}`));
|
|
39
39
|
}
|
|
40
40
|
return null;
|
|
41
41
|
}
|
|
@@ -125,7 +125,7 @@ async function updateTemplateVariables(appPath, appName, options, config) {
|
|
|
125
125
|
writeConfigFile(variablesPath, variables);
|
|
126
126
|
} catch (error) {
|
|
127
127
|
if (error.message && !error.message.includes('not found')) {
|
|
128
|
-
logger.warn(chalk.yellow(
|
|
128
|
+
logger.warn(chalk.yellow(`⚠ Warning: Could not update application config: ${error.message}`));
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Test log writer - writes debug logs to integration/<
|
|
2
|
+
* Test log writer - writes debug logs to integration/<systemKey>/logs/
|
|
3
3
|
* Sanitization (tokens, secrets) is done by dataplane before responses are returned.
|
|
4
4
|
*
|
|
5
5
|
* @fileoverview Write test request/response logs for debugging
|
|
@@ -29,9 +29,9 @@ function sanitizeForLog(obj, seen = new Set()) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Write test log to integration/<
|
|
32
|
+
* Write test log to integration/<systemKey>/logs/<logType>-<timestamp>.json
|
|
33
33
|
* @async
|
|
34
|
-
* @param {string} appKey -
|
|
34
|
+
* @param {string} appKey - Integration folder name under integration/ (used for path)
|
|
35
35
|
* @param {Object} data - Log data (request, response) - will be sanitized
|
|
36
36
|
* @param {string} [logType] - Log type prefix (default: test-integration)
|
|
37
37
|
* @param {string} [integrationBaseDir] - Base dir for integration (default: cwd/integration)
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
13
|
const yaml = require('js-yaml');
|
|
15
14
|
const config = require('../core/config');
|
|
16
15
|
const logger = require('./logger');
|
|
@@ -21,12 +20,43 @@ const {
|
|
|
21
20
|
} = require('./token-manager-refresh');
|
|
22
21
|
const { warnRefreshFailureOnce, warnRefreshTokenExpiredOnce } = require('./token-manager-messages');
|
|
23
22
|
|
|
23
|
+
/** App key used for dataplane client credentials in secrets.local.yaml */
|
|
24
|
+
const DATAPLANE_APP_KEY = 'dataplane';
|
|
25
|
+
|
|
24
26
|
function getSecretsFilePath() {
|
|
25
|
-
return
|
|
27
|
+
return pathsUtil.getPrimaryUserSecretsLocalPath();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validate that secrets.local.yaml contains dataplane client credentials.
|
|
32
|
+
* If missing, the developer should run: aifabrix app rotate-secret dataplane
|
|
33
|
+
* @param {string} [secretsFilePath] - Path to secrets file; defaults to ~/.aifabrix/secrets.local.yaml
|
|
34
|
+
* @returns {{ valid: boolean, hint?: string }}
|
|
35
|
+
*/
|
|
36
|
+
function validateDataplaneSecrets(secretsFilePath) {
|
|
37
|
+
const filePath = secretsFilePath || getSecretsFilePath();
|
|
38
|
+
const hint = 'Dataplane credentials are missing. Run: aifabrix app rotate-secret dataplane';
|
|
39
|
+
try {
|
|
40
|
+
if (!fs.existsSync(filePath)) {
|
|
41
|
+
return { valid: false, hint };
|
|
42
|
+
}
|
|
43
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
44
|
+
const secrets = yaml.load(content) || {};
|
|
45
|
+
const clientIdKey = `${DATAPLANE_APP_KEY}-client-idKeyVault`;
|
|
46
|
+
const clientSecretKey = `${DATAPLANE_APP_KEY}-client-secretKeyVault`;
|
|
47
|
+
const hasId = secrets[clientIdKey] !== null && secrets[clientIdKey] !== undefined && String(secrets[clientIdKey]).trim() !== '';
|
|
48
|
+
const hasSecret = secrets[clientSecretKey] !== null && secrets[clientSecretKey] !== undefined && String(secrets[clientSecretKey]).trim() !== '';
|
|
49
|
+
if (hasId && hasSecret) {
|
|
50
|
+
return { valid: true };
|
|
51
|
+
}
|
|
52
|
+
return { valid: false, hint };
|
|
53
|
+
} catch {
|
|
54
|
+
return { valid: false, hint };
|
|
55
|
+
}
|
|
26
56
|
}
|
|
27
57
|
|
|
28
58
|
/**
|
|
29
|
-
* Load client credentials from secrets.local.yaml or process.env (e.g. integration/hubspot/.env).
|
|
59
|
+
* Load client credentials from secrets.local.yaml or process.env (e.g. integration/hubspot-test/.env).
|
|
30
60
|
* Reads secrets file using pattern: <app-name>-client-idKeyVault and <app-name>-client-secretKeyVault.
|
|
31
61
|
* If not found, checks process.env.CLIENTID and process.env.CLIENTSECRET (set when .env is loaded).
|
|
32
62
|
* @param {string} appName - Application name
|
|
@@ -60,7 +90,7 @@ async function loadClientCredentials(appName) {
|
|
|
60
90
|
logger.warn(`Failed to load credentials from secrets.local.yaml: ${error.message}`);
|
|
61
91
|
}
|
|
62
92
|
|
|
63
|
-
// Fallback: use CLIENTID/CLIENTSECRET from process.env (e.g. from integration/hubspot/.env)
|
|
93
|
+
// Fallback: use CLIENTID/CLIENTSECRET from process.env (e.g. from integration/hubspot-test/.env)
|
|
64
94
|
const envClientId = process.env.CLIENTID || process.env.CLIENT_ID;
|
|
65
95
|
const envClientSecret = process.env.CLIENTSECRET || process.env.CLIENT_SECRET;
|
|
66
96
|
if (envClientId && envClientSecret) {
|
|
@@ -439,6 +469,7 @@ function requireBearerForDataplanePipeline(authConfig) {
|
|
|
439
469
|
}
|
|
440
470
|
|
|
441
471
|
module.exports = {
|
|
472
|
+
DATAPLANE_APP_KEY,
|
|
442
473
|
getDeviceToken,
|
|
443
474
|
getClientToken,
|
|
444
475
|
isTokenExpired,
|
|
@@ -452,5 +483,6 @@ module.exports = {
|
|
|
452
483
|
getDeploymentAuth,
|
|
453
484
|
getDeviceOnlyAuth,
|
|
454
485
|
extractClientCredentials,
|
|
455
|
-
requireBearerForDataplanePipeline
|
|
486
|
+
requireBearerForDataplanePipeline,
|
|
487
|
+
validateDataplaneSecrets
|
|
456
488
|
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public URL base for url:// expansion when Traefik + frontDoorRouting.host (plan 122 phase 2).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Traefik host template vs remote-server vs localhost+port (no app-specific rules)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { expandFrontDoorHostPlaceholders } = require('./compose-generator');
|
|
12
|
+
const { publishedHostPort, localHostPort } = require('./declarative-url-ports');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Expand frontDoorRouting.host placeholders for url:// (same rules as Traefik labels in compose-generator).
|
|
16
|
+
*
|
|
17
|
+
* @param {string} template
|
|
18
|
+
* @param {Object} opts
|
|
19
|
+
* @param {string|number|null|undefined} opts.developerIdRaw
|
|
20
|
+
* @param {string|null|undefined} opts.remoteServer
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
function expandFrontDoorHostTemplateForUrls(template, opts) {
|
|
24
|
+
const { developerIdRaw, remoteServer } = opts || {};
|
|
25
|
+
return expandFrontDoorHostPlaceholders(template, developerIdRaw, remoteServer);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function hostPortForProfile(profile, listenPort, developerIdNum) {
|
|
29
|
+
return profile === 'docker'
|
|
30
|
+
? publishedHostPort(listenPort, developerIdNum)
|
|
31
|
+
: localHostPort(listenPort, developerIdNum);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Local profile: workstation `+10` applies only to the app being resolved (`currentAppKey`).
|
|
36
|
+
* Cross-app tokens (e.g. `url://keycloak-public` from miso-controller) use `publishedHostPort`
|
|
37
|
+
* (manifest + dev*100, no +10) so sibling services keep compose-published offsets only.
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} opts
|
|
40
|
+
* @param {'docker'|'local'} opts.profile
|
|
41
|
+
* @param {number} opts.listenPort - published/manifest basis for public URLs
|
|
42
|
+
* @param {number} opts.developerIdNum
|
|
43
|
+
* @param {string|undefined} opts.declarativeTargetAppKey - target of the url:// token
|
|
44
|
+
* @param {string|undefined} opts.declarativeCurrentAppKey - app whose env is being generated
|
|
45
|
+
* @returns {number}
|
|
46
|
+
*/
|
|
47
|
+
function resolveHostPortForDeclarativePublic(opts) {
|
|
48
|
+
const {
|
|
49
|
+
profile,
|
|
50
|
+
listenPort,
|
|
51
|
+
developerIdNum,
|
|
52
|
+
declarativeTargetAppKey,
|
|
53
|
+
declarativeCurrentAppKey
|
|
54
|
+
} = opts;
|
|
55
|
+
if (profile !== 'local') {
|
|
56
|
+
return hostPortForProfile(profile, listenPort, developerIdNum);
|
|
57
|
+
}
|
|
58
|
+
const cur = String(declarativeCurrentAppKey || '').trim();
|
|
59
|
+
const tgt = String(declarativeTargetAppKey || '').trim();
|
|
60
|
+
const isCurrentApp = !cur || !tgt || tgt === cur;
|
|
61
|
+
return isCurrentApp ? localHostPort(listenPort, developerIdNum) : publishedHostPort(listenPort, developerIdNum);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Without Traefik, `remote-server` is the dev machine host. Apps bind published host ports, not 443 + path.
|
|
66
|
+
* If the URL already has an explicit port, keep host:port but **scheme follows `infraTlsEnabled`**, not the
|
|
67
|
+
* literal `https://` in `remote-server` when TLS is off (`up-infra` without `--tls`).
|
|
68
|
+
* If `remote-server` omits a port, append the profile-specific published/listen-derived port.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} rawRemote
|
|
71
|
+
* @param {'docker'|'local'} profile
|
|
72
|
+
* @param {number} listenPort
|
|
73
|
+
* @param {number} developerIdNum
|
|
74
|
+
* @param {boolean} infraTlsEnabled
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function remotePublicBaseWithoutTraefik(
|
|
78
|
+
rawRemote,
|
|
79
|
+
profile,
|
|
80
|
+
listenPort,
|
|
81
|
+
developerIdNum,
|
|
82
|
+
infraTlsEnabled,
|
|
83
|
+
declarativePortOpts
|
|
84
|
+
) {
|
|
85
|
+
const raw = String(rawRemote || '').trim().replace(/\/+$/, '');
|
|
86
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : (infraTlsEnabled ? `https://${raw}` : `http://${raw}`);
|
|
87
|
+
const u = new URL(withScheme);
|
|
88
|
+
const scheme = infraTlsEnabled ? 'https' : 'http';
|
|
89
|
+
|
|
90
|
+
if (u.port !== '') {
|
|
91
|
+
return `${scheme}://${u.host}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const hostPort = resolveHostPortForDeclarativePublic({
|
|
95
|
+
profile,
|
|
96
|
+
listenPort,
|
|
97
|
+
developerIdNum,
|
|
98
|
+
declarativeTargetAppKey: declarativePortOpts && declarativePortOpts.declarativeTargetAppKey,
|
|
99
|
+
declarativeCurrentAppKey: declarativePortOpts && declarativePortOpts.declarativeCurrentAppKey
|
|
100
|
+
});
|
|
101
|
+
const defaultPort = scheme === 'https' ? 443 : 80;
|
|
102
|
+
if (Number(hostPort) === defaultPort) {
|
|
103
|
+
return `${scheme}://${u.hostname}`;
|
|
104
|
+
}
|
|
105
|
+
return `${scheme}://${u.hostname}:${hostPort}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {Object} opts - same shape as computePublicUrlBaseString
|
|
110
|
+
* @returns {string|null}
|
|
111
|
+
*/
|
|
112
|
+
function buildTraefikPublicBaseIfApplicable(opts) {
|
|
113
|
+
const { traefik, pathActive, hostTemplate, tls, developerIdRaw, remoteServer, infraTlsEnabled } =
|
|
114
|
+
opts;
|
|
115
|
+
// Plan 124: Traefik host authority only when pathActive (traefik ∧ frontDoorRouting.enabled)
|
|
116
|
+
if (!traefik || !pathActive || !hostTemplate || !String(hostTemplate).trim()) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const expanded = expandFrontDoorHostTemplateForUrls(hostTemplate, {
|
|
120
|
+
developerIdRaw,
|
|
121
|
+
remoteServer
|
|
122
|
+
});
|
|
123
|
+
if (!expanded) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const useHttps = Boolean(infraTlsEnabled) || tls !== false;
|
|
127
|
+
const scheme = useHttps ? 'https' : 'http';
|
|
128
|
+
return `${scheme}://${expanded}`.replace(/\/+$/, '');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Scheme + authority (no path) for public url://* when Traefik, remote, or localhost.
|
|
133
|
+
*
|
|
134
|
+
* @param {Object} opts
|
|
135
|
+
* @param {boolean} [opts.traefik]
|
|
136
|
+
* @param {string|null|undefined} opts.hostTemplate
|
|
137
|
+
* @param {boolean} [opts.tls]
|
|
138
|
+
* @param {string|number|null|undefined} opts.developerIdRaw
|
|
139
|
+
* @param {string|null|undefined} opts.remoteServer
|
|
140
|
+
* @param {'docker'|'local'} opts.profile
|
|
141
|
+
* @param {number} opts.listenPort
|
|
142
|
+
* @param {number} opts.developerIdNum
|
|
143
|
+
* @param {boolean} [opts.infraTlsEnabled] - `tlsEnabled` from ~/.aifabrix/config.yaml (`up-infra --tls`); when true, Traefik front-door public URLs use https even if application.yaml has `frontDoorRouting.tls: false`
|
|
144
|
+
* @param {boolean} [opts.pathActive] - traefik ∧ frontDoorRouting.enabled; required for Traefik host branch (plan 124)
|
|
145
|
+
* @returns {string}
|
|
146
|
+
*/
|
|
147
|
+
function computePublicUrlBaseString(opts) {
|
|
148
|
+
const { remoteServer, profile, listenPort, developerIdNum, infraTlsEnabled } = opts;
|
|
149
|
+
const declarativePortOpts = {
|
|
150
|
+
declarativeTargetAppKey: opts.declarativeTargetAppKey,
|
|
151
|
+
declarativeCurrentAppKey: opts.declarativeCurrentAppKey
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const traefikBase = buildTraefikPublicBaseIfApplicable({
|
|
155
|
+
...opts,
|
|
156
|
+
pathActive: Boolean(opts.pathActive)
|
|
157
|
+
});
|
|
158
|
+
if (traefikBase) {
|
|
159
|
+
return traefikBase;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (remoteServer && String(remoteServer).trim()) {
|
|
163
|
+
return remotePublicBaseWithoutTraefik(
|
|
164
|
+
remoteServer,
|
|
165
|
+
profile,
|
|
166
|
+
listenPort,
|
|
167
|
+
developerIdNum,
|
|
168
|
+
Boolean(infraTlsEnabled),
|
|
169
|
+
declarativePortOpts
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const hostPort = resolveHostPortForDeclarativePublic({
|
|
174
|
+
profile,
|
|
175
|
+
listenPort,
|
|
176
|
+
developerIdNum,
|
|
177
|
+
declarativeTargetAppKey: declarativePortOpts.declarativeTargetAppKey,
|
|
178
|
+
declarativeCurrentAppKey: declarativePortOpts.declarativeCurrentAppKey
|
|
179
|
+
});
|
|
180
|
+
const scheme = infraTlsEnabled ? 'https' : 'http';
|
|
181
|
+
return `${scheme}://localhost:${hostPort}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
expandFrontDoorHostTemplateForUrls,
|
|
186
|
+
computePublicUrlBaseString,
|
|
187
|
+
resolveHostPortForDeclarativePublic
|
|
188
|
+
};
|