From 8e9c61e6247bfa52009d180c17a4b10ae5740fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 15 Mar 2026 12:24:46 +0800 Subject: [PATCH] ccm,ocm: normalize legacy fields into credentials at init, remove dual code path --- service/ccm/credential_builder.go | 150 ++++++++++++------------------ service/ccm/service.go | 60 ++++++------ service/ccm/service_handler.go | 4 +- service/ccm/service_status.go | 73 ++++++++------- service/ocm/credential_builder.go | 150 ++++++++++++------------------ service/ocm/service.go | 68 +++++++------- service/ocm/service_handler.go | 8 +- service/ocm/service_status.go | 73 ++++++++------- 8 files changed, 265 insertions(+), 321 deletions(-) diff --git a/service/ccm/credential_builder.go b/service/ccm/credential_builder.go index 63bfd0395..94e0af957 100644 --- a/service/ccm/credential_builder.go +++ b/service/ccm/credential_builder.go @@ -71,84 +71,68 @@ func resolveCredentialTags(tags []string, allCredentials map[string]Credential, } func validateCCMOptions(options option.CCMServiceOptions) error { - hasCredentials := len(options.Credentials) > 0 - hasLegacyPath := options.CredentialPath != "" - hasLegacyUsages := options.UsagesPath != "" - hasLegacyDetour := options.Detour != "" - - if hasCredentials && hasLegacyPath { - return E.New("credential_path and credentials are mutually exclusive") - } - if hasCredentials && hasLegacyUsages { - return E.New("usages_path and credentials are mutually exclusive; use usages_path on individual credentials") - } - if hasCredentials && hasLegacyDetour { - return E.New("detour and credentials are mutually exclusive; use detour on individual credentials") - } - - if hasCredentials { - tags := make(map[string]bool) - credentialTypes := make(map[string]string) - for _, credential := range options.Credentials { - if tags[credential.Tag] { - return E.New("duplicate credential tag: ", credential.Tag) + tags := make(map[string]bool) + credentialTypes := make(map[string]string) + for _, credential := range options.Credentials { + if tags[credential.Tag] { + return E.New("duplicate credential tag: ", credential.Tag) + } + tags[credential.Tag] = true + credentialTypes[credential.Tag] = credential.Type + if credential.Type == "default" || credential.Type == "" { + if credential.DefaultOptions.Reserve5h > 99 { + return E.New("credential ", credential.Tag, ": reserve_5h must be at most 99") } - tags[credential.Tag] = true - credentialTypes[credential.Tag] = credential.Type - if credential.Type == "default" || credential.Type == "" { - if credential.DefaultOptions.Reserve5h > 99 { - return E.New("credential ", credential.Tag, ": reserve_5h must be at most 99") - } - if credential.DefaultOptions.ReserveWeekly > 99 { - return E.New("credential ", credential.Tag, ": reserve_weekly must be at most 99") - } - if credential.DefaultOptions.Limit5h > 100 { - return E.New("credential ", credential.Tag, ": limit_5h must be at most 100") - } - if credential.DefaultOptions.LimitWeekly > 100 { - return E.New("credential ", credential.Tag, ": limit_weekly must be at most 100") - } - if credential.DefaultOptions.Reserve5h > 0 && credential.DefaultOptions.Limit5h > 0 { - return E.New("credential ", credential.Tag, ": reserve_5h and limit_5h are mutually exclusive") - } - if credential.DefaultOptions.ReserveWeekly > 0 && credential.DefaultOptions.LimitWeekly > 0 { - return E.New("credential ", credential.Tag, ": reserve_weekly and limit_weekly are mutually exclusive") - } + if credential.DefaultOptions.ReserveWeekly > 99 { + return E.New("credential ", credential.Tag, ": reserve_weekly must be at most 99") } - if credential.Type == "external" { - if credential.ExternalOptions.Token == "" { - return E.New("credential ", credential.Tag, ": external credential requires token") - } - if credential.ExternalOptions.Reverse && credential.ExternalOptions.URL == "" { - return E.New("credential ", credential.Tag, ": reverse external credential requires url") - } + if credential.DefaultOptions.Limit5h > 100 { + return E.New("credential ", credential.Tag, ": limit_5h must be at most 100") } - if credential.Type == "balancer" { - switch credential.BalancerOptions.Strategy { - case "", C.BalancerStrategyLeastUsed, C.BalancerStrategyRoundRobin, C.BalancerStrategyRandom, C.BalancerStrategyFallback: - default: - return E.New("credential ", credential.Tag, ": unknown balancer strategy: ", credential.BalancerOptions.Strategy) - } - if credential.BalancerOptions.RebalanceThreshold < 0 { - return E.New("credential ", credential.Tag, ": rebalance_threshold must not be negative") - } + if credential.DefaultOptions.LimitWeekly > 100 { + return E.New("credential ", credential.Tag, ": limit_weekly must be at most 100") + } + if credential.DefaultOptions.Reserve5h > 0 && credential.DefaultOptions.Limit5h > 0 { + return E.New("credential ", credential.Tag, ": reserve_5h and limit_5h are mutually exclusive") + } + if credential.DefaultOptions.ReserveWeekly > 0 && credential.DefaultOptions.LimitWeekly > 0 { + return E.New("credential ", credential.Tag, ": reserve_weekly and limit_weekly are mutually exclusive") } } + if credential.Type == "external" { + if credential.ExternalOptions.Token == "" { + return E.New("credential ", credential.Tag, ": external credential requires token") + } + if credential.ExternalOptions.Reverse && credential.ExternalOptions.URL == "" { + return E.New("credential ", credential.Tag, ": reverse external credential requires url") + } + } + if credential.Type == "balancer" { + switch credential.BalancerOptions.Strategy { + case "", C.BalancerStrategyLeastUsed, C.BalancerStrategyRoundRobin, C.BalancerStrategyRandom, C.BalancerStrategyFallback: + default: + return E.New("credential ", credential.Tag, ": unknown balancer strategy: ", credential.BalancerOptions.Strategy) + } + if credential.BalancerOptions.RebalanceThreshold < 0 { + return E.New("credential ", credential.Tag, ": rebalance_threshold must not be negative") + } + } + } - for _, user := range options.Users { - if user.Credential == "" { - return E.New("user ", user.Name, " must specify credential in multi-credential mode") + singleCredential := len(options.Credentials) == 1 + for _, user := range options.Users { + if user.Credential == "" && !singleCredential { + return E.New("user ", user.Name, " must specify credential in multi-credential mode") + } + if user.Credential != "" && !tags[user.Credential] { + return E.New("user ", user.Name, " references unknown credential: ", user.Credential) + } + if user.ExternalCredential != "" { + if !tags[user.ExternalCredential] { + return E.New("user ", user.Name, " references unknown external_credential: ", user.ExternalCredential) } - if !tags[user.Credential] { - return E.New("user ", user.Name, " references unknown credential: ", user.Credential) - } - if user.ExternalCredential != "" { - if !tags[user.ExternalCredential] { - return E.New("user ", user.Name, " references unknown external_credential: ", user.ExternalCredential) - } - if credentialTypes[user.ExternalCredential] != "external" { - return E.New("user ", user.Name, ": external_credential must reference an external type credential") - } + if credentialTypes[user.ExternalCredential] != "external" { + return E.New("user ", user.Name, ": external_credential must reference an external type credential") } } } @@ -159,16 +143,18 @@ func validateCCMOptions(options option.CCMServiceOptions) error { func credentialForUser( userConfigMap map[string]*option.CCMUser, providers map[string]credentialProvider, - legacyProvider credentialProvider, username string, ) (credentialProvider, error) { - if legacyProvider != nil { - return legacyProvider, nil - } userConfig, exists := userConfigMap[username] if !exists { return nil, E.New("no credential mapping for user: ", username) } + if userConfig.Credential == "" { + for _, provider := range providers { + return provider, nil + } + return nil, E.New("no credential available") + } provider, exists := providers[userConfig.Credential] if !exists { return nil, E.New("unknown credential: ", userConfig.Credential) @@ -176,17 +162,3 @@ func credentialForUser( return provider, nil } -func noUserCredentialProvider( - providers map[string]credentialProvider, - legacyProvider credentialProvider, - options option.CCMServiceOptions, -) credentialProvider { - if legacyProvider != nil { - return legacyProvider - } - if len(options.Credentials) > 0 { - tag := options.Credentials[0].Tag - return providers[tag] - } - return nil -} diff --git a/service/ccm/service.go b/service/ccm/service.go index 043a147c1..74952173c 100644 --- a/service/ccm/service.go +++ b/service/ccm/service.go @@ -161,11 +161,6 @@ type Service struct { trackingGroup sync.WaitGroup shuttingDown bool - // Legacy mode (single credential) - legacyCredential *defaultCredential - legacyProvider credentialProvider - - // Multi-credential mode providers map[string]credentialProvider allCredentials []Credential userConfigMap map[string]*option.CCMUser @@ -174,6 +169,25 @@ type Service struct { func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) { initCCMUserAgent(logger) + hasLegacy := options.CredentialPath != "" || options.UsagesPath != "" || options.Detour != "" + if hasLegacy && len(options.Credentials) > 0 { + return nil, E.New("credential_path/usages_path/detour and credentials are mutually exclusive") + } + if len(options.Credentials) == 0 { + options.Credentials = []option.CCMCredential{{ + Type: "default", + Tag: "default", + DefaultOptions: option.CCMDefaultCredentialOptions{ + CredentialPath: options.CredentialPath, + UsagesPath: options.UsagesPath, + Detour: options.Detour, + }, + }} + options.CredentialPath = "" + options.UsagesPath = "" + options.Detour = "" + } + err := validateCCMOptions(options) if err != nil { return nil, E.Cause(err, "validate options") @@ -198,32 +212,18 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio userManager: userManager, } - if len(options.Credentials) > 0 { - providers, allCredentials, err := buildCredentialProviders(ctx, options, logger) - if err != nil { - return nil, E.Cause(err, "build credential providers") - } - service.providers = providers - service.allCredentials = allCredentials - - userConfigMap := make(map[string]*option.CCMUser) - for i := range options.Users { - userConfigMap[options.Users[i].Name] = &options.Users[i] - } - service.userConfigMap = userConfigMap - } else { - credential, err := newDefaultCredential(ctx, "default", option.CCMDefaultCredentialOptions{ - CredentialPath: options.CredentialPath, - UsagesPath: options.UsagesPath, - Detour: options.Detour, - }, logger) - if err != nil { - return nil, err - } - service.legacyCredential = credential - service.legacyProvider = &singleCredentialProvider{credential: credential} - service.allCredentials = []Credential{credential} + providers, allCredentials, err := buildCredentialProviders(ctx, options, logger) + if err != nil { + return nil, E.Cause(err, "build credential providers") } + service.providers = providers + service.allCredentials = allCredentials + + userConfigMap := make(map[string]*option.CCMUser) + for i := range options.Users { + userConfigMap[options.Users[i].Name] = &options.Users[i] + } + service.userConfigMap = userConfigMap if options.TLS != nil { tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) diff --git a/service/ccm/service_handler.go b/service/ccm/service_handler.go index 14ae3adeb..fdbb68203 100644 --- a/service/ccm/service_handler.go +++ b/service/ccm/service_handler.go @@ -168,14 +168,14 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { if len(s.options.Users) > 0 { userConfig = s.userConfigMap[username] var err error - provider, err = credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username) + provider, err = credentialForUser(s.userConfigMap, s.providers, username) if err != nil { s.logger.ErrorContext(ctx, "resolve credential: ", err) writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error()) return } } else { - provider = noUserCredentialProvider(s.providers, s.legacyProvider, s.options) + provider = s.providers[s.options.Credentials[0].Tag] } if provider == nil { writeJSONError(w, r, http.StatusInternalServerError, "api_error", "no credential available") diff --git a/service/ccm/service_status.go b/service/ccm/service_status.go index cfc8c7635..bd8aa4b22 100644 --- a/service/ccm/service_status.go +++ b/service/ccm/service_status.go @@ -15,42 +15,43 @@ func (s *Service) handleStatusEndpoint(w http.ResponseWriter, r *http.Request) { return } - if len(s.options.Users) == 0 { - writeJSONError(w, r, http.StatusForbidden, "authentication_error", "status endpoint requires user authentication") - return - } + var provider credentialProvider + var userConfig *option.CCMUser + if len(s.options.Users) > 0 { + if r.Header.Get("X-Api-Key") != "" || r.Header.Get("Api-Key") != "" { + writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error", + "API key authentication is not supported; use Authorization: Bearer with a CCM user token") + return + } - if r.Header.Get("X-Api-Key") != "" || r.Header.Get("Api-Key") != "" { - writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error", - "API key authentication is not supported; use Authorization: Bearer with a CCM user token") - return - } + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key") + return + } + clientToken := strings.TrimPrefix(authHeader, "Bearer ") + if clientToken == authHeader { + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format") + return + } + username, ok := s.userManager.Authenticate(clientToken) + if !ok { + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key") + return + } - authHeader := r.Header.Get("Authorization") - if authHeader == "" { - writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key") - return + userConfig = s.userConfigMap[username] + var err error + provider, err = credentialForUser(s.userConfigMap, s.providers, username) + if err != nil { + writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error()) + return + } + } else { + provider = s.providers[s.options.Credentials[0].Tag] } - clientToken := strings.TrimPrefix(authHeader, "Bearer ") - if clientToken == authHeader { - writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format") - return - } - username, ok := s.userManager.Authenticate(clientToken) - if !ok { - writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key") - return - } - - userConfig := s.userConfigMap[username] - if userConfig == nil { - writeJSONError(w, r, http.StatusInternalServerError, "api_error", "user config not found") - return - } - - provider, err := credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username) - if err != nil { - writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error()) + if provider == nil { + writeJSONError(w, r, http.StatusInternalServerError, "api_error", "no credential available") return } @@ -72,10 +73,10 @@ func (s *Service) computeAggregatedUtilization(provider credentialProvider, user if !credential.isAvailable() { continue } - if userConfig.ExternalCredential != "" && credential.tagName() == userConfig.ExternalCredential { + if userConfig != nil && userConfig.ExternalCredential != "" && credential.tagName() == userConfig.ExternalCredential { continue } - if !userConfig.AllowExternalUsage && credential.isExternal() { + if userConfig != nil && !userConfig.AllowExternalUsage && credential.isExternal() { continue } weight := credential.planWeight() @@ -100,7 +101,7 @@ func (s *Service) computeAggregatedUtilization(provider credentialProvider, user } func (s *Service) rewriteResponseHeadersForExternalUser(headers http.Header, userConfig *option.CCMUser) { - provider, err := credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, userConfig.Name) + provider, err := credentialForUser(s.userConfigMap, s.providers, userConfig.Name) if err != nil { return } diff --git a/service/ocm/credential_builder.go b/service/ocm/credential_builder.go index e308d04d1..c800e039d 100644 --- a/service/ocm/credential_builder.go +++ b/service/ocm/credential_builder.go @@ -71,84 +71,68 @@ func resolveCredentialTags(tags []string, allCredentials map[string]Credential, } func validateOCMOptions(options option.OCMServiceOptions) error { - hasCredentials := len(options.Credentials) > 0 - hasLegacyPath := options.CredentialPath != "" - hasLegacyUsages := options.UsagesPath != "" - hasLegacyDetour := options.Detour != "" - - if hasCredentials && hasLegacyPath { - return E.New("credential_path and credentials are mutually exclusive") - } - if hasCredentials && hasLegacyUsages { - return E.New("usages_path and credentials are mutually exclusive; use usages_path on individual credentials") - } - if hasCredentials && hasLegacyDetour { - return E.New("detour and credentials are mutually exclusive; use detour on individual credentials") - } - - if hasCredentials { - tags := make(map[string]bool) - credentialTypes := make(map[string]string) - for _, credential := range options.Credentials { - if tags[credential.Tag] { - return E.New("duplicate credential tag: ", credential.Tag) + tags := make(map[string]bool) + credentialTypes := make(map[string]string) + for _, credential := range options.Credentials { + if tags[credential.Tag] { + return E.New("duplicate credential tag: ", credential.Tag) + } + tags[credential.Tag] = true + credentialTypes[credential.Tag] = credential.Type + if credential.Type == "default" || credential.Type == "" { + if credential.DefaultOptions.Reserve5h > 99 { + return E.New("credential ", credential.Tag, ": reserve_5h must be at most 99") } - tags[credential.Tag] = true - credentialTypes[credential.Tag] = credential.Type - if credential.Type == "default" || credential.Type == "" { - if credential.DefaultOptions.Reserve5h > 99 { - return E.New("credential ", credential.Tag, ": reserve_5h must be at most 99") - } - if credential.DefaultOptions.ReserveWeekly > 99 { - return E.New("credential ", credential.Tag, ": reserve_weekly must be at most 99") - } - if credential.DefaultOptions.Limit5h > 100 { - return E.New("credential ", credential.Tag, ": limit_5h must be at most 100") - } - if credential.DefaultOptions.LimitWeekly > 100 { - return E.New("credential ", credential.Tag, ": limit_weekly must be at most 100") - } - if credential.DefaultOptions.Reserve5h > 0 && credential.DefaultOptions.Limit5h > 0 { - return E.New("credential ", credential.Tag, ": reserve_5h and limit_5h are mutually exclusive") - } - if credential.DefaultOptions.ReserveWeekly > 0 && credential.DefaultOptions.LimitWeekly > 0 { - return E.New("credential ", credential.Tag, ": reserve_weekly and limit_weekly are mutually exclusive") - } + if credential.DefaultOptions.ReserveWeekly > 99 { + return E.New("credential ", credential.Tag, ": reserve_weekly must be at most 99") } - if credential.Type == "external" { - if credential.ExternalOptions.Token == "" { - return E.New("credential ", credential.Tag, ": external credential requires token") - } - if credential.ExternalOptions.Reverse && credential.ExternalOptions.URL == "" { - return E.New("credential ", credential.Tag, ": reverse external credential requires url") - } + if credential.DefaultOptions.Limit5h > 100 { + return E.New("credential ", credential.Tag, ": limit_5h must be at most 100") } - if credential.Type == "balancer" { - switch credential.BalancerOptions.Strategy { - case "", C.BalancerStrategyLeastUsed, C.BalancerStrategyRoundRobin, C.BalancerStrategyRandom, C.BalancerStrategyFallback: - default: - return E.New("credential ", credential.Tag, ": unknown balancer strategy: ", credential.BalancerOptions.Strategy) - } - if credential.BalancerOptions.RebalanceThreshold < 0 { - return E.New("credential ", credential.Tag, ": rebalance_threshold must not be negative") - } + if credential.DefaultOptions.LimitWeekly > 100 { + return E.New("credential ", credential.Tag, ": limit_weekly must be at most 100") + } + if credential.DefaultOptions.Reserve5h > 0 && credential.DefaultOptions.Limit5h > 0 { + return E.New("credential ", credential.Tag, ": reserve_5h and limit_5h are mutually exclusive") + } + if credential.DefaultOptions.ReserveWeekly > 0 && credential.DefaultOptions.LimitWeekly > 0 { + return E.New("credential ", credential.Tag, ": reserve_weekly and limit_weekly are mutually exclusive") } } + if credential.Type == "external" { + if credential.ExternalOptions.Token == "" { + return E.New("credential ", credential.Tag, ": external credential requires token") + } + if credential.ExternalOptions.Reverse && credential.ExternalOptions.URL == "" { + return E.New("credential ", credential.Tag, ": reverse external credential requires url") + } + } + if credential.Type == "balancer" { + switch credential.BalancerOptions.Strategy { + case "", C.BalancerStrategyLeastUsed, C.BalancerStrategyRoundRobin, C.BalancerStrategyRandom, C.BalancerStrategyFallback: + default: + return E.New("credential ", credential.Tag, ": unknown balancer strategy: ", credential.BalancerOptions.Strategy) + } + if credential.BalancerOptions.RebalanceThreshold < 0 { + return E.New("credential ", credential.Tag, ": rebalance_threshold must not be negative") + } + } + } - for _, user := range options.Users { - if user.Credential == "" { - return E.New("user ", user.Name, " must specify credential in multi-credential mode") + singleCredential := len(options.Credentials) == 1 + for _, user := range options.Users { + if user.Credential == "" && !singleCredential { + return E.New("user ", user.Name, " must specify credential in multi-credential mode") + } + if user.Credential != "" && !tags[user.Credential] { + return E.New("user ", user.Name, " references unknown credential: ", user.Credential) + } + if user.ExternalCredential != "" { + if !tags[user.ExternalCredential] { + return E.New("user ", user.Name, " references unknown external_credential: ", user.ExternalCredential) } - if !tags[user.Credential] { - return E.New("user ", user.Name, " references unknown credential: ", user.Credential) - } - if user.ExternalCredential != "" { - if !tags[user.ExternalCredential] { - return E.New("user ", user.Name, " references unknown external_credential: ", user.ExternalCredential) - } - if credentialTypes[user.ExternalCredential] != "external" { - return E.New("user ", user.Name, ": external_credential must reference an external type credential") - } + if credentialTypes[user.ExternalCredential] != "external" { + return E.New("user ", user.Name, ": external_credential must reference an external type credential") } } } @@ -190,16 +174,18 @@ func validateOCMCompositeCredentialModes( func credentialForUser( userConfigMap map[string]*option.OCMUser, providers map[string]credentialProvider, - legacyProvider credentialProvider, username string, ) (credentialProvider, error) { - if legacyProvider != nil { - return legacyProvider, nil - } userConfig, exists := userConfigMap[username] if !exists { return nil, E.New("no credential mapping for user: ", username) } + if userConfig.Credential == "" { + for _, provider := range providers { + return provider, nil + } + return nil, E.New("no credential available") + } provider, exists := providers[userConfig.Credential] if !exists { return nil, E.New("unknown credential: ", userConfig.Credential) @@ -207,17 +193,3 @@ func credentialForUser( return provider, nil } -func noUserCredentialProvider( - providers map[string]credentialProvider, - legacyProvider credentialProvider, - options option.OCMServiceOptions, -) credentialProvider { - if legacyProvider != nil { - return legacyProvider - } - if len(options.Credentials) > 0 { - tag := options.Credentials[0].Tag - return providers[tag] - } - return nil -} diff --git a/service/ocm/service.go b/service/ocm/service.go index 289152b64..641872e5d 100644 --- a/service/ocm/service.go +++ b/service/ocm/service.go @@ -176,17 +176,31 @@ type Service struct { webSocketConns map[*webSocketSession]struct{} shuttingDown bool - // Legacy mode - legacyCredential *defaultCredential - legacyProvider credentialProvider - - // Multi-credential mode providers map[string]credentialProvider allCredentials []Credential userConfigMap map[string]*option.OCMUser } func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) { + hasLegacy := options.CredentialPath != "" || options.UsagesPath != "" || options.Detour != "" + if hasLegacy && len(options.Credentials) > 0 { + return nil, E.New("credential_path/usages_path/detour and credentials are mutually exclusive") + } + if len(options.Credentials) == 0 { + options.Credentials = []option.OCMCredential{{ + Type: "default", + Tag: "default", + DefaultOptions: option.OCMDefaultCredentialOptions{ + CredentialPath: options.CredentialPath, + UsagesPath: options.UsagesPath, + Detour: options.Detour, + }, + }} + options.CredentialPath = "" + options.UsagesPath = "" + options.Detour = "" + } + err := validateOCMOptions(options) if err != nil { return nil, E.Cause(err, "validate options") @@ -212,32 +226,18 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio webSocketConns: make(map[*webSocketSession]struct{}), } - if len(options.Credentials) > 0 { - providers, allCredentials, err := buildOCMCredentialProviders(ctx, options, logger) - if err != nil { - return nil, E.Cause(err, "build credential providers") - } - service.providers = providers - service.allCredentials = allCredentials - - userConfigMap := make(map[string]*option.OCMUser) - for i := range options.Users { - userConfigMap[options.Users[i].Name] = &options.Users[i] - } - service.userConfigMap = userConfigMap - } else { - credential, err := newDefaultCredential(ctx, "default", option.OCMDefaultCredentialOptions{ - CredentialPath: options.CredentialPath, - UsagesPath: options.UsagesPath, - Detour: options.Detour, - }, logger) - if err != nil { - return nil, err - } - service.legacyCredential = credential - service.legacyProvider = &singleCredentialProvider{credential: credential} - service.allCredentials = []Credential{credential} + providers, allCredentials, err := buildOCMCredentialProviders(ctx, options, logger) + if err != nil { + return nil, E.Cause(err, "build credential providers") } + service.providers = providers + service.allCredentials = allCredentials + + userConfigMap := make(map[string]*option.OCMUser) + for i := range options.Users { + userConfigMap[options.Users[i].Name] = &options.Users[i] + } + service.userConfigMap = userConfigMap if options.TLS != nil { tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) @@ -270,11 +270,9 @@ func (s *Service) Start(stage adapter.StartStage) error { s.interruptWebSocketSessionsForCredential(tag) }) } - if len(s.options.Credentials) > 0 { - err := validateOCMCompositeCredentialModes(s.options, s.providers) - if err != nil { - return E.Cause(err, "validate loaded credentials") - } + err := validateOCMCompositeCredentialModes(s.options, s.providers) + if err != nil { + return E.Cause(err, "validate loaded credentials") } router := chi.NewRouter() diff --git a/service/ocm/service_handler.go b/service/ocm/service_handler.go index 905b1f5ae..7c9242f5a 100644 --- a/service/ocm/service_handler.go +++ b/service/ocm/service_handler.go @@ -53,9 +53,9 @@ func extractWeeklyCycleHint(headers http.Header) *WeeklyCycleHint { func (s *Service) resolveCredentialProvider(username string) (credentialProvider, error) { if len(s.options.Users) > 0 { - return credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username) + return credentialForUser(s.userConfigMap, s.providers, username) } - provider := noUserCredentialProvider(s.providers, s.legacyProvider, s.options) + provider := s.providers[s.options.Credentials[0].Tag] if provider == nil { return nil, E.New("no credential available") } @@ -117,14 +117,14 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { if len(s.options.Users) > 0 { userConfig = s.userConfigMap[username] var err error - provider, err = credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username) + provider, err = credentialForUser(s.userConfigMap, s.providers, username) if err != nil { s.logger.ErrorContext(ctx, "resolve credential: ", err) writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error()) return } } else { - provider = noUserCredentialProvider(s.providers, s.legacyProvider, s.options) + provider = s.providers[s.options.Credentials[0].Tag] } if provider == nil { writeJSONError(w, r, http.StatusInternalServerError, "api_error", "no credential available") diff --git a/service/ocm/service_status.go b/service/ocm/service_status.go index e32d8244e..327d3a2da 100644 --- a/service/ocm/service_status.go +++ b/service/ocm/service_status.go @@ -15,42 +15,43 @@ func (s *Service) handleStatusEndpoint(w http.ResponseWriter, r *http.Request) { return } - if len(s.options.Users) == 0 { - writeJSONError(w, r, http.StatusForbidden, "authentication_error", "status endpoint requires user authentication") - return - } + var provider credentialProvider + var userConfig *option.OCMUser + if len(s.options.Users) > 0 { + if r.Header.Get("X-Api-Key") != "" || r.Header.Get("Api-Key") != "" { + writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error", + "API key authentication is not supported; use Authorization: Bearer with an OCM user token") + return + } - if r.Header.Get("X-Api-Key") != "" || r.Header.Get("Api-Key") != "" { - writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error", - "API key authentication is not supported; use Authorization: Bearer with an OCM user token") - return - } + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key") + return + } + clientToken := strings.TrimPrefix(authHeader, "Bearer ") + if clientToken == authHeader { + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format") + return + } + username, ok := s.userManager.Authenticate(clientToken) + if !ok { + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key") + return + } - authHeader := r.Header.Get("Authorization") - if authHeader == "" { - writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key") - return + userConfig = s.userConfigMap[username] + var err error + provider, err = credentialForUser(s.userConfigMap, s.providers, username) + if err != nil { + writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error()) + return + } + } else { + provider = s.providers[s.options.Credentials[0].Tag] } - clientToken := strings.TrimPrefix(authHeader, "Bearer ") - if clientToken == authHeader { - writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format") - return - } - username, ok := s.userManager.Authenticate(clientToken) - if !ok { - writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key") - return - } - - userConfig := s.userConfigMap[username] - if userConfig == nil { - writeJSONError(w, r, http.StatusInternalServerError, "api_error", "user config not found") - return - } - - provider, err := credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, username) - if err != nil { - writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error()) + if provider == nil { + writeJSONError(w, r, http.StatusInternalServerError, "api_error", "no credential available") return } @@ -72,10 +73,10 @@ func (s *Service) computeAggregatedUtilization(provider credentialProvider, user if !credential.isAvailable() { continue } - if userConfig.ExternalCredential != "" && credential.tagName() == userConfig.ExternalCredential { + if userConfig != nil && userConfig.ExternalCredential != "" && credential.tagName() == userConfig.ExternalCredential { continue } - if !userConfig.AllowExternalUsage && credential.isExternal() { + if userConfig != nil && !userConfig.AllowExternalUsage && credential.isExternal() { continue } weight := credential.planWeight() @@ -100,7 +101,7 @@ func (s *Service) computeAggregatedUtilization(provider credentialProvider, user } func (s *Service) rewriteResponseHeadersForExternalUser(headers http.Header, userConfig *option.OCMUser) { - provider, err := credentialForUser(s.userConfigMap, s.providers, s.legacyProvider, userConfig.Name) + provider, err := credentialForUser(s.userConfigMap, s.providers, userConfig.Name) if err != nil { return }