ccm,ocm: normalize legacy fields into credentials at init, remove dual code path
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
+30
-30
@@ -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))
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+33
-35
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user