fix managed downstream deny-all semantics

This commit is contained in:
cita
2026-03-20 00:39:39 +08:00
parent 5403a946c6
commit 77d6a415d1
7 changed files with 9 additions and 17 deletions
@@ -114,10 +114,10 @@ function resolveUpstreamPath(apiVersion: string, modelActionPath: string): strin
return `/${normalizedVersion}/${normalizedAction}`; return `/${normalizedVersion}/${normalizedAction}`;
} }
function hasDownstreamModelRestrictions(policy: { supportedModels?: unknown; allowedRouteIds?: unknown }): boolean { function hasDownstreamModelRestrictions(policy: { supportedModels?: unknown; allowedRouteIds?: unknown; denyAllWhenEmpty?: unknown }): boolean {
const supportedModels = Array.isArray(policy.supportedModels) ? policy.supportedModels : []; const supportedModels = Array.isArray(policy.supportedModels) ? policy.supportedModels : [];
const allowedRouteIds = Array.isArray(policy.allowedRouteIds) ? policy.allowedRouteIds : []; const allowedRouteIds = Array.isArray(policy.allowedRouteIds) ? policy.allowedRouteIds : [];
return supportedModels.length > 0 || allowedRouteIds.length > 0; return supportedModels.length > 0 || allowedRouteIds.length > 0 || policy.denyAllWhenEmpty === true;
} }
function extractGeminiListedModelName(item: unknown): string { function extractGeminiListedModelName(item: unknown): string {
+1 -7
View File
@@ -1,16 +1,11 @@
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from 'fastify';
import { getProxyAuthContext } from '../../middleware/auth.js'; import { getProxyAuthContext } from '../../middleware/auth.js';
import { isModelAllowedByPolicyOrAllowedRoutes, recordManagedKeyCostUsage } from '../../services/downstreamApiKeyService.js'; import { isModelAllowedByPolicyOrAllowedRoutes, recordManagedKeyCostUsage } from '../../services/downstreamApiKeyService.js';
import { import { EMPTY_DOWNSTREAM_ROUTING_POLICY, type DownstreamRoutingPolicy } from '../../services/downstreamPolicyTypes.js';
EMPTY_DOWNSTREAM_ROUTING_POLICY,
GLOBAL_UNRESTRICTED_DOWNSTREAM_ROUTING_POLICY,
type DownstreamRoutingPolicy,
} from '../../services/downstreamPolicyTypes.js';
export function getDownstreamRoutingPolicy(request: FastifyRequest): DownstreamRoutingPolicy { export function getDownstreamRoutingPolicy(request: FastifyRequest): DownstreamRoutingPolicy {
const authContext = getProxyAuthContext(request); const authContext = getProxyAuthContext(request);
if (!authContext) return EMPTY_DOWNSTREAM_ROUTING_POLICY; if (!authContext) return EMPTY_DOWNSTREAM_ROUTING_POLICY;
if (authContext.keyId === null) return GLOBAL_UNRESTRICTED_DOWNSTREAM_ROUTING_POLICY;
return authContext.policy; return authContext.policy;
} }
@@ -21,7 +16,6 @@ export async function ensureModelAllowedForDownstreamKey(
): Promise<boolean> { ): Promise<boolean> {
const authContext = getProxyAuthContext(request); const authContext = getProxyAuthContext(request);
if (!authContext) return true; if (!authContext) return true;
if (authContext.keyId === null) return true;
if (await isModelAllowedByPolicyOrAllowedRoutes(requestedModel, authContext.policy)) { if (await isModelAllowedByPolicyOrAllowedRoutes(requestedModel, authContext.policy)) {
return true; return true;
@@ -141,6 +141,7 @@ describe('downstreamApiKeyService', () => {
supportedModels: [], supportedModels: [],
allowedRouteIds: [], allowedRouteIds: [],
siteWeightMultipliers: {}, siteWeightMultipliers: {},
denyAllWhenEmpty: true,
}; };
expect(await service.isModelAllowedByPolicyOrAllowedRoutes('gpt-4o-mini', policy)).toBe(false); expect(await service.isModelAllowedByPolicyOrAllowedRoutes('gpt-4o-mini', policy)).toBe(false);
@@ -234,7 +234,7 @@ export async function isModelAllowedByPolicyOrAllowedRoutes(model: string, polic
const hasPatternRules = patterns.length > 0; const hasPatternRules = patterns.length > 0;
const hasRouteRules = allowedRouteIds.length > 0; const hasRouteRules = allowedRouteIds.length > 0;
if (!hasPatternRules && !hasRouteRules) return false; if (!hasPatternRules && !hasRouteRules) return policy.denyAllWhenEmpty === true ? false : true;
if (hasPatternRules && patterns.some((pattern) => matchesDownstreamModelPattern(model, pattern))) { if (hasPatternRules && patterns.some((pattern) => matchesDownstreamModelPattern(model, pattern))) {
return true; return true;
@@ -278,6 +278,7 @@ export function toPolicyFromView(view: Pick<DownstreamApiKeyPolicyView, 'support
supportedModels: normalizeSupportedModelsInput(view.supportedModels), supportedModels: normalizeSupportedModelsInput(view.supportedModels),
allowedRouteIds: normalizeAllowedRouteIdsInput(view.allowedRouteIds), allowedRouteIds: normalizeAllowedRouteIdsInput(view.allowedRouteIds),
siteWeightMultipliers: normalizeSiteWeightMultipliersInput(view.siteWeightMultipliers), siteWeightMultipliers: normalizeSiteWeightMultipliersInput(view.siteWeightMultipliers),
denyAllWhenEmpty: true,
}; };
} }
+1 -6
View File
@@ -2,6 +2,7 @@ export interface DownstreamRoutingPolicy {
supportedModels: string[]; supportedModels: string[];
allowedRouteIds: number[]; allowedRouteIds: number[];
siteWeightMultipliers: Record<number, number>; siteWeightMultipliers: Record<number, number>;
denyAllWhenEmpty?: boolean;
} }
export const EMPTY_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = { export const EMPTY_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = {
@@ -9,9 +10,3 @@ export const EMPTY_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = {
allowedRouteIds: [], allowedRouteIds: [],
siteWeightMultipliers: {}, siteWeightMultipliers: {},
}; };
export const GLOBAL_UNRESTRICTED_DOWNSTREAM_ROUTING_POLICY: DownstreamRoutingPolicy = {
supportedModels: ['*'],
allowedRouteIds: [],
siteWeightMultipliers: {},
};
@@ -137,6 +137,7 @@ describe('TokenRouter downstream policy', () => {
allowedRouteIds: [], allowedRouteIds: [],
supportedModels: [], supportedModels: [],
siteWeightMultipliers: {}, siteWeightMultipliers: {},
denyAllWhenEmpty: true,
}); });
expect(deniedPick).toBeNull(); expect(deniedPick).toBeNull();
+1 -1
View File
@@ -537,7 +537,7 @@ function isModelAllowedByDownstreamPolicy(requestedModel: string, policy: Downst
: []; : [];
const hasSupportedPatterns = supportedPatterns.length > 0; const hasSupportedPatterns = supportedPatterns.length > 0;
const hasAllowedRoutes = policy.allowedRouteIds.length > 0; const hasAllowedRoutes = policy.allowedRouteIds.length > 0;
if (!hasSupportedPatterns && !hasAllowedRoutes) return false; if (!hasSupportedPatterns && !hasAllowedRoutes) return policy.denyAllWhenEmpty === true ? false : true;
const matchedSupportedPattern = supportedPatterns.some((pattern) => matchesModelPattern(requestedModel, pattern)); const matchedSupportedPattern = supportedPatterns.some((pattern) => matchesModelPattern(requestedModel, pattern));
if (matchedSupportedPattern) return true; if (matchedSupportedPattern) return true;
if (hasAllowedRoutes) return true; if (hasAllowedRoutes) return true;