fix: 保真渲染 AgentRun 多键工具凭据
This commit is contained in:
@@ -579,7 +579,9 @@ export function agentRunExecutionPolicyWithLaneCredentials(basePolicy: Record<st
|
||||
},
|
||||
};
|
||||
});
|
||||
const toolCredentials = includeToolCredentials ? agentRunToolCredentialRefs(spec).map((credential) => {
|
||||
const toolCredentialRefs = includeToolCredentials ? agentRunToolCredentialRefs(spec) : [];
|
||||
if (includeToolCredentials) validateBasePolicyToolCredentials(basePolicy, toolCredentialRefs, target);
|
||||
const toolCredentials = toolCredentialRefs.map((credential) => {
|
||||
if (credential.secretRef.namespace !== spec.runtime.namespace) {
|
||||
throw new AgentRunRestError("validation-failed", `toolCredential ${credential.tool} Secret ${credential.secretRef.name} is in namespace ${credential.secretRef.namespace}, expected target lane namespace ${spec.runtime.namespace}`);
|
||||
}
|
||||
@@ -588,11 +590,11 @@ export function agentRunExecutionPolicyWithLaneCredentials(basePolicy: Record<st
|
||||
purpose: credential.purpose,
|
||||
secretRef: {
|
||||
name: credential.secretRef.name,
|
||||
keys: [credential.secretRef.key],
|
||||
keys: credential.secretRef.keys,
|
||||
},
|
||||
projection: credential.projection,
|
||||
};
|
||||
}) : [];
|
||||
});
|
||||
const secretScope = record(basePolicy.secretScope);
|
||||
return {
|
||||
...basePolicy,
|
||||
@@ -604,45 +606,80 @@ export function agentRunExecutionPolicyWithLaneCredentials(basePolicy: Record<st
|
||||
};
|
||||
}
|
||||
|
||||
export const AGENTRUN_DEFAULT_TOOL_CREDENTIAL_IDS = new Set(["tool-github-pr-token", "tool-unidesk-ssh-token"]);
|
||||
|
||||
export function agentRunToolCredentialRefs(spec: AgentRunLaneSpec, options: { includeUnsupported?: boolean } = {}): Array<{ tool: string; purpose: string; projection: Record<string, unknown>; sourceId: string; secretRef: { namespace: string; name: string; key: string }; valuesPrinted: false }> {
|
||||
return spec.secrets
|
||||
.filter((secret) => secret.providerCredentialProfile === null && secret.id.startsWith("tool-"))
|
||||
.map((secret) => {
|
||||
const binding = agentRunToolCredentialBinding(secret.id, secret.targetRef.key);
|
||||
return {
|
||||
tool: binding.tool,
|
||||
purpose: binding.purpose,
|
||||
projection: binding.projection,
|
||||
sourceId: secret.id,
|
||||
secretRef: secret.targetRef,
|
||||
valuesPrinted: false as const,
|
||||
};
|
||||
})
|
||||
.filter((credential) => options.includeUnsupported === true || AGENTRUN_DEFAULT_TOOL_CREDENTIAL_IDS.has(credential.sourceId));
|
||||
export interface AgentRunToolCredentialRef {
|
||||
readonly tool: string;
|
||||
readonly purpose: string;
|
||||
readonly projection: Record<string, unknown>;
|
||||
readonly sourceId: string;
|
||||
readonly secretRef: { readonly namespace: string; readonly name: string; readonly keys: readonly string[] };
|
||||
readonly valuesPrinted: false;
|
||||
}
|
||||
|
||||
export function agentRunToolCredentialBinding(sourceId: string, key: string): { tool: string; purpose: string; projection: Record<string, unknown> } {
|
||||
if (sourceId === "tool-github-pr-token") {
|
||||
return {
|
||||
tool: "github",
|
||||
purpose: "github-pr",
|
||||
projection: { kind: "env", envName: key, secretKey: key },
|
||||
};
|
||||
export function agentRunToolCredentialRefs(spec: AgentRunLaneSpec): AgentRunToolCredentialRef[] {
|
||||
return spec.toolCredentials.map((credential) => ({
|
||||
tool: credential.tool,
|
||||
purpose: credential.purpose,
|
||||
projection: credential.projection,
|
||||
sourceId: credential.id,
|
||||
secretRef: credential.secretRef,
|
||||
valuesPrinted: false,
|
||||
}));
|
||||
}
|
||||
|
||||
function validateBasePolicyToolCredentials(basePolicy: Record<string, unknown>, laneCredentials: readonly AgentRunToolCredentialRef[], target: AgentRunSessionPolicyTarget): void {
|
||||
const rawCredentials = record(basePolicy.secretScope).toolCredentials;
|
||||
if (rawCredentials === undefined || rawCredentials === null) return;
|
||||
if (!Array.isArray(rawCredentials)) {
|
||||
throw new AgentRunRestError("validation-failed", "executionPolicy.secretScope.toolCredentials must be an array when lane tool credentials are enabled");
|
||||
}
|
||||
if (sourceId === "tool-unidesk-ssh-token") {
|
||||
return {
|
||||
tool: "unidesk-ssh",
|
||||
purpose: "ssh-passthrough",
|
||||
projection: { kind: "env", envName: key, secretKey: key },
|
||||
};
|
||||
const laneByIdentity = new Map(laneCredentials.map((credential) => [`${credential.tool}\0${credential.purpose}`, credential]));
|
||||
const seen = new Set<string>();
|
||||
for (const [index, rawCredential] of rawCredentials.entries()) {
|
||||
const credential = record(rawCredential);
|
||||
const tool = stringOrNull(credential.tool);
|
||||
const purpose = stringOrNull(credential.purpose);
|
||||
if (tool === null || purpose === null) {
|
||||
throw new AgentRunRestError("validation-failed", `executionPolicy.secretScope.toolCredentials[${index}] must declare tool and purpose`);
|
||||
}
|
||||
const identity = `${tool}\0${purpose}`;
|
||||
if (seen.has(identity)) {
|
||||
throw new AgentRunRestError("validation-failed", `executionPolicy.secretScope.toolCredentials contains duplicate ${tool}/${purpose} identity`);
|
||||
}
|
||||
seen.add(identity);
|
||||
const laneCredential = laneByIdentity.get(identity);
|
||||
if (laneCredential === undefined) {
|
||||
throw new AgentRunRestError("validation-failed", `executionPolicy toolCredential ${tool}/${purpose} is not declared by YAML lane ${target.spec.nodeId}/${target.spec.lane}`);
|
||||
}
|
||||
const secretRef = record(credential.secretRef);
|
||||
const secretName = stringOrNull(secretRef.name);
|
||||
const namespace = stringOrNull(secretRef.namespace) ?? target.spec.runtime.namespace;
|
||||
if (secretName !== laneCredential.secretRef.name || namespace !== laneCredential.secretRef.namespace) {
|
||||
throw new AgentRunRestError("validation-failed", `executionPolicy toolCredential ${tool}/${purpose} SecretRef conflicts with YAML lane ${target.spec.nodeId}/${target.spec.lane}`);
|
||||
}
|
||||
const keys = normalizedCredentialKeys(secretRef.keys);
|
||||
const laneKeys = normalizedCredentialKeys(laneCredential.secretRef.keys);
|
||||
if (keys === null || laneKeys === null || keys.length !== laneKeys.length || keys.some((key, keyIndex) => key !== laneKeys[keyIndex])) {
|
||||
throw new AgentRunRestError("validation-failed", `executionPolicy toolCredential ${tool}/${purpose} keys conflict with YAML lane ${target.spec.nodeId}/${target.spec.lane}`);
|
||||
}
|
||||
if (!toolCredentialProjectionMatches(record(credential.projection), laneCredential.projection)) {
|
||||
throw new AgentRunRestError("validation-failed", `executionPolicy toolCredential ${tool}/${purpose} projection conflicts with YAML lane ${target.spec.nodeId}/${target.spec.lane}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
tool: sourceId.replace(/^tool-/u, "").replace(/-token$/u, ""),
|
||||
purpose: sourceId.replace(/^tool-/u, ""),
|
||||
projection: {},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizedCredentialKeys(value: unknown): string[] | null {
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim().length === 0)) return null;
|
||||
const keys = value.map((item) => String(item)).sort();
|
||||
return new Set(keys).size === keys.length ? keys : null;
|
||||
}
|
||||
|
||||
function toolCredentialProjectionMatches(actual: Record<string, unknown>, expected: Record<string, unknown>): boolean {
|
||||
if (actual.kind !== expected.kind) return false;
|
||||
if (expected.kind === "env") {
|
||||
return actual.envName === expected.envName && actual.secretKey === expected.secretKey;
|
||||
}
|
||||
if (expected.kind === "volume") return actual.mountPath === expected.mountPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function renderAgentRunSessionPolicyExplanation(args: string[] = [], options: AgentRunRestTargetOptions = { node: null, lane: null }): string {
|
||||
@@ -660,7 +697,7 @@ export function renderAgentRunSessionPolicyExplanation(args: string[] = [], opti
|
||||
secretRef: credential.secretRef,
|
||||
valuesPrinted: false,
|
||||
}));
|
||||
const toolCredentialSources = agentRunToolCredentialRefs(spec, { includeUnsupported: true });
|
||||
const toolCredentialSources = agentRunToolCredentialRefs(spec);
|
||||
return [
|
||||
"KIND: session-policy",
|
||||
`CONFIG: ${config.sourcePath}`,
|
||||
|
||||
Reference in New Issue
Block a user