@aifabrix/builder 2.55.2 → 2.56.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/lib/api/pipeline.api.js +32 -3
- package/lib/app/deploy.js +6 -5
- package/lib/cli/setup-app.js +7 -6
- package/lib/cli/setup-external-system-help.js +15 -3
- package/lib/cli/setup-external-system.js +21 -3
- package/lib/commands/repair-datasource-auto-rbac.js +4 -9
- package/lib/commands/upload-openapi-sync-followup.js +59 -0
- package/lib/commands/upload-qualification-client.js +131 -0
- package/lib/commands/upload-qualification-followups.js +104 -0
- package/lib/commands/upload-qualification-poll.js +143 -0
- package/lib/commands/upload.js +37 -156
- package/lib/deployment/deploy-poll-ui.js +14 -10
- package/lib/deployment/deployer-status.js +4 -4
- package/lib/external-system/deploy.js +46 -51
- package/lib/generator/helpers.js +4 -47
- package/lib/generator/index.js +0 -4
- package/lib/programmatic/builder-api-request.js +8 -4
- package/lib/programmatic/capability-registry.js +2 -0
- package/lib/programmatic/get-deploy-status.js +45 -0
- package/lib/programmatic/get-upload-status.js +45 -0
- package/lib/programmatic/http-accepted-response.js +31 -0
- package/lib/programmatic/openapi-descriptions.js +14 -4
- package/lib/programmatic/request-timeout.js +21 -15
- package/lib/programmatic/route-handler-map.js +6 -0
- package/lib/programmatic/run-deploy.js +7 -3
- package/lib/programmatic/run-upload.js +11 -7
- package/lib/schema/infra.parameter.yaml +6 -7
- package/lib/utils/cli-utils.js +3 -2
- package/lib/utils/qualification-poll-ui.js +136 -0
- package/package.json +3 -2
- package/templates/applications/builder-api/env.template +4 -0
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/keycloak/Dockerfile +12 -5
- package/templates/applications/keycloak/application.yaml +2 -2
- package/templates/applications/keycloak/cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java +3 -3
- package/templates/applications/keycloak/cimd-lenient-parse/SOURCE.md +6 -6
- package/templates/applications/keycloak/env.template +13 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +13 -16
- package/lib/generator/deploy-manifest-azure-kv.js +0 -81
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TTY ora spinner + throttled non-TTY lines for qualification job polling.
|
|
3
|
+
* Mirrors host-ops-poll-ui.js's presentation pattern (plan 191.0 task 1).
|
|
4
|
+
* @author AI Fabrix Team
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const logger = require('./logger');
|
|
12
|
+
|
|
13
|
+
const SILENT_POLL_HANDLERS = {
|
|
14
|
+
onPollProgress: () => {},
|
|
15
|
+
onTransientError: () => {},
|
|
16
|
+
finish: () => {}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const NON_TTY_THROTTLE_MS = 5000;
|
|
20
|
+
|
|
21
|
+
const HUMAN_STEP_LABELS = Object.freeze({
|
|
22
|
+
stageCandidate: 'Uploading configuration',
|
|
23
|
+
gateOperations: 'Checking operations',
|
|
24
|
+
gateTrust: 'Checking trust',
|
|
25
|
+
gateGovernance: 'Checking governance',
|
|
26
|
+
sealCertificate: 'Finalizing upload'
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function shouldUseQualificationPollSpinner() {
|
|
30
|
+
return Boolean(process && process.stdout && process.stdout.isTTY);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Object} job - PublishJobStatusResponse-shaped data ({ state, steps })
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
function currentQualificationStepLabel(job, options = {}) {
|
|
38
|
+
if (!job || !Array.isArray(job.steps) || job.steps.length === 0) {
|
|
39
|
+
return 'Preparing upload';
|
|
40
|
+
}
|
|
41
|
+
const running = job.steps.find((step) => step.status === 'running');
|
|
42
|
+
if (running) return qualificationStepLabel(running, 'running', options);
|
|
43
|
+
const failed = job.steps.find((step) => step.status === 'failed');
|
|
44
|
+
if (failed) return qualificationStepLabel(failed, 'failed', options);
|
|
45
|
+
const pending = job.steps.find((step) => step.status === 'pending');
|
|
46
|
+
if (pending) return qualificationStepLabel(pending, 'pending', options);
|
|
47
|
+
return 'Upload complete';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function qualificationStepLabel(step, _status, _options) {
|
|
51
|
+
return HUMAN_STEP_LABELS[step.key] || 'Processing upload';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} jobId
|
|
56
|
+
* @param {Object|null} job
|
|
57
|
+
* @param {number} elapsedMs
|
|
58
|
+
* @param {string} [transientNote]
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function buildQualificationPollLine(jobId, job, elapsedMs, transientNote, options = {}) {
|
|
62
|
+
const seconds = Math.max(0, Math.round(elapsedMs / 1000));
|
|
63
|
+
const note = transientNote ? ` — ${transientNote}` : '';
|
|
64
|
+
if (options.verbose === true) {
|
|
65
|
+
return `Upload (job ${jobId}): ${currentQualificationStepLabel(job, options)} — ${seconds}s elapsed${note}`;
|
|
66
|
+
}
|
|
67
|
+
return `${currentQualificationStepLabel(job, options)} — ${seconds}s elapsed${note}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createSpinnerQualificationPollHandlers(jobId, startedAt, options) {
|
|
71
|
+
const ora = require('ora');
|
|
72
|
+
const spinner = ora({
|
|
73
|
+
text: buildQualificationPollLine(jobId, null, 0, undefined, options),
|
|
74
|
+
spinner: 'dots'
|
|
75
|
+
}).start();
|
|
76
|
+
return {
|
|
77
|
+
onPollProgress: (job) => {
|
|
78
|
+
spinner.text = buildQualificationPollLine(jobId, job, Date.now() - startedAt, undefined, options);
|
|
79
|
+
},
|
|
80
|
+
onTransientError: (status, elapsedMs) => {
|
|
81
|
+
spinner.text = buildQualificationPollLine(
|
|
82
|
+
jobId,
|
|
83
|
+
null,
|
|
84
|
+
elapsedMs,
|
|
85
|
+
'service temporarily unavailable, retrying',
|
|
86
|
+
options
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
finish: () => spinner.stop()
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createNonTtyQualificationPollHandlers(jobId, startedAt, options) {
|
|
94
|
+
let lastLogAt = 0;
|
|
95
|
+
return {
|
|
96
|
+
onPollProgress: (job) => {
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
if (now - lastLogAt < NON_TTY_THROTTLE_MS) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
lastLogAt = now;
|
|
102
|
+
logger.log(chalk.gray(` ${buildQualificationPollLine(jobId, job, now - startedAt, undefined, options)}`));
|
|
103
|
+
},
|
|
104
|
+
onTransientError: (status, elapsedMs) => {
|
|
105
|
+
logger.log(
|
|
106
|
+
chalk.gray(
|
|
107
|
+
` ${buildQualificationPollLine(jobId, null, elapsedMs, 'service temporarily unavailable, retrying', options)}`
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
finish: () => {}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {string} jobId
|
|
117
|
+
* @param {{ startedAt?: number, silent?: boolean, verbose?: boolean }} [options]
|
|
118
|
+
* @returns {{ onPollProgress: Function, onTransientError: Function, finish: () => void }}
|
|
119
|
+
*/
|
|
120
|
+
function createQualificationPollHandlers(jobId, options = {}) {
|
|
121
|
+
if (options.silent === true) {
|
|
122
|
+
return SILENT_POLL_HANDLERS;
|
|
123
|
+
}
|
|
124
|
+
const startedAt = options.startedAt || Date.now();
|
|
125
|
+
if (shouldUseQualificationPollSpinner()) {
|
|
126
|
+
return createSpinnerQualificationPollHandlers(jobId, startedAt, options);
|
|
127
|
+
}
|
|
128
|
+
return createNonTtyQualificationPollHandlers(jobId, startedAt, options);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
currentQualificationStepLabel,
|
|
133
|
+
buildQualificationPollLine,
|
|
134
|
+
createQualificationPollHandlers,
|
|
135
|
+
shouldUseQualificationPollSpinner
|
|
136
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.56.0",
|
|
4
4
|
"description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"dev-reload": "node scripts/pnpm/dev-reload.mjs",
|
|
22
22
|
"dev-build": "node scripts/pnpm/af.mjs dev-build",
|
|
23
23
|
"dev-promote": "node scripts/pnpm/af.mjs dev-promote",
|
|
24
|
+
"dev-deploy": "node scripts/pnpm/dev-deploy.mjs",
|
|
24
25
|
"af": "node scripts/pnpm/af.mjs",
|
|
25
26
|
"check-quiet": "node scripts/pnpm/check-quiet.mjs",
|
|
26
27
|
"check": "npm run lint && npm run test",
|
|
@@ -125,7 +126,7 @@
|
|
|
125
126
|
}
|
|
126
127
|
},
|
|
127
128
|
"dependencies": {
|
|
128
|
-
"@aifabrix/miso-client": "
|
|
129
|
+
"@aifabrix/miso-client": "4.22.4",
|
|
129
130
|
"adm-zip": "^0.6.0",
|
|
130
131
|
"ajv": "^8.20.0",
|
|
131
132
|
"ajv-formats": "^3.0.1",
|
|
@@ -37,6 +37,10 @@ DATAPLANE_URL=url://dataplane-internal
|
|
|
37
37
|
# Public: Slack/Teams/ChatGPT callbacks, packages, and portal remaining-step URLs
|
|
38
38
|
DATAPLANE_PUBLIC_URL=url://dataplane-public
|
|
39
39
|
|
|
40
|
+
# Self URLs: internal service calls and public links generated by Builder API
|
|
41
|
+
BUILDER_API_URL=url://internal
|
|
42
|
+
BUILDER_API_PUBLIC_URL=url://public
|
|
43
|
+
|
|
40
44
|
DEFAULT_ENVIRONMENT=dev
|
|
41
45
|
|
|
42
46
|
# Browser Origin allowlist for portal calls (CORS). url:// expands at resolve.
|
|
@@ -5,7 +5,7 @@ app:
|
|
|
5
5
|
description: "AI Fabrix Dataplane is a secure, in-tenant integration and automation layer that supplies governed, normalized, and explainable enterprise data to AI agents. Using CIP as a declarative standard, it enforces RBAC and ABAC, executes integrations, and exposes trusted data via MCP and OpenAPI."
|
|
6
6
|
type: webapp
|
|
7
7
|
language: python # Explicitly specify Python language
|
|
8
|
-
version: 2.0.
|
|
8
|
+
version: 2.0.68
|
|
9
9
|
# Image Configuration
|
|
10
10
|
# Set tag to match your build (example: aifabrix build dataplane -t 1.0.0).
|
|
11
11
|
# Registry is required so the controller can pull the image (avoids docker-not-found on the controller host).
|
|
@@ -44,7 +44,7 @@ API_KEY_BYPASS_ROLES=full-access
|
|
|
44
44
|
|
|
45
45
|
# API Configuration
|
|
46
46
|
API_V1_STR=/api/v1
|
|
47
|
-
VERSION=2.0.
|
|
47
|
+
VERSION=2.0.68
|
|
48
48
|
# Minimum supported aifabrix-builder CLI semver (advertised on /health and /api/v1/health; empty = no gate)
|
|
49
49
|
# MIN_BUILDER_CLI_VERSION=2.45.0
|
|
50
50
|
MIN_BUILDER_CLI_VERSION=2.45.5
|
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
# Keycloak Identity and Access Management with Custom Themes
|
|
2
|
-
# Official 26.7.
|
|
2
|
+
# Official 26.7.3 distro + experimental CIMD. Does not compile Keycloak from source.
|
|
3
3
|
# One class is recompiled against this image's jars:
|
|
4
4
|
# - PR 51235: CIMD parse ignores unknown JSON fields (ChatGPT client.json)
|
|
5
5
|
# - #50362: strip jwt-bearer (and other confidential-only grants) on public CIMD
|
|
6
6
|
# 26.7 advertises token_endpoint_auth_methods_supported "none" for Claude CIMD.
|
|
7
7
|
|
|
8
|
-
FROM quay.io/keycloak/keycloak:26.7.
|
|
8
|
+
FROM quay.io/keycloak/keycloak:26.7.3 AS keycloak-base
|
|
9
9
|
|
|
10
10
|
FROM eclipse-temurin:21-jdk-jammy AS cimd-patch
|
|
11
11
|
COPY --from=keycloak-base /opt/keycloak/lib/lib/main /opt/kc-jars/main
|
|
12
12
|
COPY --from=keycloak-base /opt/keycloak/lib/lib/boot /opt/kc-jars/boot
|
|
13
13
|
COPY cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java \
|
|
14
14
|
/tmp/src/org/keycloak/protocol/oauth2/cimd/clientpolicy/executor/AbstractClientIdMetadataDocumentExecutor.java
|
|
15
|
+
COPY mori-email-sender/com /tmp/src/com
|
|
16
|
+
COPY mori-email-sender/META-INF /tmp/mori-provider/META-INF
|
|
15
17
|
RUN mkdir -p /tmp/classes \
|
|
16
18
|
&& find /opt/kc-jars -name '*.jar' | paste -sd: - > /tmp/cp.txt \
|
|
17
19
|
&& { \
|
|
18
20
|
echo --release; echo 21; echo -cp; cat /tmp/cp.txt; echo -d; echo /tmp/classes; \
|
|
19
21
|
echo /tmp/src/org/keycloak/protocol/oauth2/cimd/clientpolicy/executor/AbstractClientIdMetadataDocumentExecutor.java; \
|
|
22
|
+
find /tmp/src/com -name '*.java'; \
|
|
20
23
|
} > /tmp/javac.args \
|
|
21
24
|
&& javac @/tmp/javac.args \
|
|
22
25
|
&& SERVICES_JAR=$(ls /opt/kc-jars/main/org.keycloak.keycloak-services-*.jar) \
|
|
23
26
|
&& cp "$SERVICES_JAR" /tmp/keycloak-services-patched.jar \
|
|
24
|
-
&& jar uf /tmp/keycloak-services-patched.jar -C /tmp/classes
|
|
27
|
+
&& jar uf /tmp/keycloak-services-patched.jar -C /tmp/classes org/keycloak \
|
|
28
|
+
&& cp -r /tmp/classes/com /tmp/mori-provider/ \
|
|
29
|
+
&& jar cf /tmp/mori-email-sender.jar -C /tmp/mori-provider .
|
|
25
30
|
|
|
26
|
-
FROM quay.io/keycloak/keycloak:26.7.
|
|
31
|
+
FROM quay.io/keycloak/keycloak:26.7.3
|
|
27
32
|
|
|
28
33
|
# Set working directory
|
|
29
34
|
WORKDIR /opt/keycloak
|
|
@@ -31,6 +36,7 @@ WORKDIR /opt/keycloak
|
|
|
31
36
|
# Copy custom themes from share directory (only if themes/ folder exists)
|
|
32
37
|
USER root
|
|
33
38
|
COPY --from=cimd-patch /tmp/keycloak-services-patched.jar /tmp/keycloak-services-patched.jar
|
|
39
|
+
COPY --from=cimd-patch /tmp/mori-email-sender.jar /opt/keycloak/providers/mori-email-sender.jar
|
|
34
40
|
RUN SERVICES_JAR=$(ls /opt/keycloak/lib/lib/main/org.keycloak.keycloak-services-*.jar) \
|
|
35
41
|
&& cp /tmp/keycloak-services-patched.jar "$SERVICES_JAR" \
|
|
36
42
|
&& rm /tmp/keycloak-services-patched.jar
|
|
@@ -50,7 +56,8 @@ RUN if [ -d "/tmp/build-context/themes" ] && [ "$(ls -A /tmp/build-context/theme
|
|
|
50
56
|
# Build Keycloak with health checks enabled (similar to Keycloak 24.0)
|
|
51
57
|
# This enables the /health, /health/live, /health/ready, and /health/started endpoints
|
|
52
58
|
# Expose health endpoints on main HTTP port (like 24.0) instead of management port
|
|
53
|
-
RUN /opt/keycloak/bin/kc.sh build --health-enabled=true --http-management-health-enabled=false
|
|
59
|
+
RUN /opt/keycloak/bin/kc.sh build --health-enabled=true --http-management-health-enabled=false \
|
|
60
|
+
--features=cimd --spi-email-sender--provider=mori
|
|
54
61
|
|
|
55
62
|
# Switch back to keycloak user
|
|
56
63
|
USER keycloak
|
|
@@ -4,12 +4,12 @@ app:
|
|
|
4
4
|
displayName: 'AI Fabrix Keycloak'
|
|
5
5
|
description: 'Identity and Access Management'
|
|
6
6
|
type: webapp
|
|
7
|
-
version: '26.7.
|
|
7
|
+
version: '26.7.3-aifabrix.1'
|
|
8
8
|
|
|
9
9
|
# Image Configuration
|
|
10
10
|
image:
|
|
11
11
|
name: aifabrix/keycloak
|
|
12
|
-
tag: '
|
|
12
|
+
tag: '26.7.3-aifabrix.1'
|
|
13
13
|
registry: aifabrixacr.azurecr.io
|
|
14
14
|
registryMode: external
|
|
15
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
package org.keycloak.protocol.oauth2.cimd.clientpolicy.executor;
|
|
2
2
|
|
|
3
|
-
// AI Fabrix Keycloak image patch on 26.7.
|
|
3
|
+
// AI Fabrix Keycloak image patch on 26.7.3 (not a Keycloak source build):
|
|
4
4
|
// 1) PR 51235 — ignore unknown CIMD JSON fields (ChatGPT client.json).
|
|
5
5
|
// 2) keycloak#50362 / PR 50411 idea — drop confidential-only grants (jwt-bearer)
|
|
6
6
|
// from public CIMD metadata so Claude hosted Connect can register.
|
|
@@ -848,7 +848,7 @@ public abstract class AbstractClientIdMetadataDocumentExecutor<CONFIG extends Ab
|
|
|
848
848
|
|
|
849
849
|
/**
|
|
850
850
|
* Grant types Keycloak's DescriptionConverter refuses on a public client.
|
|
851
|
-
* Same set as Keycloak PR 50411 (not in 26.7.
|
|
851
|
+
* Same set as Keycloak PR 50411 (not in 26.7.3).
|
|
852
852
|
*/
|
|
853
853
|
protected static final Set<String> CONFIDENTIAL_ONLY_GRANT_TYPES = new LinkedHashSet<>(Arrays.asList(
|
|
854
854
|
OAuth2Constants.CLIENT_CREDENTIALS,
|
|
@@ -873,7 +873,7 @@ public abstract class AbstractClientIdMetadataDocumentExecutor<CONFIG extends Ab
|
|
|
873
873
|
|
|
874
874
|
/**
|
|
875
875
|
* RFC 7591 §3.2.1 lets the AS replace requested grant_types. Claude CIMD is public
|
|
876
|
-
* (`none`) plus jwt-bearer; Keycloak 26.7.
|
|
876
|
+
* (`none`) plus jwt-bearer; Keycloak 26.7.3 rejects that combination (#50362).
|
|
877
877
|
*/
|
|
878
878
|
protected void stripConfidentialOnlyGrantsForPublicClient(OIDCClientRepresentation oidcClient) {
|
|
879
879
|
if (!"none".equals(oidcClient.getTokenEndpointAuthMethod())) {
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
# CIMD image patch (Keycloak 26.7.
|
|
1
|
+
# CIMD image patch (Keycloak 26.7.3)
|
|
2
2
|
|
|
3
|
-
This directory is **not** a Keycloak source build. `aifabrix build keycloak` still starts from `quay.io/keycloak/keycloak:26.7.
|
|
3
|
+
This directory is **not** a Keycloak source build. `aifabrix build keycloak` still starts from `quay.io/keycloak/keycloak:26.7.3`. A Docker stage recompiles this one class against that image’s jars and injects it into `org.keycloak.keycloak-services-*.jar` before `kc.sh build`.
|
|
4
4
|
|
|
5
|
-
Upstream file: `services/.../AbstractClientIdMetadataDocumentExecutor.java` @ tag `26.7.
|
|
5
|
+
Upstream file: `services/.../AbstractClientIdMetadataDocumentExecutor.java` @ tag `26.7.3` (Apache 2.0).
|
|
6
6
|
|
|
7
7
|
## Lenient parse (ChatGPT)
|
|
8
8
|
|
|
9
9
|
ChatGPT’s CIMD `client.json` includes extra fields (for example `token_endpoint_auth_methods_supported`) that Keycloak 26.7.x rejects because CIMD parse uses Jackson `FAIL_ON_UNKNOWN_PROPERTIES`.
|
|
10
10
|
|
|
11
|
-
- [keycloak/keycloak#51235](https://github.com/keycloak/keycloak/pull/51235) (open; not in 26.7.
|
|
11
|
+
- [keycloak/keycloak#51235](https://github.com/keycloak/keycloak/pull/51235) (open; not in 26.7.3)
|
|
12
12
|
|
|
13
13
|
## Public client + jwt-bearer (Claude hosted)
|
|
14
14
|
|
|
15
|
-
Claude’s CIMD is a public client (`token_endpoint_auth_method: none`) that also lists `urn:ietf:params:oauth:grant-type:jwt-bearer`. Keycloak 26.7.
|
|
15
|
+
Claude’s CIMD is a public client (`token_endpoint_auth_method: none`) that also lists `urn:ietf:params:oauth:grant-type:jwt-bearer`. Keycloak 26.7.3 then fails authorize with `JWT authorization grant cannot be enabled in a public client` ([#50362](https://github.com/keycloak/keycloak/issues/50362)). Official [PR 50411](https://github.com/keycloak/keycloak/pull/50411) is still a draft and defaults the filter **off**. This image always drops confidential-only grants on public CIMD metadata (RFC 7591 §3.2.1), which is enough for hosted Claude Connect (authorization_code). It does **not** implement public-client JWT Authorization Grant / EMA.
|
|
16
16
|
|
|
17
|
-
26.7.
|
|
17
|
+
26.7.3 already advertises `token_endpoint_auth_methods_supported` `"none"` when `--features=cimd`.
|
|
18
18
|
|
|
19
19
|
Drop this patched class when the consumed `quay.io` tag includes both behaviors.
|
|
@@ -55,7 +55,7 @@ KC_HEALTH_ENABLED=true
|
|
|
55
55
|
KC_HTTP_MANAGEMENT_HEALTH_ENABLED=false
|
|
56
56
|
# Single-instance: use local cache so /health/ready passes (avoids Infinispan cluster check)
|
|
57
57
|
KC_CACHE=local
|
|
58
|
-
# Runtime features. Include cimd so Keycloak 26.7.
|
|
58
|
+
# Runtime features. Include cimd so Keycloak 26.7.3 advertises Client ID Metadata
|
|
59
59
|
# Documents. preview alone triggers a start-time rebuild that drops cimd.
|
|
60
60
|
KC_FEATURES=preview,cimd
|
|
61
61
|
|
|
@@ -67,6 +67,18 @@ KC_FEATURES=preview,cimd
|
|
|
67
67
|
MISO_CLIENTID=kv://keycloak-client-idKeyVault
|
|
68
68
|
MISO_CLIENTSECRET=kv://keycloak-client-secretKeyVault
|
|
69
69
|
|
|
70
|
+
# =============================================================================
|
|
71
|
+
# MANAGED ACTION EMAIL (Keycloak -> Mori REST)
|
|
72
|
+
# =============================================================================
|
|
73
|
+
# The custom EmailSenderProvider relays Keycloak-rendered signed action emails to
|
|
74
|
+
# Mori. It never receives ACS credentials and never sends the action URL to Miso.
|
|
75
|
+
# Installation must supply the Mori environment returned during registration.
|
|
76
|
+
MORI_BASE_URL=kv://mori-controller-web-server-url
|
|
77
|
+
MORI_ENVIRONMENT_ID=kv://mori-controller-environment-idKeyVault
|
|
78
|
+
MORI_API_KEY=kv://mori-controller-api-keyKeyVault
|
|
79
|
+
MORI_NOTIFICATION_TIMEOUT_MS=10000
|
|
80
|
+
MORI_NOTIFICATION_MAX_ATTEMPTS=2
|
|
81
|
+
|
|
70
82
|
# Connects to postgres service in Docker network (postgres) or localhost (local)
|
|
71
83
|
|
|
72
84
|
KC_DB=postgres
|
|
@@ -4,7 +4,7 @@ app:
|
|
|
4
4
|
displayName: 'Miso Controller'
|
|
5
5
|
description: 'Miso is the AI Fabrix in-tenant controller and portal layer for securely operating enterprise AI apps inside a customer’s Azure tenant. It provides Entra ID SSO, RBAC, audit logs, environment/app configuration via schemas, and safe secret handling via Key Vault references—ensuring governance, traceability, and predictable UX across portal, SDK, and CLI.'
|
|
6
6
|
type: webapp
|
|
7
|
-
version: '2.0.
|
|
7
|
+
version: '2.0.78'
|
|
8
8
|
# Image Configuration
|
|
9
9
|
# Set tag to match your build (e.g. aifabrix build miso-controller -t 1.0.0 then tag: 1.0.0)
|
|
10
10
|
# Registry is required so the controller can pull the image (avoids "docker: not found" on the controller host).
|
|
@@ -177,7 +177,7 @@ IDENTITY_LOCAL_PASSWORD_MODE=admin-reveal
|
|
|
177
177
|
# Both onboarding modes use Mori's AI Fabrix-owned sender. MORI_API_KEY below is the
|
|
178
178
|
# existing installation service key; no notification-specific secret is created.
|
|
179
179
|
NOTIFICATION_BINDING=aifabrixManaged
|
|
180
|
-
MORI_ENVIRONMENT_ID=
|
|
180
|
+
MORI_ENVIRONMENT_ID=kv://mori-controller-environment-idKeyVault
|
|
181
181
|
|
|
182
182
|
# Keycloak Events Configuration
|
|
183
183
|
KEYCLOAK_EVENTS_ENABLED=true
|
|
@@ -237,7 +237,7 @@ CONTROLLER_EVALUATION=
|
|
|
237
237
|
#
|
|
238
238
|
# Delegated Graph (second redirect URI on the same vendor app): see section below.
|
|
239
239
|
ENTRA_CALLBACK_STATE_SECRET=kv://entra-callback-state-secret
|
|
240
|
-
ENTRA_ID_CENTRAL_CALLBACK_URI=https://aifabrix
|
|
240
|
+
ENTRA_ID_CENTRAL_CALLBACK_URI=https://dev01.aifabrix.dev/mori/entra-id-concent/callback
|
|
241
241
|
ENTRA_ID_CLIENT_ID=kv://entra-client-idKeyVault
|
|
242
242
|
ENTRA_ID_CLIENT_SECRET=kv://entra-client-secretKeyVault
|
|
243
243
|
|
|
@@ -323,8 +323,10 @@ SHOW_TA_COUNT_DOWN=false
|
|
|
323
323
|
# Encryption Key for Database Secrets
|
|
324
324
|
ENCRYPTION_KEY=kv://miso-controller-secrets-encryptionKeyVault
|
|
325
325
|
|
|
326
|
-
# Comma-separated application keys allowed for POST .../applications/bootstrap (install-time only)
|
|
327
|
-
|
|
326
|
+
# Comma-separated application keys allowed for POST .../applications/bootstrap (install-time only).
|
|
327
|
+
# Environment placement is a separate controller rule: workloads (dataplane, builder-api) install into
|
|
328
|
+
# dev/tst/pro but never `miso`; miso-controller and keycloak only into `miso`; mori-controller anywhere.
|
|
329
|
+
BOOTSTRAP_APPKEYS=dataplane,builder-api,mori-controller
|
|
328
330
|
|
|
329
331
|
# Key Vault Integration (for security parameter encryption)
|
|
330
332
|
# Set to true to enable Azure Key Vault for storing security parameters
|
|
@@ -347,18 +349,16 @@ API_KEY=kv://miso-controller-secrets-apiKeyVault
|
|
|
347
349
|
# =============================================================================
|
|
348
350
|
# LICENSE CONFIGURATION — [always]
|
|
349
351
|
# =============================================================================
|
|
350
|
-
# [saasDirect] Must use the Mori flow
|
|
352
|
+
# [saasDirect] Must use the Mori flow: Mori issues and owns the
|
|
351
353
|
# 30-day STANDARD / M trial, and sales later updates the same subscription.
|
|
352
354
|
#
|
|
353
355
|
# Offline JWT license (optional):
|
|
354
356
|
# - If set, controller validates license offline (RS256) without Mori subscription status call.
|
|
355
357
|
# - Value can be literal JWT or kv:// reference.
|
|
356
|
-
# - If
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
360
|
-
# - Use only for local development (DEPLOYMENT=local|database|azure-mock).
|
|
361
|
-
# - Ignored when DEPLOYMENT=azure (Azure marketplace / App Service installs).
|
|
358
|
+
# - If empty and no stored licence exists, controller runs as Community edition
|
|
359
|
+
# (DEV environment, size S, shared resources).
|
|
360
|
+
# - Set only to a real signed offline licence JWT. Controlled tests may use the separate
|
|
361
|
+
# LICENSE_VALIDATION_DISABLED=true switch; do not use it in deployed installations.
|
|
362
362
|
LICENSE_JWT=kv://miso-controller-jwt-licenseKeyVault
|
|
363
363
|
|
|
364
364
|
# =============================================================================
|
|
@@ -366,7 +366,7 @@ LICENSE_JWT=kv://miso-controller-jwt-licenseKeyVault
|
|
|
366
366
|
# =============================================================================
|
|
367
367
|
# Must match a Web redirect URI on the vendor multi-tenant app and Mori’s route
|
|
368
368
|
# …/entra-graph-delegated/callback. Shared pro Mori addon (CICD *-miso-rg):
|
|
369
|
-
ENTRA_GRAPH_DELEGATED_REDIRECT_URI=https://aifabrix
|
|
369
|
+
ENTRA_GRAPH_DELEGATED_REDIRECT_URI=https://dev01.aifabrix.dev/mori/entra-graph-delegated/callback
|
|
370
370
|
|
|
371
371
|
# Optional delegated Graph scopes for GET …/graph-auth-url (space- or comma-separated).
|
|
372
372
|
# ENTRA_GRAPH_DELEGATED_SCOPES=https://graph.microsoft.com/Application.ReadWrite.All,https://graph.microsoft.com/Group.ReadWrite.All,https://graph.microsoft.com/Synchronization.ReadWrite.All,openid,profile,offline_access
|
|
@@ -381,12 +381,9 @@ ENTRA_GRAPH_DELEGATED_REDIRECT_URI=https://aifabrix-pro-miso-mori.azurewebsites.
|
|
|
381
381
|
# only works when a local Mori app is registered and running for this developer.
|
|
382
382
|
# MORI_CALLBACK_API_KEY must be the same secret as Mori MORI_CONTROLLER_CALLBACK_API_KEY
|
|
383
383
|
# (both resolve from kv://mori-callback-api-keyKeyVault — do not generate separate values).
|
|
384
|
-
MORI_BASE_URL=
|
|
385
|
-
MORI_AUTH_METHOD=kv://mori-controller-auth-methodKeyVault
|
|
384
|
+
MORI_BASE_URL=kv://mori-controller-web-server-url
|
|
386
385
|
MORI_CALLBACK_API_KEY=kv://mori-callback-api-keyKeyVault
|
|
387
386
|
MORI_API_KEY=kv://mori-controller-api-keyKeyVault
|
|
388
|
-
MORI_USERNAME=kv://mori-controller-basic-usernameKeyVault
|
|
389
|
-
MORI_PASSWORD=kv://mori-controller-basic-passwordKeyVault
|
|
390
387
|
|
|
391
388
|
# =============================================================================
|
|
392
389
|
# LOGGING CONFIGURATION
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Map url:// placeholders and Traefik host templates to Azure Key Vault secret names
|
|
3
|
-
* in deployment manifests (Miso Controller / App Service @Microsoft.KeyVault references).
|
|
4
|
-
*
|
|
5
|
-
* @fileoverview Plan 122 — deploy JSON must not ship url:// or ${DEV_USERNAME} for Azure
|
|
6
|
-
* @author AI Fabrix Team
|
|
7
|
-
* @version 1.0.0
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
'use strict';
|
|
11
|
-
|
|
12
|
-
const { parseUrlToken } = require('../utils/url-declarative-token-parse');
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param {string|undefined} surface
|
|
16
|
-
* @param {string} subject
|
|
17
|
-
* @param {boolean} isPublic
|
|
18
|
-
* @returns {string|undefined} Secret name when surface is vdir/host
|
|
19
|
-
*/
|
|
20
|
-
function secretNameFromSurface(surface, subject, isPublic) {
|
|
21
|
-
if (surface === 'vdir') {
|
|
22
|
-
return `${subject}-vdir-${isPublic ? 'public' : 'internal'}`;
|
|
23
|
-
}
|
|
24
|
-
if (surface === 'host') {
|
|
25
|
-
return `${subject}-host-${isPublic ? 'public' : 'internal'}`;
|
|
26
|
-
}
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Key Vault secret name for a url:// token (path after url://).
|
|
32
|
-
* Aligns with infrastructure/bicep (e.g. keycloak-web-server-url, miso-controller-web-server-url).
|
|
33
|
-
*
|
|
34
|
-
* @param {string} ownerAppKey - application.yaml app.key for the manifest being generated
|
|
35
|
-
* @param {string} token - Token after url:// (no scheme)
|
|
36
|
-
* @returns {string}
|
|
37
|
-
*/
|
|
38
|
-
function urlTokenToKeyVaultSecretName(ownerAppKey, token) {
|
|
39
|
-
const selfKey = String(ownerAppKey || '').trim() || 'app';
|
|
40
|
-
const t = String(token || '').trim();
|
|
41
|
-
if (!t) {
|
|
42
|
-
throw new Error('Empty url:// reference (expected a token after url://)');
|
|
43
|
-
}
|
|
44
|
-
const { targetKey, kind, surface } = parseUrlToken(t);
|
|
45
|
-
const isPublic = kind === 'public';
|
|
46
|
-
const subject = targetKey ? String(targetKey).trim() : selfKey;
|
|
47
|
-
|
|
48
|
-
const fromSurface = secretNameFromSurface(surface, subject, isPublic);
|
|
49
|
-
if (fromSurface !== undefined) {
|
|
50
|
-
return fromSurface;
|
|
51
|
-
}
|
|
52
|
-
if (targetKey === 'keycloak' || (!targetKey && selfKey === 'keycloak')) {
|
|
53
|
-
return isPublic ? 'keycloak-web-server-url' : 'keycloak-internal-server-url';
|
|
54
|
-
}
|
|
55
|
-
if (!targetKey) {
|
|
56
|
-
return isPublic ? `${selfKey}-web-server-url` : `${selfKey}-internal-server-url`;
|
|
57
|
-
}
|
|
58
|
-
return isPublic ? `${subject}-web-server-url` : `${subject}-internal-server-url`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Replace Traefik-style host templates with a Key Vault secret name consumed by Azure provisioning.
|
|
63
|
-
*
|
|
64
|
-
* @param {Object} deployment - Deployment manifest (mutated in place)
|
|
65
|
-
* @returns {void}
|
|
66
|
-
*/
|
|
67
|
-
function rewriteFrontDoorHostForAzureDeploy(deployment) {
|
|
68
|
-
if (!deployment || !deployment.frontDoorRouting || typeof deployment.frontDoorRouting.host !== 'string') {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const h = deployment.frontDoorRouting.host;
|
|
72
|
-
if (/\$\{(DEV_USERNAME|REMOTE_HOST)\}/.test(h)) {
|
|
73
|
-
const key = deployment.key || 'app';
|
|
74
|
-
deployment.frontDoorRouting.host = `${key}-frontdoor-routing-host`;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
module.exports = {
|
|
79
|
-
urlTokenToKeyVaultSecretName,
|
|
80
|
-
rewriteFrontDoorHostForAzureDeploy
|
|
81
|
-
};
|