2020-05-05 09:53:42 +02:00
package main
import (
2023-09-20 15:04:52 +02:00
"bytes"
2021-04-02 21:14:53 +02:00
"encoding/base64"
2020-05-05 09:53:42 +02:00
"flag"
"fmt"
2023-11-03 12:04:17 +01:00
"net/http"
2020-05-05 09:53:42 +02:00
"net/url"
2021-05-21 15:34:03 +02:00
"os"
2021-03-05 17:21:11 +01:00
"regexp"
2021-05-29 00:00:23 +02:00
"strconv"
2020-05-05 09:53:42 +02:00
"strings"
"sync"
"sync/atomic"
2023-03-23 09:34:12 +01:00
"time"
2020-05-05 09:53:42 +02:00
2023-11-03 12:04:17 +01:00
"github.com/VictoriaMetrics/metrics"
"gopkg.in/yaml.v2"
2020-08-13 15:43:55 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
2023-02-11 09:27:40 +01:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
2023-12-08 22:27:53 +01:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
2021-12-02 22:32:03 +01:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
2020-05-05 09:53:42 +02:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
)
var (
2021-12-02 23:08:42 +01:00
authConfigPath = flag . String ( "auth.config" , "" , "Path to auth config. It can point either to local file or to http url. " +
"See https://docs.victoriametrics.com/vmauth.html for details on the format of this auth config" )
2023-03-23 09:34:12 +01:00
configCheckInterval = flag . Duration ( "configCheckInterval" , 0 , "interval for config file re-read. " +
"Zero value disables config re-reading. By default, refreshing is disabled, send SIGHUP for config refresh." )
2023-12-08 22:27:53 +01:00
defaultRetryStatusCodes = flagutil . NewArrayInt ( "retryStatusCodes" , 0 , "Comma-separated list of default HTTP response status codes when vmauth re-tries the request on other backends. " +
"See https://docs.victoriametrics.com/vmauth.html#load-balancing for details" )
defaultLoadBalancingPolicy = flag . String ( "loadBalancingPolicy" , "least_loaded" , "The default load balancing policy to use for backend urls specified inside url_prefix section. " +
"Supported policies: least_loaded, first_available. See https://docs.victoriametrics.com/vmauth.html#load-balancing for more details" )
2020-05-05 09:53:42 +02:00
)
// AuthConfig represents auth config.
type AuthConfig struct {
2023-04-24 14:57:13 +02:00
Users [ ] UserInfo ` yaml:"users,omitempty" `
UnauthorizedUser * UserInfo ` yaml:"unauthorized_user,omitempty" `
2020-05-05 09:53:42 +02:00
}
// UserInfo is user information read from authConfigPath
type UserInfo struct {
2023-11-13 22:30:39 +01:00
Name string ` yaml:"name,omitempty" `
BearerToken string ` yaml:"bearer_token,omitempty" `
Username string ` yaml:"username,omitempty" `
Password string ` yaml:"password,omitempty" `
URLPrefix * URLPrefix ` yaml:"url_prefix,omitempty" `
URLMaps [ ] URLMap ` yaml:"url_map,omitempty" `
HeadersConf HeadersConf ` yaml:",inline" `
MaxConcurrentRequests int ` yaml:"max_concurrent_requests,omitempty" `
DefaultURL * URLPrefix ` yaml:"default_url,omitempty" `
RetryStatusCodes [ ] int ` yaml:"retry_status_codes,omitempty" `
2023-12-08 22:27:53 +01:00
LoadBalancingPolicy string ` yaml:"load_balancing_policy,omitempty" `
2023-11-13 22:30:39 +01:00
DropSrcPathPrefixParts int ` yaml:"drop_src_path_prefix_parts,omitempty" `
TLSInsecureSkipVerify * bool ` yaml:"tls_insecure_skip_verify,omitempty" `
TLSCAFile string ` yaml:"tls_ca_file,omitempty" `
2023-02-10 05:03:01 +01:00
concurrencyLimitCh chan struct { }
concurrencyLimitReached * metrics . Counter
2020-05-05 09:53:42 +02:00
2023-11-03 12:04:17 +01:00
httpTransport * http . Transport
2023-06-27 20:15:17 +02:00
requests * metrics . Counter
requestsDuration * metrics . Summary
2020-05-05 09:53:42 +02:00
}
2023-08-31 14:26:51 +02:00
// HeadersConf represents config for request and response headers.
type HeadersConf struct {
RequestHeaders [ ] Header ` yaml:"headers,omitempty" `
ResponseHeaders [ ] Header ` yaml:"response_headers,omitempty" `
}
2023-02-10 05:03:01 +01:00
func ( ui * UserInfo ) beginConcurrencyLimit ( ) error {
select {
case ui . concurrencyLimitCh <- struct { } { } :
return nil
default :
ui . concurrencyLimitReached . Inc ( )
2023-02-11 09:27:40 +01:00
return fmt . Errorf ( "cannot handle more than %d concurrent requests from user %s" , ui . getMaxConcurrentRequests ( ) , ui . name ( ) )
2023-02-10 05:03:01 +01:00
}
}
func ( ui * UserInfo ) endConcurrencyLimit ( ) {
2023-02-11 06:57:49 +01:00
<- ui . concurrencyLimitCh
}
func ( ui * UserInfo ) getMaxConcurrentRequests ( ) int {
mcr := ui . MaxConcurrentRequests
2023-02-12 05:51:39 +01:00
if mcr <= 0 {
2023-02-11 06:57:49 +01:00
mcr = * maxConcurrentPerUserRequests
2023-02-10 05:03:01 +01:00
}
2023-02-11 06:57:49 +01:00
return mcr
2023-02-10 05:03:01 +01:00
}
2021-10-22 18:08:06 +02:00
// Header is `Name: Value` http header, which must be added to the proxied request.
type Header struct {
Name string
Value string
}
// UnmarshalYAML unmarshals h from f.
func ( h * Header ) UnmarshalYAML ( f func ( interface { } ) error ) error {
var s string
if err := f ( & s ) ; err != nil {
return err
}
n := strings . IndexByte ( s , ':' )
if n < 0 {
return fmt . Errorf ( "missing speparator char ':' between Name and Value in the header %q; expected format - 'Name: Value'" , s )
}
h . Name = strings . TrimSpace ( s [ : n ] )
h . Value = strings . TrimSpace ( s [ n + 1 : ] )
return nil
}
// MarshalYAML marshals h to yaml.
func ( h * Header ) MarshalYAML ( ) ( interface { } , error ) {
s := fmt . Sprintf ( "%s: %s" , h . Name , h . Value )
return s , nil
}
2021-02-11 11:40:59 +01:00
// URLMap is a mapping from source paths to target urls.
type URLMap struct {
2023-11-13 22:30:39 +01:00
SrcPaths [ ] * SrcPath ` yaml:"src_paths,omitempty" `
URLPrefix * URLPrefix ` yaml:"url_prefix,omitempty" `
HeadersConf HeadersConf ` yaml:",inline" `
RetryStatusCodes [ ] int ` yaml:"retry_status_codes,omitempty" `
2023-12-08 22:27:53 +01:00
LoadBalancingPolicy string ` yaml:"load_balancing_policy,omitempty" `
2023-11-13 22:30:39 +01:00
DropSrcPathPrefixParts int ` yaml:"drop_src_path_prefix_parts,omitempty" `
2021-03-05 17:21:11 +01:00
}
// SrcPath represents an src path
type SrcPath struct {
sOriginal string
re * regexp . Regexp
}
2023-02-13 13:27:13 +01:00
// URLPrefix represents passed `url_prefix`
2021-05-29 00:00:23 +02:00
type URLPrefix struct {
2023-12-08 22:27:53 +01:00
n uint32
// the list of backend urls
2023-02-11 09:27:40 +01:00
bus [ ] * backendURL
2023-12-08 22:27:53 +01:00
// requests are re-tried on other backend urls for these http response status codes
retryStatusCodes [ ] int
// load balancing policy used
loadBalancingPolicy string
}
func ( up * URLPrefix ) setLoadBalancingPolicy ( loadBalancingPolicy string ) error {
switch loadBalancingPolicy {
case "" , // empty string is equivalent to least_loaded
"least_loaded" ,
"first_available" :
up . loadBalancingPolicy = loadBalancingPolicy
return nil
default :
return fmt . Errorf ( "unexpected load_balancing_policy: %q; want least_loaded or first_available" , loadBalancingPolicy )
}
2021-04-21 09:55:29 +02:00
}
2023-02-11 09:27:40 +01:00
type backendURL struct {
brokenDeadline uint64
concurrentRequests int32
url * url . URL
}
func ( bu * backendURL ) isBroken ( ) bool {
ct := fasttime . UnixTimestamp ( )
return ct < atomic . LoadUint64 ( & bu . brokenDeadline )
}
func ( bu * backendURL ) setBroken ( ) {
2023-08-02 14:30:21 +02:00
deadline := fasttime . UnixTimestamp ( ) + uint64 ( ( * failTimeout ) . Seconds ( ) )
2023-02-11 09:27:40 +01:00
atomic . StoreUint64 ( & bu . brokenDeadline , deadline )
}
2023-12-08 22:27:53 +01:00
func ( bu * backendURL ) get ( ) {
atomic . AddInt32 ( & bu . concurrentRequests , 1 )
}
2023-02-11 09:27:40 +01:00
func ( bu * backendURL ) put ( ) {
atomic . AddInt32 ( & bu . concurrentRequests , - 1 )
}
func ( up * URLPrefix ) getBackendsCount ( ) int {
return len ( up . bus )
}
2023-12-08 22:27:53 +01:00
// getBackendURL returns the backendURL depending on the load balance policy.
//
// backendURL.put() must be called on the returned backendURL after the request is complete.
func ( up * URLPrefix ) getBackendURL ( ) * backendURL {
if up . loadBalancingPolicy == "first_available" {
return up . getFirstAvailableBackendURL ( )
}
return up . getLeastLoadedBackendURL ( )
}
// getFirstAvailableBackendURL returns the first available backendURL, which isn't broken.
//
// backendURL.put() must be called on the returned backendURL after the request is complete.
func ( up * URLPrefix ) getFirstAvailableBackendURL ( ) * backendURL {
bus := up . bus
bu := bus [ 0 ]
if ! bu . isBroken ( ) {
// Fast path - send the request to the first url.
bu . get ( )
return bu
}
// Slow path - the first url is temporarily unavailabel. Fall back to the remaining urls.
for i := 1 ; i < len ( bus ) ; i ++ {
if ! bus [ i ] . isBroken ( ) {
bu = bus [ i ]
break
}
}
bu . get ( )
return bu
}
2023-02-11 09:27:40 +01:00
// getLeastLoadedBackendURL returns the backendURL with the minimum number of concurrent requests.
//
// backendURL.put() must be called on the returned backendURL after the request is complete.
func ( up * URLPrefix ) getLeastLoadedBackendURL ( ) * backendURL {
bus := up . bus
if len ( bus ) == 1 {
// Fast path - return the only backend url.
bu := bus [ 0 ]
2023-12-08 22:27:53 +01:00
bu . get ( )
2023-02-11 09:27:40 +01:00
return bu
}
// Slow path - select other backend urls.
2021-05-29 00:00:23 +02:00
n := atomic . AddUint32 ( & up . n , 1 )
2023-02-11 09:27:40 +01:00
for i := uint32 ( 0 ) ; i < uint32 ( len ( bus ) ) ; i ++ {
idx := ( n + i ) % uint32 ( len ( bus ) )
bu := bus [ idx ]
if bu . isBroken ( ) {
continue
}
if atomic . CompareAndSwapInt32 ( & bu . concurrentRequests , 0 , 1 ) {
// Fast path - return the backend with zero concurrently executed requests.
return bu
}
}
// Slow path - return the backend with the minimum number of concurrently executed requests.
buMin := bus [ n % uint32 ( len ( bus ) ) ]
minRequests := atomic . LoadInt32 ( & buMin . concurrentRequests )
for _ , bu := range bus {
if bu . isBroken ( ) {
continue
}
if n := atomic . LoadInt32 ( & bu . concurrentRequests ) ; n < minRequests {
buMin = bu
minRequests = n
}
}
2023-12-08 22:27:53 +01:00
buMin . get ( )
2023-02-11 09:27:40 +01:00
return buMin
2021-05-29 00:00:23 +02:00
}
// UnmarshalYAML unmarshals up from yaml.
func ( up * URLPrefix ) UnmarshalYAML ( f func ( interface { } ) error ) error {
var v interface { }
if err := f ( & v ) ; err != nil {
2021-04-21 09:55:29 +02:00
return err
}
2023-12-08 22:27:53 +01:00
2021-05-29 00:00:23 +02:00
var urls [ ] string
switch x := v . ( type ) {
case string :
urls = [ ] string { x }
case [ ] interface { } :
if len ( x ) == 0 {
return fmt . Errorf ( "`url_prefix` must contain at least a single url" )
}
us := make ( [ ] string , len ( x ) )
for i , xx := range x {
s , ok := xx . ( string )
if ! ok {
return fmt . Errorf ( "`url_prefix` must contain array of strings; got %T" , xx )
}
us [ i ] = s
}
urls = us
default :
return fmt . Errorf ( "unexpected type for `url_prefix`: %T; want string or []string" , v )
}
2023-12-08 22:27:53 +01:00
2023-02-11 09:27:40 +01:00
bus := make ( [ ] * backendURL , len ( urls ) )
2021-05-29 00:00:23 +02:00
for i , u := range urls {
pu , err := url . Parse ( u )
if err != nil {
return fmt . Errorf ( "cannot unmarshal %q into url: %w" , u , err )
}
2023-02-11 09:27:40 +01:00
bus [ i ] = & backendURL {
url : pu ,
}
2021-04-21 09:55:29 +02:00
}
2023-02-11 09:27:40 +01:00
up . bus = bus
2021-04-21 09:55:29 +02:00
return nil
}
2021-05-29 00:00:23 +02:00
// MarshalYAML marshals up to yaml.
func ( up * URLPrefix ) MarshalYAML ( ) ( interface { } , error ) {
var b [ ] byte
2023-02-11 09:27:40 +01:00
if len ( up . bus ) == 1 {
u := up . bus [ 0 ] . url . String ( )
2021-05-29 00:00:23 +02:00
b = strconv . AppendQuote ( b , u )
return string ( b ) , nil
}
b = append ( b , '[' )
2023-02-11 09:27:40 +01:00
for i , bu := range up . bus {
u := bu . url . String ( )
2021-05-29 00:00:23 +02:00
b = strconv . AppendQuote ( b , u )
2023-02-11 09:27:40 +01:00
if i + 1 < len ( up . bus ) {
2021-05-29 00:00:23 +02:00
b = append ( b , ',' )
}
}
b = append ( b , ']' )
return string ( b ) , nil
2021-04-21 09:55:29 +02:00
}
2021-03-05 17:21:11 +01:00
func ( sp * SrcPath ) match ( s string ) bool {
prefix , ok := sp . re . LiteralPrefix ( )
if ok {
// Fast path - literal match
return s == prefix
}
if ! strings . HasPrefix ( s , prefix ) {
return false
}
return sp . re . MatchString ( s )
}
// UnmarshalYAML implements yaml.Unmarshaler
func ( sp * SrcPath ) UnmarshalYAML ( f func ( interface { } ) error ) error {
var s string
if err := f ( & s ) ; err != nil {
return err
}
sAnchored := "^(?:" + s + ")$"
re , err := regexp . Compile ( sAnchored )
if err != nil {
return fmt . Errorf ( "cannot build regexp from %q: %w" , s , err )
}
sp . sOriginal = s
sp . re = re
return nil
}
// MarshalYAML implements yaml.Marshaler.
func ( sp * SrcPath ) MarshalYAML ( ) ( interface { } , error ) {
return sp . sOriginal , nil
2021-02-11 11:40:59 +01:00
}
2023-09-20 15:04:52 +02:00
var (
configReloads = metrics . NewCounter ( ` vmauth_config_last_reload_total ` )
configReloadErrors = metrics . NewCounter ( ` vmauth_config_last_reload_errors_total ` )
configSuccess = metrics . NewCounter ( ` vmauth_config_last_reload_successful ` )
configTimestamp = metrics . NewCounter ( ` vmauth_config_last_reload_success_timestamp_seconds ` )
)
2020-05-05 09:53:42 +02:00
func initAuthConfig ( ) {
if len ( * authConfigPath ) == 0 {
2020-06-05 19:13:03 +02:00
logger . Fatalf ( "missing required `-auth.config` command-line flag" )
2020-05-05 09:53:42 +02:00
}
2021-05-21 15:34:03 +02:00
// Register SIGHUP handler for config re-read just before readAuthConfig call.
// This guarantees that the config will be re-read if the signal arrives during readAuthConfig call.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1240
sighupCh := procutil . NewSighupChan ( )
2023-09-20 15:04:52 +02:00
_ , err := loadAuthConfig ( )
2020-05-05 09:53:42 +02:00
if err != nil {
2023-05-08 19:04:25 +02:00
logger . Fatalf ( "cannot load auth config: %s" , err )
2020-05-05 09:53:42 +02:00
}
2023-04-20 19:08:27 +02:00
2023-09-20 15:04:52 +02:00
configSuccess . Set ( 1 )
configTimestamp . Set ( fasttime . UnixTimestamp ( ) )
2020-05-05 09:53:42 +02:00
stopCh = make ( chan struct { } )
authConfigWG . Add ( 1 )
go func ( ) {
defer authConfigWG . Done ( )
2021-05-21 15:34:03 +02:00
authConfigReloader ( sighupCh )
2020-05-05 09:53:42 +02:00
} ( )
}
func stopAuthConfig ( ) {
close ( stopCh )
authConfigWG . Wait ( )
}
2021-05-21 15:34:03 +02:00
func authConfigReloader ( sighupCh <- chan os . Signal ) {
2023-03-23 09:34:12 +01:00
var refreshCh <- chan time . Time
// initialize auth refresh interval
if * configCheckInterval > 0 {
ticker := time . NewTicker ( * configCheckInterval )
defer ticker . Stop ( )
refreshCh = ticker . C
}
2023-09-20 15:04:52 +02:00
updateFn := func ( ) {
configReloads . Inc ( )
updated , err := loadAuthConfig ( )
if err != nil {
logger . Errorf ( "failed to load auth config; using the last successfully loaded config; error: %s" , err )
configSuccess . Set ( 0 )
configReloadErrors . Inc ( )
return
}
configSuccess . Set ( 1 )
if updated {
configTimestamp . Set ( fasttime . UnixTimestamp ( ) )
}
}
2020-05-05 09:53:42 +02:00
for {
select {
case <- stopCh :
return
2023-03-23 09:34:12 +01:00
case <- refreshCh :
2023-09-20 15:04:52 +02:00
updateFn ( )
2020-05-05 09:53:42 +02:00
case <- sighupCh :
2020-06-03 22:22:09 +02:00
logger . Infof ( "SIGHUP received; loading -auth.config=%q" , * authConfigPath )
2023-09-20 15:04:52 +02:00
updateFn ( )
2020-05-05 09:53:42 +02:00
}
}
}
2023-09-20 15:04:52 +02:00
// authConfigData stores the yaml definition for this config.
// authConfigData needs to be updated each time authConfig is updated.
var authConfigData atomic . Pointer [ [ ] byte ]
2023-04-20 19:08:27 +02:00
var authConfig atomic . Pointer [ AuthConfig ]
var authUsers atomic . Pointer [ map [ string ] * UserInfo ]
2020-05-05 09:53:42 +02:00
var authConfigWG sync . WaitGroup
var stopCh chan struct { }
2023-09-20 15:04:52 +02:00
// loadAuthConfig loads and applies the config from *authConfigPath.
// It returns bool value to identify if new config was applied.
// The config can be not applied if there is a parsing error
// or if there are no changes to the current authConfig.
func loadAuthConfig ( ) ( bool , error ) {
data , err := fs . ReadFileOrHTTP ( * authConfigPath )
if err != nil {
2023-09-21 11:04:13 +02:00
return false , fmt . Errorf ( "failed to read -auth.config=%q: %w" , * authConfigPath , err )
2023-09-20 15:04:52 +02:00
}
oldData := authConfigData . Load ( )
if oldData != nil && bytes . Equal ( data , * oldData ) {
// there are no updates in the config - skip reloading.
return false , nil
}
ac , err := parseAuthConfig ( data )
2020-05-05 09:53:42 +02:00
if err != nil {
2023-09-21 11:04:13 +02:00
return false , fmt . Errorf ( "failed to parse -auth.config=%q: %w" , * authConfigPath , err )
2020-05-05 09:53:42 +02:00
}
2023-04-20 19:08:27 +02:00
m , err := parseAuthConfigUsers ( ac )
2020-05-05 09:53:42 +02:00
if err != nil {
2023-09-21 11:04:13 +02:00
return false , fmt . Errorf ( "failed to parse users from -auth.config=%q: %w" , * authConfigPath , err )
2020-05-05 09:53:42 +02:00
}
2023-05-08 19:04:25 +02:00
logger . Infof ( "loaded information about %d users from -auth.config=%q" , len ( m ) , * authConfigPath )
2023-04-20 19:08:27 +02:00
authConfig . Store ( ac )
2023-09-20 15:04:52 +02:00
authConfigData . Store ( & data )
2023-04-20 19:08:27 +02:00
authUsers . Store ( & m )
2023-09-20 15:04:52 +02:00
return true , nil
2023-04-20 19:08:27 +02:00
}
func parseAuthConfig ( data [ ] byte ) ( * AuthConfig , error ) {
data , err := envtemplate . ReplaceBytes ( data )
2022-10-18 09:28:39 +02:00
if err != nil {
return nil , fmt . Errorf ( "cannot expand environment vars: %w" , err )
}
2020-05-05 09:53:42 +02:00
var ac AuthConfig
2023-04-20 19:08:27 +02:00
if err = yaml . UnmarshalStrict ( data , & ac ) ; err != nil {
2020-06-30 21:58:18 +02:00
return nil , fmt . Errorf ( "cannot unmarshal AuthConfig data: %w" , err )
2020-05-05 09:53:42 +02:00
}
2023-04-24 14:57:13 +02:00
ui := ac . UnauthorizedUser
if ui != nil {
2023-11-01 20:59:46 +01:00
if ui . Username != "" {
return nil , fmt . Errorf ( "field username can't be specified for unauthorized_user section" )
}
if ui . Password != "" {
return nil , fmt . Errorf ( "field password can't be specified for unauthorized_user section" )
}
if ui . BearerToken != "" {
return nil , fmt . Errorf ( "field bearer_token can't be specified for unauthorized_user section" )
}
if ui . Name != "" {
return nil , fmt . Errorf ( "field name can't be specified for unauthorized_user section" )
}
2023-12-08 22:27:53 +01:00
if err := ui . initURLs ( ) ; err != nil {
return nil , err
}
2023-04-24 14:57:13 +02:00
ui . requests = metrics . GetOrCreateCounter ( ` vmauth_unauthorized_user_requests_total ` )
2023-06-27 20:15:17 +02:00
ui . requestsDuration = metrics . GetOrCreateSummary ( ` vmauth_unauthorized_user_request_duration_seconds ` )
2023-04-24 14:57:13 +02:00
ui . concurrencyLimitCh = make ( chan struct { } , ui . getMaxConcurrentRequests ( ) )
ui . concurrencyLimitReached = metrics . GetOrCreateCounter ( ` vmauth_unauthorized_user_concurrent_requests_limit_reached_total ` )
_ = metrics . GetOrCreateGauge ( ` vmauth_unauthorized_user_concurrent_requests_capacity ` , func ( ) float64 {
return float64 ( cap ( ui . concurrencyLimitCh ) )
} )
_ = metrics . GetOrCreateGauge ( ` vmauth_unauthorized_user_concurrent_requests_current ` , func ( ) float64 {
return float64 ( len ( ui . concurrencyLimitCh ) )
} )
2023-11-13 08:23:35 +01:00
tr , err := getTransport ( ui . TLSInsecureSkipVerify , ui . TLSCAFile )
if err != nil {
return nil , fmt . Errorf ( "cannot initialize HTTP transport: %w" , err )
}
ui . httpTransport = tr
2023-04-24 14:57:13 +02:00
}
2023-04-20 19:08:27 +02:00
return & ac , nil
}
func parseAuthConfigUsers ( ac * AuthConfig ) ( map [ string ] * UserInfo , error ) {
2020-05-05 09:53:42 +02:00
uis := ac . Users
2023-05-18 18:44:01 +02:00
if len ( uis ) == 0 && ac . UnauthorizedUser == nil {
return nil , fmt . Errorf ( "Missing `users` or `unauthorized_user` sections" )
2020-05-05 09:53:42 +02:00
}
2021-04-02 21:14:53 +02:00
byAuthToken := make ( map [ string ] * UserInfo , len ( uis ) )
2020-05-05 09:53:42 +02:00
for i := range uis {
ui := & uis [ i ]
2021-04-02 21:14:53 +02:00
if ui . BearerToken == "" && ui . Username == "" {
return nil , fmt . Errorf ( "either bearer_token or username must be set" )
}
if ui . BearerToken != "" && ui . Username != "" {
return nil , fmt . Errorf ( "bearer_token=%q and username=%q cannot be set simultaneously" , ui . BearerToken , ui . Username )
}
2021-11-17 12:31:16 +01:00
at1 , at2 := getAuthTokens ( ui . BearerToken , ui . Username , ui . Password )
if byAuthToken [ at1 ] != nil {
return nil , fmt . Errorf ( "duplicate auth token found for bearer_token=%q, username=%q: %q" , ui . BearerToken , ui . Username , at1 )
}
if byAuthToken [ at2 ] != nil {
return nil , fmt . Errorf ( "duplicate auth token found for bearer_token=%q, username=%q: %q" , ui . BearerToken , ui . Username , at2 )
2021-04-02 21:14:53 +02:00
}
2023-12-08 22:27:53 +01:00
if err := ui . initURLs ( ) ; err != nil {
return nil , err
2020-05-05 09:53:42 +02:00
}
2023-12-08 22:27:53 +01:00
2023-02-10 05:03:01 +01:00
name := ui . name ( )
2021-04-02 21:14:53 +02:00
if ui . BearerToken != "" {
if ui . Password != "" {
return nil , fmt . Errorf ( "password shouldn't be set for bearer_token %q" , ui . BearerToken )
}
2021-11-16 23:47:31 +01:00
ui . requests = metrics . GetOrCreateCounter ( fmt . Sprintf ( ` vmauth_user_requests_total { username=%q} ` , name ) )
2023-06-27 20:15:17 +02:00
ui . requestsDuration = metrics . GetOrCreateSummary ( fmt . Sprintf ( ` vmauth_user_request_duration_seconds { username=%q} ` , name ) )
2021-04-02 21:14:53 +02:00
}
if ui . Username != "" {
2021-11-16 23:47:31 +01:00
ui . requests = metrics . GetOrCreateCounter ( fmt . Sprintf ( ` vmauth_user_requests_total { username=%q} ` , name ) )
2023-06-27 20:15:17 +02:00
ui . requestsDuration = metrics . GetOrCreateSummary ( fmt . Sprintf ( ` vmauth_user_request_duration_seconds { username=%q} ` , name ) )
2021-04-02 21:14:53 +02:00
}
2023-02-11 06:57:49 +01:00
mcr := ui . getMaxConcurrentRequests ( )
ui . concurrencyLimitCh = make ( chan struct { } , mcr )
ui . concurrencyLimitReached = metrics . GetOrCreateCounter ( fmt . Sprintf ( ` vmauth_user_concurrent_requests_limit_reached_total { username=%q} ` , name ) )
_ = metrics . GetOrCreateGauge ( fmt . Sprintf ( ` vmauth_user_concurrent_requests_capacity { username=%q} ` , name ) , func ( ) float64 {
return float64 ( cap ( ui . concurrencyLimitCh ) )
} )
_ = metrics . GetOrCreateGauge ( fmt . Sprintf ( ` vmauth_user_concurrent_requests_current { username=%q} ` , name ) , func ( ) float64 {
return float64 ( len ( ui . concurrencyLimitCh ) )
} )
2023-12-08 22:27:53 +01:00
2023-11-13 08:23:35 +01:00
tr , err := getTransport ( ui . TLSInsecureSkipVerify , ui . TLSCAFile )
if err != nil {
return nil , fmt . Errorf ( "cannot initialize HTTP transport: %w" , err )
}
ui . httpTransport = tr
2023-11-03 12:04:17 +01:00
2021-11-17 12:31:16 +01:00
byAuthToken [ at1 ] = ui
byAuthToken [ at2 ] = ui
2020-05-05 09:53:42 +02:00
}
2021-04-02 21:14:53 +02:00
return byAuthToken , nil
}
2023-12-08 22:27:53 +01:00
func ( ui * UserInfo ) initURLs ( ) error {
retryStatusCodes := defaultRetryStatusCodes . Values ( )
loadBalancingPolicy := * defaultLoadBalancingPolicy
if ui . URLPrefix != nil {
if err := ui . URLPrefix . sanitize ( ) ; err != nil {
return err
}
if len ( ui . RetryStatusCodes ) > 0 {
retryStatusCodes = ui . RetryStatusCodes
}
if ui . LoadBalancingPolicy != "" {
loadBalancingPolicy = ui . LoadBalancingPolicy
}
ui . URLPrefix . retryStatusCodes = retryStatusCodes
if err := ui . URLPrefix . setLoadBalancingPolicy ( loadBalancingPolicy ) ; err != nil {
return err
}
}
if ui . DefaultURL != nil {
if err := ui . DefaultURL . sanitize ( ) ; err != nil {
return err
}
}
for _ , e := range ui . URLMaps {
if len ( e . SrcPaths ) == 0 {
return fmt . Errorf ( "missing `src_paths` in `url_map`" )
}
if e . URLPrefix == nil {
return fmt . Errorf ( "missing `url_prefix` in `url_map`" )
}
if err := e . URLPrefix . sanitize ( ) ; err != nil {
return err
}
rscs := retryStatusCodes
lbp := loadBalancingPolicy
if len ( e . RetryStatusCodes ) > 0 {
rscs = e . RetryStatusCodes
}
if e . LoadBalancingPolicy != "" {
lbp = e . LoadBalancingPolicy
}
e . URLPrefix . retryStatusCodes = rscs
if err := e . URLPrefix . setLoadBalancingPolicy ( lbp ) ; err != nil {
return err
}
}
if len ( ui . URLMaps ) == 0 && ui . URLPrefix == nil {
return fmt . Errorf ( "missing `url_prefix`" )
}
return nil
}
2023-02-10 05:03:01 +01:00
func ( ui * UserInfo ) name ( ) string {
if ui . Name != "" {
return ui . Name
}
if ui . Username != "" {
return ui . Username
}
if ui . BearerToken != "" {
return "bearer_token"
}
return ""
}
2021-11-17 12:31:16 +01:00
func getAuthTokens ( bearerToken , username , password string ) ( string , string ) {
if bearerToken != "" {
// Accept the bearerToken as Basic Auth username with empty password
at1 := getAuthToken ( bearerToken , "" , "" )
at2 := getAuthToken ( "" , bearerToken , "" )
return at1 , at2
}
at := getAuthToken ( "" , username , password )
return at , at
}
2021-04-02 21:14:53 +02:00
func getAuthToken ( bearerToken , username , password string ) string {
if bearerToken != "" {
return "Bearer " + bearerToken
}
token := username + ":" + password
token64 := base64 . StdEncoding . EncodeToString ( [ ] byte ( token ) )
return "Basic " + token64
2020-05-05 09:53:42 +02:00
}
2021-02-11 11:40:59 +01:00
2021-05-29 00:00:23 +02:00
func ( up * URLPrefix ) sanitize ( ) error {
2023-02-11 09:27:40 +01:00
for _ , bu := range up . bus {
puNew , err := sanitizeURLPrefix ( bu . url )
2021-05-29 00:00:23 +02:00
if err != nil {
return err
}
2023-02-11 09:27:40 +01:00
bu . url = puNew
2021-05-29 00:00:23 +02:00
}
return nil
}
2021-04-21 09:55:29 +02:00
func sanitizeURLPrefix ( urlPrefix * url . URL ) ( * url . URL , error ) {
2021-02-11 11:40:59 +01:00
// Remove trailing '/' from urlPrefix
2021-04-21 09:55:29 +02:00
for strings . HasSuffix ( urlPrefix . Path , "/" ) {
urlPrefix . Path = urlPrefix . Path [ : len ( urlPrefix . Path ) - 1 ]
2021-02-11 11:40:59 +01:00
}
// Validate urlPrefix
2021-04-21 09:55:29 +02:00
if urlPrefix . Scheme != "http" && urlPrefix . Scheme != "https" {
return nil , fmt . Errorf ( "unsupported scheme for `url_prefix: %q`: %q; must be `http` or `https`" , urlPrefix , urlPrefix . Scheme )
2021-02-11 11:40:59 +01:00
}
2021-04-21 09:55:29 +02:00
if urlPrefix . Host == "" {
return nil , fmt . Errorf ( "missing hostname in `url_prefix %q`" , urlPrefix . Host )
2021-02-11 11:40:59 +01:00
}
return urlPrefix , nil
}