Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 40 additions & 35 deletions framework/configstore/clientconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,41 +68,42 @@ func (c *CompatConfig) UnmarshalJSON(data []byte) error {
// ClientConfig represents the core configuration for Bifrost HTTP transport and the Bifrost Client.
// It includes settings for excess request handling, Prometheus metrics, and initial pool size.
type ClientConfig struct {
DropExcessRequests bool `json:"drop_excess_requests"` // Drop excess requests if the provider queue is full
InitialPoolSize int `json:"initial_pool_size"` // The initial pool size for the bifrost client
PrometheusLabels []string `json:"prometheus_labels"` // The labels to be used for prometheus metrics
EnableLogging *bool `json:"enable_logging"` // Enable logging of requests and responses
DisableContentLogging bool `json:"disable_content_logging"` // Disable logging of content
AllowPerRequestContentStorageOverride bool `json:"allow_per_request_content_storage_override"` // Allow per-request override of content storage via x-bf-disable-content-logging header/context
AllowPerRequestRawOverride bool `json:"allow_per_request_raw_override"` // Allow per-request override of raw request/response visibility via x-bf-send-back-raw-request and x-bf-send-back-raw-response headers
AllowDirectKeys bool `json:"allow_direct_keys"` // Allow callers to bypass the registered key pool via x-bf-direct-key: true header
DisableDBPingsInHealth bool `json:"disable_db_pings_in_health"`
LogRetentionDays int `json:"log_retention_days" validate:"min=1"` // Number of days to retain logs (minimum 1 day)
EnforceAuthOnInference bool `json:"enforce_auth_on_inference"` // Require auth (VK, API key, or user token) on inference endpoints
EnforceGovernanceHeader bool `json:"enforce_governance_header,omitempty"` // Deprecated: use EnforceAuthOnInference
EnforceSCIMAuth bool `json:"enforce_scim_auth,omitempty"` // Deprecated: use EnforceAuthOnInference
AllowedOrigins []string `json:"allowed_origins,omitempty"` // Additional allowed origins for CORS and WebSocket (localhost is always allowed)
AllowedHeaders []string `json:"allowed_headers,omitempty"` // Additional allowed headers for CORS and WebSocket
MaxRequestBodySizeMB int `json:"max_request_body_size_mb"` // The maximum request body size in MB
Compat CompatConfig `json:"compat"` // Compat plugin configuration
MCPAgentDepth int `json:"mcp_agent_depth"` // The maximum depth for MCP agent mode tool execution
MCPToolExecutionTimeout int `json:"mcp_tool_execution_timeout"` // The timeout for individual tool execution in seconds
MCPCodeModeBindingLevel string `json:"mcp_code_mode_binding_level"` // Code mode binding level: "server" or "tool"
MCPToolSyncInterval int `json:"mcp_tool_sync_interval"` // Global tool sync interval in minutes (default: 10, 0 = disabled)
MCPDisableAutoToolInject bool `json:"mcp_disable_auto_tool_inject"` // When true, MCP tools are not injected into requests by default
MCPEnableTempTokenAuth bool `json:"mcp_enable_temp_token_auth"` // When true, scoped temp tokens can authorize MCP per-user OAuth and per-user-headers auth pages. User-mode flows never mint regardless.
HeaderFilterConfig *tables.GlobalHeaderFilterConfig `json:"header_filter_config,omitempty"` // Global header filtering configuration for x-bf-eh-* headers
AsyncJobResultTTL int `json:"async_job_result_ttl"` // Default TTL for async job results in seconds (default: 3600 = 1 hour)
RequiredHeaders []string `json:"required_headers,omitempty"` // Headers that must be present on every request (case-insensitive)
LoggingHeaders []string `json:"logging_headers,omitempty"` // Headers to capture in log metadata
WhitelistedRoutes []string `json:"whitelisted_routes,omitempty"` // Routes that bypass auth middleware
HideDeletedVirtualKeysInFilters bool `json:"hide_deleted_virtual_keys_in_filters"` // Hide deleted virtual keys from logs/MCP filter data
RoutingChainMaxDepth int `json:"routing_chain_max_depth"` // Maximum depth for routing rule chain evaluation (default: 10)
MCPExternalClientURL *schemas.SecretVar `json:"mcp_external_client_url,omitempty"` // Public base URL used as redirect_uri when Bifrost acts as an OAuth client to upstream MCP servers. Supports env var syntax ("env.MY_VAR")
MCPServerAuthMode tables.MCPServerAuthMode `json:"mcp_server_auth_mode,omitempty"` // How /mcp authenticates inbound clients: headers (default), both, or oauth.
OAuth2ServerConfig *tables.OAuth2ServerConfig `json:"oauth2_server_config,omitempty"` // OAuth2 AS-specific settings (IssuerURL, token TTLs). Only relevant when MCPServerAuthMode is both or oauth.
ConfigHash string `json:"-"` // Config hash for reconciliation (not serialized)
DumpErrorsInConsoleLogs bool `json:"dump_errors_in_console_logs"` // Dump error details in console logs
DropExcessRequests bool `json:"drop_excess_requests"` // Drop excess requests if the provider queue is full
InitialPoolSize int `json:"initial_pool_size"` // The initial pool size for the bifrost client
PrometheusLabels []string `json:"prometheus_labels"` // The labels to be used for prometheus metrics
EnableLogging *bool `json:"enable_logging"` // Enable logging of requests and responses
DisableContentLogging bool `json:"disable_content_logging"` // Disable logging of content
AllowPerRequestContentStorageOverride bool `json:"allow_per_request_content_storage_override"` // Allow per-request override of content storage via x-bf-disable-content-logging header/context
AllowPerRequestRawOverride bool `json:"allow_per_request_raw_override"` // Allow per-request override of raw request/response visibility via x-bf-send-back-raw-request and x-bf-send-back-raw-response headers
AllowDirectKeys bool `json:"allow_direct_keys"` // Allow callers to bypass the registered key pool via x-bf-direct-key: true header
DisableDBPingsInHealth bool `json:"disable_db_pings_in_health"`
LogRetentionDays int `json:"log_retention_days" validate:"min=1"` // Number of days to retain logs (minimum 1 day)
EnforceAuthOnInference bool `json:"enforce_auth_on_inference"` // Require auth (VK, API key, or user token) on inference endpoints
DualCredentialConflictBehavior tables.DualCredentialConflictBehavior `json:"dual_credential_conflict_behavior,omitempty"` // Behavior when both an IDP token and a VK are present on an inference request
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment thread
greptile-apps[bot] marked this conversation as resolved.
EnforceGovernanceHeader bool `json:"enforce_governance_header,omitempty"` // Deprecated: use EnforceAuthOnInference
EnforceSCIMAuth bool `json:"enforce_scim_auth,omitempty"` // Deprecated: use EnforceAuthOnInference
AllowedOrigins []string `json:"allowed_origins,omitempty"` // Additional allowed origins for CORS and WebSocket (localhost is always allowed)
AllowedHeaders []string `json:"allowed_headers,omitempty"` // Additional allowed headers for CORS and WebSocket
MaxRequestBodySizeMB int `json:"max_request_body_size_mb"` // The maximum request body size in MB
Compat CompatConfig `json:"compat"` // Compat plugin configuration
MCPAgentDepth int `json:"mcp_agent_depth"` // The maximum depth for MCP agent mode tool execution
MCPToolExecutionTimeout int `json:"mcp_tool_execution_timeout"` // The timeout for individual tool execution in seconds
MCPCodeModeBindingLevel string `json:"mcp_code_mode_binding_level"` // Code mode binding level: "server" or "tool"
MCPToolSyncInterval int `json:"mcp_tool_sync_interval"` // Global tool sync interval in minutes (default: 10, 0 = disabled)
MCPDisableAutoToolInject bool `json:"mcp_disable_auto_tool_inject"` // When true, MCP tools are not injected into requests by default
MCPEnableTempTokenAuth bool `json:"mcp_enable_temp_token_auth"` // When true, scoped temp tokens can authorize MCP per-user OAuth and per-user-headers auth pages. User-mode flows never mint regardless.
HeaderFilterConfig *tables.GlobalHeaderFilterConfig `json:"header_filter_config,omitempty"` // Global header filtering configuration for x-bf-eh-* headers
AsyncJobResultTTL int `json:"async_job_result_ttl"` // Default TTL for async job results in seconds (default: 3600 = 1 hour)
RequiredHeaders []string `json:"required_headers,omitempty"` // Headers that must be present on every request (case-insensitive)
LoggingHeaders []string `json:"logging_headers,omitempty"` // Headers to capture in log metadata
WhitelistedRoutes []string `json:"whitelisted_routes,omitempty"` // Routes that bypass auth middleware
HideDeletedVirtualKeysInFilters bool `json:"hide_deleted_virtual_keys_in_filters"` // Hide deleted virtual keys from logs/MCP filter data
RoutingChainMaxDepth int `json:"routing_chain_max_depth"` // Maximum depth for routing rule chain evaluation (default: 10)
MCPExternalClientURL *schemas.SecretVar `json:"mcp_external_client_url,omitempty"` // Public base URL used as redirect_uri when Bifrost acts as an OAuth client to upstream MCP servers. Supports env var syntax ("env.MY_VAR")
MCPServerAuthMode tables.MCPServerAuthMode `json:"mcp_server_auth_mode,omitempty"` // How /mcp authenticates inbound clients: headers (default), both, or oauth.
OAuth2ServerConfig *tables.OAuth2ServerConfig `json:"oauth2_server_config,omitempty"` // OAuth2 AS-specific settings (IssuerURL, token TTLs). Only relevant when MCPServerAuthMode is both or oauth.
ConfigHash string `json:"-"` // Config hash for reconciliation (not serialized)
DumpErrorsInConsoleLogs bool `json:"dump_errors_in_console_logs"` // Dump error details in console logs
}

// IsMCPOAuthDiscoveryEnabled reports whether the well-known OAuth discovery
Expand Down Expand Up @@ -167,6 +168,10 @@ func (c *ClientConfig) GenerateClientConfigHash() (string, error) {
hash.Write([]byte("enforceAuthOnInference:false"))
}

if c.DualCredentialConflictBehavior != "" && c.DualCredentialConflictBehavior != tables.DualCredentialConflictBehaviorPreferIDP {
hash.Write([]byte("dualCredentialConflictBehavior:" + string(c.DualCredentialConflictBehavior)))
}

if c.Compat.ConvertTextToChat {
hash.Write([]byte("compatConvertTextToChat:true"))
}
Expand Down
31 changes: 31 additions & 0 deletions framework/configstore/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ var configstoreMigrationSteps = []migrationStep{
{IDs: []string{"add_inference_geo_multiplier_column"}, run: migrationAddInferenceGeoMultiplierColumn},
{IDs: []string{"repair_bare_wildcard_allowed_models"}, run: migrationRepairBareWildcardAllowedModels},
{IDs: []string{"add_bedrock_project_id_columns"}, run: migrationAddBedrockProjectIDColumns},
{IDs: []string{"add_dual_credential_conflict_behavior_column"}, run: migrationAddDualCredentialConflictBehaviorColumn},
}

// quoteSQLiteIdentifier quotes a SQLite identifier, escaping any double quotes.
Expand Down Expand Up @@ -5395,6 +5396,36 @@ func migrationAddEnforceAuthOnInferenceColumn(ctx context.Context, db *gorm.DB,
return nil
}

// migrationAddDualCredentialConflictBehaviorColumn adds the dual_credential_conflict_behavior
// column to the config_client table. The column is added with its gorm-defined
// NOT NULL default ('prefer_idp'), so existing rows retain the pre-feature behavior.
func migrationAddDualCredentialConflictBehaviorColumn(ctx context.Context, db *gorm.DB, logger schemas.Logger) error {
migrationName := "add_dual_credential_conflict_behavior_column"
logger.Info("[configstore] starting migration %s", migrationName)
defer logger.Info("[configstore] finished migration %s", migrationName)
m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
ID: migrationName,
Migrate: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
if err := addColumnIfNotExists(tx, logger, &tables.TableClientConfig{}, "dual_credential_conflict_behavior"); err != nil {
return err
}
return nil
},
Rollback: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
if err := dropColumnIfExists(tx, logger, &tables.TableClientConfig{}, "dual_credential_conflict_behavior"); err != nil {
return err
}
return nil
},
}})
if err := m.Migrate(); err != nil {
return fmt.Errorf("error running dual credential conflict behavior column migration: %s", err.Error())
}
return nil
}

Comment thread
BearTS marked this conversation as resolved.
func migrationReconcilePricingOverridesTable(ctx context.Context, db *gorm.DB, logger schemas.Logger) error {
migrationName := "reconcile_pricing_overrides_table"
logger.Info("[configstore] starting migration %s", migrationName)
Expand Down
36 changes: 36 additions & 0 deletions framework/configstore/migrations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2767,3 +2767,39 @@ func TestFullMigration_UpgradeFromPreDumpErrorsSchema(t *testing.T) {
assert.NotEmpty(t, gotHash)
assert.NotEqual(t, "stale-hash", gotHash, "config_hash should have been recomputed by the chain")
}

// TestMigrationAddDualCredentialConflictBehaviorColumn verifies that upgrading a
// config_client that predates dual_credential_conflict_behavior re-adds the column
// and backfills existing rows with the NOT NULL default ('prefer_idp'). Without the
// migration, an upgraded deployment would carry a struct column the physical table
// lacks and fail the config save/sync path.
func TestMigrationAddDualCredentialConflictBehaviorColumn(t *testing.T) {
db := setupTestDB(t)
ctx := context.Background()

// Build the current schema, seed a row, then drop the column to simulate a
// pre-feature config_client.
require.NoError(t, db.AutoMigrate(&tables.TableClientConfig{}))
seed := &tables.TableClientConfig{ConfigHash: "seed-hash"}
require.NoError(t, db.Create(seed).Error)

require.NoError(t, db.Migrator().DropColumn(&tables.TableClientConfig{}, "dual_credential_conflict_behavior"))
require.False(t, db.Migrator().HasColumn(&tables.TableClientConfig{}, "dual_credential_conflict_behavior"),
"precondition: dual_credential_conflict_behavior must be absent to reproduce the upgrade path")

require.NoError(t, migrationAddDualCredentialConflictBehaviorColumn(ctx, db, testMigrationLogger),
"migration should re-add the column")

require.True(t, db.Migrator().HasColumn(&tables.TableClientConfig{}, "dual_credential_conflict_behavior"),
"migration should have added dual_credential_conflict_behavior")

// The pre-existing row must be backfilled with the prefer_idp default so upgraded
// deployments retain the pre-feature behavior.
var got string
require.NoError(t, db.Raw("SELECT dual_credential_conflict_behavior FROM config_client WHERE id = ?", seed.ID).Scan(&got).Error)
assert.Equal(t, "prefer_idp", got, "existing rows should default to prefer_idp after migration")

// Idempotency: re-running the migration is a no-op and must not error.
require.NoError(t, migrationAddDualCredentialConflictBehaviorColumn(ctx, db, testMigrationLogger),
"re-running the migration should be idempotent")
}
Loading
Loading