@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
|
@@ -22,60 +22,210 @@ const SSL_UNTRUSTED_CODES = [
|
|
|
22
22
|
'CERT_UNTRUSTED',
|
|
23
23
|
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
24
24
|
'UNABLE_TO_GET_ISSUER_CERT',
|
|
25
|
-
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY'
|
|
25
|
+
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
|
|
26
|
+
'CERT_HAS_EXPIRED'
|
|
26
27
|
];
|
|
27
28
|
|
|
29
|
+
const SSL_HOSTNAME_MISMATCH_CODES = ['ERR_TLS_CERT_ALTNAME_INVALID'];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Walks err and nested err.cause (fetch wraps TLS failures).
|
|
33
|
+
* @param {Error|undefined|null} err - Error chain root
|
|
34
|
+
* @param {function(Error): void} fn - Visitor
|
|
35
|
+
* @param {number} [maxDepth=12]
|
|
36
|
+
*/
|
|
37
|
+
function walkErrorCauseChain(err, fn, maxDepth = 12) {
|
|
38
|
+
let e = err;
|
|
39
|
+
let depth = 0;
|
|
40
|
+
while (e && depth < maxDepth) {
|
|
41
|
+
fn(e);
|
|
42
|
+
e = e.cause;
|
|
43
|
+
depth++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
/**
|
|
29
48
|
* Returns true if the error indicates an untrusted/self-signed server certificate.
|
|
30
49
|
* @param {Error} err - Thrown error (e.g. from devApi.getHealth)
|
|
31
50
|
* @returns {boolean}
|
|
32
51
|
*/
|
|
33
52
|
function isSslUntrustedError(err) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
53
|
+
if (!err) return false;
|
|
54
|
+
let untrusted = false;
|
|
55
|
+
walkErrorCauseChain(err, (e) => {
|
|
56
|
+
const code = e && e.code;
|
|
57
|
+
const msg = ((e && e.message) || '').toUpperCase();
|
|
58
|
+
if (SSL_UNTRUSTED_CODES.some(c => code === c || msg.includes(c))) {
|
|
59
|
+
untrusted = true;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return untrusted;
|
|
37
63
|
}
|
|
38
64
|
|
|
39
65
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* @param {
|
|
43
|
-
* @returns {
|
|
66
|
+
* True when the server cert is trusted enough to verify but the hostname does not match (wrong SAN).
|
|
67
|
+
* Installing the dev CA does not fix this.
|
|
68
|
+
* @param {Error} err - Thrown error
|
|
69
|
+
* @returns {boolean}
|
|
44
70
|
*/
|
|
45
|
-
function
|
|
71
|
+
function isSslHostnameMismatchError(err) {
|
|
72
|
+
if (!err) return false;
|
|
73
|
+
let mismatch = false;
|
|
74
|
+
walkErrorCauseChain(err, (e) => {
|
|
75
|
+
const code = e && e.code;
|
|
76
|
+
if (SSL_HOSTNAME_MISMATCH_CODES.includes(code)) {
|
|
77
|
+
mismatch = true;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return mismatch;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const MAX_INSTALL_CA_REDIRECTS = 5;
|
|
84
|
+
const PEM_CERT_BLOCK_RE = /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/;
|
|
85
|
+
const JSON_CA_KEYS = [
|
|
86
|
+
'caCertificate', 'ca', 'certificate', 'pem', 'rootCa', 'rootCertificate', 'caPem', 'data'
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* One-line preview of a response body for errors (no PEM secrets expected in typical error HTML).
|
|
91
|
+
* @param {string} body
|
|
92
|
+
* @param {number} maxLen
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
function truncateBodyForError(body, maxLen = 180) {
|
|
96
|
+
const oneLine = String(body || '').replace(/\s+/g, ' ').trim();
|
|
97
|
+
if (oneLine.length <= maxLen) return oneLine || '(empty)';
|
|
98
|
+
return `${oneLine.slice(0, maxLen)}…`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {Object} j - Parsed JSON object
|
|
103
|
+
* @returns {string|null} PEM or null
|
|
104
|
+
*/
|
|
105
|
+
function pemFromJsonObject(j) {
|
|
106
|
+
if (!j || typeof j !== 'object' || Array.isArray(j)) return null;
|
|
107
|
+
for (const k of JSON_CA_KEYS) {
|
|
108
|
+
if (typeof j[k] !== 'string') continue;
|
|
109
|
+
const m = j[k].match(PEM_CERT_BLOCK_RE);
|
|
110
|
+
if (m) return m[0].trim();
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Extract first PEM certificate from install-ca response: raw PEM, JSON field, or HTML/text wrapper.
|
|
117
|
+
* @param {string} raw - Response body as UTF-8
|
|
118
|
+
* @returns {string|null} PEM text or null
|
|
119
|
+
*/
|
|
120
|
+
function extractCaPemFromBody(raw) {
|
|
121
|
+
if (!raw || typeof raw !== 'string') return null;
|
|
122
|
+
const s = raw.trim();
|
|
123
|
+
try {
|
|
124
|
+
const fromJson = pemFromJsonObject(JSON.parse(s));
|
|
125
|
+
if (fromJson) return fromJson;
|
|
126
|
+
} catch {
|
|
127
|
+
/* not JSON */
|
|
128
|
+
}
|
|
129
|
+
const block = s.match(PEM_CERT_BLOCK_RE);
|
|
130
|
+
return block ? block[0].trim() : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolve install-ca response body to PEM buffer or reject with context.
|
|
135
|
+
* @param {import('http').IncomingMessage} res
|
|
136
|
+
* @param {string} absoluteUrl
|
|
137
|
+
* @param {Buffer[]} chunks
|
|
138
|
+
* @param {function(Buffer):void} resolve
|
|
139
|
+
* @param {function(Error):void} reject
|
|
140
|
+
*/
|
|
141
|
+
function finalizeInstallCaResponse(res, absoluteUrl, chunks, resolve, reject) {
|
|
142
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
143
|
+
const status = res.statusCode || 0;
|
|
144
|
+
if (status < 200 || status >= 300) {
|
|
145
|
+
reject(
|
|
146
|
+
new Error(
|
|
147
|
+
`install-ca returned HTTP ${status}. ${truncateBodyForError(body)} — open ${absoluteUrl} in a browser or ask your admin to serve PEM at /install-ca.`
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const pem = extractCaPemFromBody(body);
|
|
153
|
+
if (!pem) {
|
|
154
|
+
const hint = body.trim().startsWith('<') ? 'Received HTML (SPA or error page). ' : '';
|
|
155
|
+
reject(
|
|
156
|
+
new Error(
|
|
157
|
+
`${hint}Invalid CA response: expected PEM certificate. ${truncateBodyForError(body)} — try: ${absoluteUrl}`
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
resolve(Buffer.from(`${pem}\n`, 'utf8'));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* GET absolute install-ca URL with insecure TLS; follow redirects (resolve relative Location).
|
|
167
|
+
* @param {string} absoluteUrl - Full https URL
|
|
168
|
+
* @param {number} redirectCount - Redirect depth
|
|
169
|
+
* @returns {Promise<Buffer>} CA PEM
|
|
170
|
+
*/
|
|
171
|
+
function fetchInstallCaAbsolute(absoluteUrl, redirectCount) {
|
|
46
172
|
return new Promise((resolve, reject) => {
|
|
47
|
-
|
|
48
|
-
|
|
173
|
+
if (redirectCount > MAX_INSTALL_CA_REDIRECTS) {
|
|
174
|
+
reject(new Error(`install-ca: too many redirects (max ${MAX_INSTALL_CA_REDIRECTS})`));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const urlObj = new URL(absoluteUrl);
|
|
49
178
|
if (urlObj.protocol !== 'https:') {
|
|
50
179
|
reject(new Error('install-ca requires https URL'));
|
|
51
180
|
return;
|
|
52
181
|
}
|
|
53
182
|
const agent = new https.Agent({ rejectUnauthorized: false });
|
|
54
183
|
const req = https.get(
|
|
55
|
-
|
|
184
|
+
absoluteUrl,
|
|
56
185
|
{ agent },
|
|
57
186
|
(res) => {
|
|
58
187
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
59
|
-
|
|
60
|
-
|
|
188
|
+
res.destroy();
|
|
189
|
+
let nextUrl;
|
|
190
|
+
try {
|
|
191
|
+
nextUrl = new URL(res.headers.location, absoluteUrl).href;
|
|
192
|
+
} catch {
|
|
193
|
+
reject(new Error(`install-ca: invalid redirect Location: ${res.headers.location}`));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
fetchInstallCaAbsolute(nextUrl, redirectCount + 1).then(resolve).catch(reject);
|
|
61
197
|
return;
|
|
62
198
|
}
|
|
199
|
+
|
|
63
200
|
const chunks = [];
|
|
64
201
|
res.on('data', c => chunks.push(c));
|
|
65
|
-
res.on('end', () =>
|
|
66
|
-
const body = Buffer.concat(chunks).toString('utf8');
|
|
67
|
-
if (!body || !body.includes('-----BEGIN CERTIFICATE-----')) {
|
|
68
|
-
reject(new Error('Invalid CA response: expected PEM certificate'));
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
resolve(Buffer.from(body, 'utf8'));
|
|
72
|
-
});
|
|
202
|
+
res.on('end', () => finalizeInstallCaResponse(res, absoluteUrl, chunks, resolve, reject));
|
|
73
203
|
}
|
|
74
204
|
);
|
|
75
205
|
req.on('error', reject);
|
|
76
206
|
});
|
|
77
207
|
}
|
|
78
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Fetch CA PEM from Builder Server via GET {baseUrl}/install-ca.
|
|
211
|
+
* Uses rejectUnauthorized: false only for this endpoint (dev setup).
|
|
212
|
+
* @param {string} baseUrl - Builder Server base URL (no trailing slash)
|
|
213
|
+
* @returns {Promise<Buffer>} CA certificate PEM
|
|
214
|
+
*/
|
|
215
|
+
function fetchInstallCa(baseUrl) {
|
|
216
|
+
const url = `${baseUrl.replace(/\/+$/, '')}/install-ca`;
|
|
217
|
+
let urlObj;
|
|
218
|
+
try {
|
|
219
|
+
urlObj = new URL(url);
|
|
220
|
+
} catch {
|
|
221
|
+
return Promise.reject(new Error('install-ca: invalid server URL'));
|
|
222
|
+
}
|
|
223
|
+
if (urlObj.protocol !== 'https:') {
|
|
224
|
+
return Promise.reject(new Error('install-ca requires https URL'));
|
|
225
|
+
}
|
|
226
|
+
return fetchInstallCaAbsolute(urlObj.href, 0);
|
|
227
|
+
}
|
|
228
|
+
|
|
79
229
|
/**
|
|
80
230
|
* Install CA PEM into OS trust store (platform-specific).
|
|
81
231
|
* @param {Buffer|string} caPem - CA certificate PEM
|
|
@@ -131,9 +281,21 @@ function promptInstallCa() {
|
|
|
131
281
|
});
|
|
132
282
|
}
|
|
133
283
|
|
|
284
|
+
/**
|
|
285
|
+
* True when installCaPlatform failed because Linux needs root to update the system CA store.
|
|
286
|
+
* @param {unknown} err - Caught error
|
|
287
|
+
* @returns {boolean}
|
|
288
|
+
*/
|
|
289
|
+
function isLinuxCaSudoRequiredError(err) {
|
|
290
|
+
const msg = err && typeof err.message === 'string' ? err.message : '';
|
|
291
|
+
return /Linux CA install requires sudo/i.test(msg);
|
|
292
|
+
}
|
|
293
|
+
|
|
134
294
|
module.exports = {
|
|
135
295
|
isSslUntrustedError,
|
|
296
|
+
isSslHostnameMismatchError,
|
|
136
297
|
fetchInstallCa,
|
|
137
298
|
installCaPlatform,
|
|
138
|
-
promptInstallCa
|
|
299
|
+
promptInstallCa,
|
|
300
|
+
isLinuxCaSudoRequiredError
|
|
139
301
|
};
|
|
@@ -6,9 +6,163 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const {
|
|
9
|
+
const { execFileSync } = require('child_process');
|
|
10
10
|
const os = require('os');
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} dir
|
|
14
|
+
* @returns {string|null}
|
|
15
|
+
*/
|
|
16
|
+
function tryOpenSSLExe(dir) {
|
|
17
|
+
if (!dir) return null;
|
|
18
|
+
const trimmed = dir.trim();
|
|
19
|
+
if (!trimmed) return null;
|
|
20
|
+
const exe = path.join(trimmed, 'openssl.exe');
|
|
21
|
+
return fs.existsSync(exe) ? exe : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Git for Windows often adds ...\\Git\\cmd to PATH but not ...\\Git\\usr\\bin (so `where openssl` fails).
|
|
26
|
+
* @param {string} pathEnv
|
|
27
|
+
* @returns {string|null}
|
|
28
|
+
*/
|
|
29
|
+
function findOpenSSLViaGitCmdOnPath(pathEnv) {
|
|
30
|
+
if (!pathEnv || typeof pathEnv !== 'string') return null;
|
|
31
|
+
const needle = `${path.sep}git${path.sep}cmd`.toLowerCase();
|
|
32
|
+
for (const segment of pathEnv.split(path.delimiter)) {
|
|
33
|
+
const normalized = path.normalize(segment.trim());
|
|
34
|
+
if (!normalized) continue;
|
|
35
|
+
const lower = normalized.toLowerCase();
|
|
36
|
+
const cmdIdx = lower.lastIndexOf(needle);
|
|
37
|
+
if (cmdIdx === -1) continue;
|
|
38
|
+
const gitRoot = normalized.slice(0, cmdIdx + `${path.sep}git`.length);
|
|
39
|
+
const fromUsrBin = tryOpenSSLExe(path.join(gitRoot, 'usr', 'bin'));
|
|
40
|
+
if (fromUsrBin) return fromUsrBin;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* First openssl.exe found in PATH directory entries.
|
|
47
|
+
* @param {string} pathEnv
|
|
48
|
+
* @returns {string|null}
|
|
49
|
+
*/
|
|
50
|
+
function findOpenSSLInPathDirs(pathEnv) {
|
|
51
|
+
if (!pathEnv || typeof pathEnv !== 'string') return null;
|
|
52
|
+
for (const segment of pathEnv.split(path.delimiter)) {
|
|
53
|
+
const found = tryOpenSSLExe(segment);
|
|
54
|
+
if (found) return found;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Windows: Git\\cmd on PATH without usr\\bin, per-user Git, standard install dirs.
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
function resolveOpenSSLExecutableWin32() {
|
|
64
|
+
const pf = process.env.ProgramFiles || 'C:\\Program Files';
|
|
65
|
+
const pf86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
|
|
66
|
+
const pf64 = process.env.ProgramW6432 || pf;
|
|
67
|
+
const localAppData = process.env.LOCALAPPDATA || '';
|
|
68
|
+
const pathEnv = process.env.Path || process.env.PATH || '';
|
|
69
|
+
|
|
70
|
+
const fromGitPath = findOpenSSLViaGitCmdOnPath(pathEnv);
|
|
71
|
+
if (fromGitPath) return fromGitPath;
|
|
72
|
+
|
|
73
|
+
const fromPathDirs = findOpenSSLInPathDirs(pathEnv);
|
|
74
|
+
if (fromPathDirs) return fromPathDirs;
|
|
75
|
+
|
|
76
|
+
const candidates = [
|
|
77
|
+
path.join(pf64, 'Git', 'usr', 'bin', 'openssl.exe'),
|
|
78
|
+
path.join(pf, 'Git', 'usr', 'bin', 'openssl.exe'),
|
|
79
|
+
path.join(pf86, 'Git', 'usr', 'bin', 'openssl.exe'),
|
|
80
|
+
localAppData ? path.join(localAppData, 'Programs', 'Git', 'usr', 'bin', 'openssl.exe') : '',
|
|
81
|
+
path.join(pf64, 'OpenSSL-Win64', 'bin', 'openssl.exe'),
|
|
82
|
+
path.join(pf, 'OpenSSL-Win64', 'bin', 'openssl.exe'),
|
|
83
|
+
path.join(pf64, 'OpenSSL', 'bin', 'openssl.exe'),
|
|
84
|
+
path.join(pf, 'OpenSSL', 'bin', 'openssl.exe')
|
|
85
|
+
];
|
|
86
|
+
for (const c of candidates) {
|
|
87
|
+
if (c && fs.existsSync(c)) return c;
|
|
88
|
+
}
|
|
89
|
+
return 'openssl.exe';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Executable used for OpenSSL (PATH name or absolute path). Admin Windows shells often
|
|
94
|
+
* omit Git usr\\bin; probe standard install locations when plain "openssl" is unavailable.
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
function resolveOpenSSLExecutable() {
|
|
98
|
+
const fromEnv = process.env.AIFABRIX_OPENSSL;
|
|
99
|
+
if (fromEnv && typeof fromEnv === 'string' && fs.existsSync(fromEnv.trim())) {
|
|
100
|
+
return path.normalize(fromEnv.trim());
|
|
101
|
+
}
|
|
102
|
+
if (process.platform === 'win32') {
|
|
103
|
+
return resolveOpenSSLExecutableWin32();
|
|
104
|
+
}
|
|
105
|
+
return 'openssl';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Run OpenSSL with argv (no shell); works when openssl is only on disk, not on PATH.
|
|
110
|
+
* @param {string[]} args
|
|
111
|
+
* @param {Object} [opts] - Extra options for execFileSync (encoding utf8 applied by default)
|
|
112
|
+
* @returns {string|Buffer}
|
|
113
|
+
*/
|
|
114
|
+
function runOpenSSL(args, opts) {
|
|
115
|
+
const exe = resolveOpenSSLExecutable();
|
|
116
|
+
return execFileSync(exe, args, {
|
|
117
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
118
|
+
encoding: 'utf8',
|
|
119
|
+
...opts
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* OpenSSL argv for CSR generation (CN is typically dev-01, dev-02, …).
|
|
125
|
+
* @param {string} cn - Distinguished name CN value
|
|
126
|
+
* @param {string} keyPath - Output key path
|
|
127
|
+
* @param {string} csrPath - Output CSR path
|
|
128
|
+
* @returns {string[]}
|
|
129
|
+
*/
|
|
130
|
+
function opensslCsrArgs(cn, keyPath, csrPath) {
|
|
131
|
+
return [
|
|
132
|
+
'req',
|
|
133
|
+
'-new',
|
|
134
|
+
'-newkey',
|
|
135
|
+
'rsa:2048',
|
|
136
|
+
'-keyout',
|
|
137
|
+
keyPath,
|
|
138
|
+
'-nodes',
|
|
139
|
+
'-subj',
|
|
140
|
+
`/CN=${cn}`,
|
|
141
|
+
'-out',
|
|
142
|
+
csrPath
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Map OpenSSL spawn/read failures to a clear Error for CSR generation.
|
|
148
|
+
* @param {Error & { code?: string }} err
|
|
149
|
+
* @returns {Error}
|
|
150
|
+
*/
|
|
151
|
+
function csrGenerationError(err) {
|
|
152
|
+
const code = err && err.code;
|
|
153
|
+
const msg = err && err.message ? String(err.message) : '';
|
|
154
|
+
if (code === 'ENOENT') {
|
|
155
|
+
return new Error(
|
|
156
|
+
'OpenSSL is required for certificate generation. Install OpenSSL or Git for Windows, add Git usr\\bin or openssl to PATH, or set AIFABRIX_OPENSSL to the full path of openssl.exe.'
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const stderr = err && err.stderr ? String(err.stderr).trim() : '';
|
|
160
|
+
if (typeof err.status === 'number' && err.status !== 0 && stderr) {
|
|
161
|
+
return new Error(`OpenSSL failed: ${stderr}`);
|
|
162
|
+
}
|
|
163
|
+
return new Error(`CSR generation failed: ${msg}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
12
166
|
/**
|
|
13
167
|
* Generate a key pair and CSR for developer certificate. Uses OpenSSL when available.
|
|
14
168
|
* CN in CSR is set to dev-<developerId> per Builder Server convention.
|
|
@@ -26,20 +180,14 @@ function generateCSR(developerId) {
|
|
|
26
180
|
const keyPath = path.join(tmpDir, 'key.pem');
|
|
27
181
|
const csrPath = path.join(tmpDir, 'csr.pem');
|
|
28
182
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
183
|
+
try {
|
|
184
|
+
runOpenSSL(opensslCsrArgs(cn, keyPath, csrPath));
|
|
185
|
+
} catch (sslErr) {
|
|
186
|
+
throw csrGenerationError(sslErr);
|
|
187
|
+
}
|
|
33
188
|
const keyPem = fs.readFileSync(keyPath, 'utf8');
|
|
34
189
|
const csrPem = fs.readFileSync(csrPath, 'utf8');
|
|
35
190
|
return { csrPem, keyPem };
|
|
36
|
-
} catch (err) {
|
|
37
|
-
if (err.message && (err.message.includes('openssl') || err.message.includes('ENOENT'))) {
|
|
38
|
-
throw new Error(
|
|
39
|
-
'OpenSSL is required for certificate generation. Install OpenSSL and ensure it is on PATH, or use a system that provides it (e.g. Git for Windows).'
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
throw new Error(`CSR generation failed: ${err.message}`);
|
|
43
191
|
} finally {
|
|
44
192
|
try {
|
|
45
193
|
fs.unlinkSync(keyPath);
|
|
@@ -91,6 +239,22 @@ function readClientKeyPem(certDir) {
|
|
|
91
239
|
}
|
|
92
240
|
}
|
|
93
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Read Builder Server root CA PEM (ca.pem) for TLS verification in Node.
|
|
244
|
+
* Node does not use the OS trust store the same way as browsers; this file is used with tls.rootCertificates.
|
|
245
|
+
* @param {string} certDir - Directory containing ca.pem
|
|
246
|
+
* @returns {string|null} PEM content or null if not found
|
|
247
|
+
*/
|
|
248
|
+
function readServerCaPem(certDir) {
|
|
249
|
+
const caPath = path.join(certDir, 'ca.pem');
|
|
250
|
+
try {
|
|
251
|
+
return fs.readFileSync(caPath, 'utf8');
|
|
252
|
+
} catch (e) {
|
|
253
|
+
if (e.code === 'ENOENT') return null;
|
|
254
|
+
throw e;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
94
258
|
/**
|
|
95
259
|
* Get certificate validity end (notAfter) from cert.pem in certDir using OpenSSL.
|
|
96
260
|
* @param {string} certDir - Directory containing cert.pem
|
|
@@ -100,10 +264,7 @@ function getCertValidNotAfter(certDir) {
|
|
|
100
264
|
const certPath = path.join(certDir, 'cert.pem');
|
|
101
265
|
try {
|
|
102
266
|
if (!fs.existsSync(certPath)) return null;
|
|
103
|
-
const out =
|
|
104
|
-
`openssl x509 -enddate -noout -in "${certPath}"`,
|
|
105
|
-
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
106
|
-
);
|
|
267
|
+
const out = runOpenSSL(['x509', '-enddate', '-noout', '-in', certPath], { encoding: 'utf8' });
|
|
107
268
|
const match = out.match(/notAfter=(.+)/);
|
|
108
269
|
if (!match) return null;
|
|
109
270
|
const date = new Date(match[1].trim());
|
|
@@ -113,10 +274,98 @@ function getCertValidNotAfter(certDir) {
|
|
|
113
274
|
}
|
|
114
275
|
}
|
|
115
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Parse developer-id digits from OpenSSL `x509 -subject -noout` output (subject=RDN… line).
|
|
279
|
+
* Expects CN in the form dev-<digits> per Builder client-cert convention.
|
|
280
|
+
*
|
|
281
|
+
* @param {string} subjectOutput - stdout from openssl x509 -subject
|
|
282
|
+
* @returns {string|null} Developer id as in the CN (e.g. "01"), or null if missing/invalid
|
|
283
|
+
*/
|
|
284
|
+
function parseDeveloperIdFromX509SubjectOutput(subjectOutput) {
|
|
285
|
+
if (!subjectOutput || typeof subjectOutput !== 'string') return null;
|
|
286
|
+
const upper = subjectOutput.replace(/\r\n/g, '\n');
|
|
287
|
+
const cnMatch = upper.match(/CN\s*=\s*dev-([0-9]+)/i);
|
|
288
|
+
if (!cnMatch) return null;
|
|
289
|
+
return cnMatch[1];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Whether two developer-id strings refer to the same non-negative integer (e.g. "2" vs "02").
|
|
294
|
+
* @param {string} configId - Id from config
|
|
295
|
+
* @param {string} certIdDigits - Id digits from certificate CN
|
|
296
|
+
* @returns {boolean}
|
|
297
|
+
*/
|
|
298
|
+
function developerIdsMatchNumeric(configId, certIdDigits) {
|
|
299
|
+
if (
|
|
300
|
+
configId === null ||
|
|
301
|
+
configId === undefined ||
|
|
302
|
+
certIdDigits === null ||
|
|
303
|
+
certIdDigits === undefined
|
|
304
|
+
) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
const a = String(configId).trim();
|
|
308
|
+
const b = String(certIdDigits).trim();
|
|
309
|
+
if (!/^[0-9]+$/.test(a) || !/^[0-9]+$/.test(b)) return false;
|
|
310
|
+
return parseInt(a, 10) === parseInt(b, 10);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Read client certificate subject and return developer-id digits from CN=dev-<id>.
|
|
315
|
+
* @param {string} certDir - Directory containing cert.pem
|
|
316
|
+
* @returns {string|null} Id string from CN or null if cert missing/unreadable or CN invalid
|
|
317
|
+
*/
|
|
318
|
+
function getCertSubjectDeveloperId(certDir) {
|
|
319
|
+
const certPath = path.join(certDir, 'cert.pem');
|
|
320
|
+
try {
|
|
321
|
+
if (!certDir || typeof certDir !== 'string') return null;
|
|
322
|
+
if (!fs.existsSync(certPath)) return null;
|
|
323
|
+
const out = runOpenSSL(['x509', '-subject', '-noout', '-in', certPath], { encoding: 'utf8' });
|
|
324
|
+
return parseDeveloperIdFromX509SubjectOutput(out);
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Normalize PEM: JSON-escaped \\n to real newlines (Docker/OpenSSL).
|
|
332
|
+
* @param {string} pem
|
|
333
|
+
* @returns {string}
|
|
334
|
+
*/
|
|
335
|
+
function normalizePemNewlines(pem) {
|
|
336
|
+
if (typeof pem !== 'string') return pem;
|
|
337
|
+
return pem.replace(/\\n/g, '\n');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Distinct PEM blocks joined for ca.pem (HTTPS root + issue-cert CA, deduped).
|
|
342
|
+
* @param {...(string|null|undefined)} pems
|
|
343
|
+
* @returns {string|null}
|
|
344
|
+
*/
|
|
345
|
+
function mergeCaPemBlocks(...pems) {
|
|
346
|
+
const blocks = [];
|
|
347
|
+
const seen = new Set();
|
|
348
|
+
for (const p of pems) {
|
|
349
|
+
if (!p || typeof p !== 'string') continue;
|
|
350
|
+
const normalized = normalizePemNewlines(p.trim());
|
|
351
|
+
if (!normalized) continue;
|
|
352
|
+
if (seen.has(normalized)) continue;
|
|
353
|
+
seen.add(normalized);
|
|
354
|
+
blocks.push(normalized);
|
|
355
|
+
}
|
|
356
|
+
return blocks.length ? blocks.join('\n\n') : null;
|
|
357
|
+
}
|
|
358
|
+
|
|
116
359
|
module.exports = {
|
|
117
360
|
generateCSR,
|
|
118
361
|
getCertDir,
|
|
119
362
|
readClientCertPem,
|
|
120
363
|
readClientKeyPem,
|
|
121
|
-
|
|
364
|
+
readServerCaPem,
|
|
365
|
+
getCertValidNotAfter,
|
|
366
|
+
getCertSubjectDeveloperId,
|
|
367
|
+
parseDeveloperIdFromX509SubjectOutput,
|
|
368
|
+
developerIdsMatchNumeric,
|
|
369
|
+
normalizePemNewlines,
|
|
370
|
+
mergeCaPemBlocks
|
|
122
371
|
};
|