2020-05-05 07:53:42 +00:00
package main
import (
2023-09-20 13:04:52 +00:00
"bytes"
2021-04-02 19:14:53 +00:00
"encoding/base64"
2020-05-05 07:53:42 +00:00
"flag"
"fmt"
2023-11-03 11:04:17 +00:00
"net/http"
2020-05-05 07:53:42 +00:00
"net/url"
2021-05-21 13:34:03 +00:00
"os"
2021-03-05 16:21:11 +00:00
"regexp"
2024-01-21 02:40:52 +00:00
"sort"
2021-05-28 22:00:23 +00:00
"strconv"
2020-05-05 07:53:42 +00:00
"strings"
"sync"
"sync/atomic"
2023-03-23 08:34:12 +00:00
"time"
2020-05-05 07:53:42 +00:00
2023-11-03 11:04:17 +00:00
"github.com/VictoriaMetrics/metrics"
2024-01-21 02:40:52 +00:00
"github.com/cespare/xxhash/v2"
2023-11-03 11:04:17 +00:00
"gopkg.in/yaml.v2"
2020-08-13 13:43:55 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
2023-02-11 08:27:40 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
2023-12-08 21:27:53 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
2024-01-21 19:58:26 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs/fscore"
2020-05-05 07:53:42 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/procutil"
)
var (
2021-12-02 22:08:42 +00: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 08:34:12 +00: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 21:27:53 +00: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 07:53:42 +00:00
)
// AuthConfig represents auth config.
type AuthConfig struct {
2023-04-24 12:57:13 +00:00
Users [ ] UserInfo ` yaml:"users,omitempty" `
UnauthorizedUser * UserInfo ` yaml:"unauthorized_user,omitempty" `
2020-05-05 07:53:42 +00:00
}
// UserInfo is user information read from authConfigPath
type UserInfo struct {
2023-11-13 21:30:39 +00: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 21:27:53 +00:00
LoadBalancingPolicy string ` yaml:"load_balancing_policy,omitempty" `
2023-12-13 23:04:46 +00:00
DropSrcPathPrefixParts * int ` yaml:"drop_src_path_prefix_parts,omitempty" `
2023-11-13 21:30:39 +00:00
TLSInsecureSkipVerify * bool ` yaml:"tls_insecure_skip_verify,omitempty" `
TLSCAFile string ` yaml:"tls_ca_file,omitempty" `
2023-02-10 04:03:01 +00:00
2024-01-21 02:40:52 +00:00
MetricLabels map [ string ] string ` yaml:"metric_labels,omitempty" `
2023-02-10 04:03:01 +00:00
concurrencyLimitCh chan struct { }
concurrencyLimitReached * metrics . Counter
2020-05-05 07:53:42 +00:00
2023-11-03 11:04:17 +00:00
httpTransport * http . Transport
2023-06-27 18:15:17 +00:00
requests * metrics . Counter
2024-01-21 02:40:52 +00:00
backendErrors * metrics . Counter
2023-06-27 18:15:17 +00:00
requestsDuration * metrics . Summary
2020-05-05 07:53:42 +00:00
}
2023-08-31 12:26:51 +00: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 04:03:01 +00:00
func ( ui * UserInfo ) beginConcurrencyLimit ( ) error {
select {
case ui . concurrencyLimitCh <- struct { } { } :
return nil
default :
ui . concurrencyLimitReached . Inc ( )
2023-02-11 08:27:40 +00:00
return fmt . Errorf ( "cannot handle more than %d concurrent requests from user %s" , ui . getMaxConcurrentRequests ( ) , ui . name ( ) )
2023-02-10 04:03:01 +00:00
}
}
func ( ui * UserInfo ) endConcurrencyLimit ( ) {
2023-02-11 05:57:49 +00:00
<- ui . concurrencyLimitCh
}
func ( ui * UserInfo ) getMaxConcurrentRequests ( ) int {
mcr := ui . MaxConcurrentRequests
2023-02-12 04:51:39 +00:00
if mcr <= 0 {
2023-02-11 05:57:49 +00:00
mcr = * maxConcurrentPerUserRequests
2023-02-10 04:03:01 +00:00
}
2023-02-11 05:57:49 +00:00
return mcr
2023-02-10 04:03:01 +00:00
}
2021-10-22 16:08:06 +00: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 10:40:59 +00:00
// URLMap is a mapping from source paths to target urls.
type URLMap struct {
2023-12-13 22:46:36 +00:00
// SrcHosts is the list of regular expressions, which match the request hostname.
SrcHosts [ ] * Regex ` yaml:"src_hosts,omitempty" `
// SrcPaths is the list of regular expressions, which match the request path.
SrcPaths [ ] * Regex ` yaml:"src_paths,omitempty" `
// UrlPrefix contains backend url prefixes for the proxied request url.
URLPrefix * URLPrefix ` yaml:"url_prefix,omitempty" `
// HeadersConf is the config for augumenting request and response headers.
HeadersConf HeadersConf ` yaml:",inline" `
// RetryStatusCodes is the list of response status codes used for retrying requests.
RetryStatusCodes [ ] int ` yaml:"retry_status_codes,omitempty" `
// LoadBalancingPolicy is load balancing policy among UrlPrefix backends.
LoadBalancingPolicy string ` yaml:"load_balancing_policy,omitempty" `
// DropSrcPathPrefixParts is the number of `/`-delimited request path prefix parts to drop before proxying the request to backend.
2023-12-13 23:04:46 +00:00
DropSrcPathPrefixParts * int ` yaml:"drop_src_path_prefix_parts,omitempty" `
2021-03-05 16:21:11 +00:00
}
2023-12-13 22:46:36 +00:00
// Regex represents a regex
type Regex struct {
2021-03-05 16:21:11 +00:00
sOriginal string
re * regexp . Regexp
}
2023-02-13 12:27:13 +00:00
// URLPrefix represents passed `url_prefix`
2021-05-28 22:00:23 +00:00
type URLPrefix struct {
2023-12-08 21:27:53 +00:00
n uint32
// the list of backend urls
2023-02-11 08:27:40 +00:00
bus [ ] * backendURL
2023-12-08 21:27:53 +00:00
// requests are re-tried on other backend urls for these http response status codes
retryStatusCodes [ ] int
// load balancing policy used
loadBalancingPolicy string
2023-12-13 23:04:46 +00:00
// how many request path prefix parts to drop before routing the request to backendURL.
dropSrcPathPrefixParts int
2023-12-08 21:27:53 +00:00
}
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 07:55:29 +00:00
}
2023-02-11 08:27:40 +00: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 12:30:21 +00:00
deadline := fasttime . UnixTimestamp ( ) + uint64 ( ( * failTimeout ) . Seconds ( ) )
2023-02-11 08:27:40 +00:00
atomic . StoreUint64 ( & bu . brokenDeadline , deadline )
}
2023-12-08 21:27:53 +00:00
func ( bu * backendURL ) get ( ) {
atomic . AddInt32 ( & bu . concurrentRequests , 1 )
}
2023-02-11 08:27:40 +00:00
func ( bu * backendURL ) put ( ) {
atomic . AddInt32 ( & bu . concurrentRequests , - 1 )
}
func ( up * URLPrefix ) getBackendsCount ( ) int {
return len ( up . bus )
}
2023-12-08 21:27:53 +00: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 08:27:40 +00: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 21:27:53 +00:00
bu . get ( )
2023-02-11 08:27:40 +00:00
return bu
}
// Slow path - select other backend urls.
2021-05-28 22:00:23 +00:00
n := atomic . AddUint32 ( & up . n , 1 )
2023-02-11 08:27:40 +00:00
for i := uint32 ( 0 ) ; i < uint32 ( len ( bus ) ) ; i ++ {
idx := ( n + i ) % uint32 ( len ( bus ) )
bu := bus [ idx ]
if bu . isBroken ( ) {
continue
}
2024-01-21 11:58:27 +00:00
if atomic . LoadInt32 ( & bu . concurrentRequests ) == 0 {
2023-02-11 08:27:40 +00:00
// Fast path - return the backend with zero concurrently executed requests.
2024-01-21 11:58:27 +00:00
// Do not use atomic.CompareAndSwapInt32(), since it is much slower on systems with many CPU cores.
atomic . AddInt32 ( & bu . concurrentRequests , 1 )
2023-02-11 08:27:40 +00:00
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 21:27:53 +00:00
buMin . get ( )
2023-02-11 08:27:40 +00:00
return buMin
2021-05-28 22:00:23 +00: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 07:55:29 +00:00
return err
}
2023-12-08 21:27:53 +00:00
2021-05-28 22:00:23 +00: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 21:27:53 +00:00
2023-02-11 08:27:40 +00:00
bus := make ( [ ] * backendURL , len ( urls ) )
2021-05-28 22:00:23 +00: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 08:27:40 +00:00
bus [ i ] = & backendURL {
url : pu ,
}
2021-04-21 07:55:29 +00:00
}
2023-02-11 08:27:40 +00:00
up . bus = bus
2021-04-21 07:55:29 +00:00
return nil
}
2021-05-28 22:00:23 +00:00
// MarshalYAML marshals up to yaml.
func ( up * URLPrefix ) MarshalYAML ( ) ( interface { } , error ) {
var b [ ] byte
2023-02-11 08:27:40 +00:00
if len ( up . bus ) == 1 {
u := up . bus [ 0 ] . url . String ( )
2021-05-28 22:00:23 +00:00
b = strconv . AppendQuote ( b , u )
return string ( b ) , nil
}
b = append ( b , '[' )
2023-02-11 08:27:40 +00:00
for i , bu := range up . bus {
u := bu . url . String ( )
2021-05-28 22:00:23 +00:00
b = strconv . AppendQuote ( b , u )
2023-02-11 08:27:40 +00:00
if i + 1 < len ( up . bus ) {
2021-05-28 22:00:23 +00:00
b = append ( b , ',' )
}
}
b = append ( b , ']' )
return string ( b ) , nil
2021-04-21 07:55:29 +00:00
}
2023-12-13 22:46:36 +00:00
func ( r * Regex ) match ( s string ) bool {
prefix , ok := r . re . LiteralPrefix ( )
2021-03-05 16:21:11 +00:00
if ok {
// Fast path - literal match
return s == prefix
}
if ! strings . HasPrefix ( s , prefix ) {
return false
}
2023-12-13 22:46:36 +00:00
return r . re . MatchString ( s )
2021-03-05 16:21:11 +00:00
}
// UnmarshalYAML implements yaml.Unmarshaler
2023-12-13 22:46:36 +00:00
func ( r * Regex ) UnmarshalYAML ( f func ( interface { } ) error ) error {
2021-03-05 16:21:11 +00:00
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 )
}
2023-12-13 22:46:36 +00:00
r . sOriginal = s
r . re = re
2021-03-05 16:21:11 +00:00
return nil
}
// MarshalYAML implements yaml.Marshaler.
2023-12-13 22:46:36 +00:00
func ( r * Regex ) MarshalYAML ( ) ( interface { } , error ) {
return r . sOriginal , nil
2021-02-11 10:40:59 +00:00
}
2023-09-20 13:04:52 +00:00
var (
configReloads = metrics . NewCounter ( ` vmauth_config_last_reload_total ` )
configReloadErrors = metrics . NewCounter ( ` vmauth_config_last_reload_errors_total ` )
2023-12-20 12:23:38 +00:00
configSuccess = metrics . NewGauge ( ` vmauth_config_last_reload_successful ` , nil )
2023-09-20 13:04:52 +00:00
configTimestamp = metrics . NewCounter ( ` vmauth_config_last_reload_success_timestamp_seconds ` )
)
2020-05-05 07:53:42 +00:00
func initAuthConfig ( ) {
if len ( * authConfigPath ) == 0 {
2020-06-05 17:13:03 +00:00
logger . Fatalf ( "missing required `-auth.config` command-line flag" )
2020-05-05 07:53:42 +00:00
}
2021-05-21 13:34:03 +00: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 13:04:52 +00:00
_ , err := loadAuthConfig ( )
2020-05-05 07:53:42 +00:00
if err != nil {
2023-05-08 17:04:25 +00:00
logger . Fatalf ( "cannot load auth config: %s" , err )
2020-05-05 07:53:42 +00:00
}
2023-04-20 17:08:27 +00:00
2023-09-20 13:04:52 +00:00
configSuccess . Set ( 1 )
configTimestamp . Set ( fasttime . UnixTimestamp ( ) )
2020-05-05 07:53:42 +00:00
stopCh = make ( chan struct { } )
authConfigWG . Add ( 1 )
go func ( ) {
defer authConfigWG . Done ( )
2021-05-21 13:34:03 +00:00
authConfigReloader ( sighupCh )
2020-05-05 07:53:42 +00:00
} ( )
}
func stopAuthConfig ( ) {
close ( stopCh )
authConfigWG . Wait ( )
}
2021-05-21 13:34:03 +00:00
func authConfigReloader ( sighupCh <- chan os . Signal ) {
2023-03-23 08:34:12 +00: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 13:04:52 +00: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 07:53:42 +00:00
for {
select {
case <- stopCh :
return
2023-03-23 08:34:12 +00:00
case <- refreshCh :
2023-09-20 13:04:52 +00:00
updateFn ( )
2020-05-05 07:53:42 +00:00
case <- sighupCh :
2020-06-03 20:22:09 +00:00
logger . Infof ( "SIGHUP received; loading -auth.config=%q" , * authConfigPath )
2023-09-20 13:04:52 +00:00
updateFn ( )
2020-05-05 07:53:42 +00:00
}
}
}
2023-09-20 13:04:52 +00:00
// authConfigData stores the yaml definition for this config.
// authConfigData needs to be updated each time authConfig is updated.
var authConfigData atomic . Pointer [ [ ] byte ]
2024-01-21 02:40:52 +00:00
var (
authConfig atomic . Pointer [ AuthConfig ]
authUsers atomic . Pointer [ map [ string ] * UserInfo ]
authConfigWG sync . WaitGroup
stopCh chan struct { }
)
2020-05-05 07:53:42 +00:00
2023-09-20 13:04:52 +00: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 ) {
2024-01-21 19:58:26 +00:00
data , err := fscore . ReadFileOrHTTP ( * authConfigPath )
2023-09-20 13:04:52 +00:00
if err != nil {
2023-09-21 09:04:13 +00:00
return false , fmt . Errorf ( "failed to read -auth.config=%q: %w" , * authConfigPath , err )
2023-09-20 13:04:52 +00: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 07:53:42 +00:00
if err != nil {
2023-09-21 09:04:13 +00:00
return false , fmt . Errorf ( "failed to parse -auth.config=%q: %w" , * authConfigPath , err )
2020-05-05 07:53:42 +00:00
}
2023-04-20 17:08:27 +00:00
m , err := parseAuthConfigUsers ( ac )
2020-05-05 07:53:42 +00:00
if err != nil {
2023-09-21 09:04:13 +00:00
return false , fmt . Errorf ( "failed to parse users from -auth.config=%q: %w" , * authConfigPath , err )
2020-05-05 07:53:42 +00:00
}
2023-05-08 17:04:25 +00:00
logger . Infof ( "loaded information about %d users from -auth.config=%q" , len ( m ) , * authConfigPath )
2023-04-20 17:08:27 +00:00
authConfig . Store ( ac )
2023-09-20 13:04:52 +00:00
authConfigData . Store ( & data )
2023-04-20 17:08:27 +00:00
authUsers . Store ( & m )
2023-09-20 13:04:52 +00:00
return true , nil
2023-04-20 17:08:27 +00:00
}
func parseAuthConfig ( data [ ] byte ) ( * AuthConfig , error ) {
data , err := envtemplate . ReplaceBytes ( data )
2022-10-18 07:28:39 +00:00
if err != nil {
return nil , fmt . Errorf ( "cannot expand environment vars: %w" , err )
}
2020-05-05 07:53:42 +00:00
var ac AuthConfig
2023-04-20 17:08:27 +00:00
if err = yaml . UnmarshalStrict ( data , & ac ) ; err != nil {
2020-06-30 19:58:18 +00:00
return nil , fmt . Errorf ( "cannot unmarshal AuthConfig data: %w" , err )
2020-05-05 07:53:42 +00:00
}
2023-04-24 12:57:13 +00:00
ui := ac . UnauthorizedUser
if ui != nil {
2023-11-01 19:59:46 +00: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 21:27:53 +00:00
if err := ui . initURLs ( ) ; err != nil {
return nil , err
}
2024-01-21 02:40:52 +00:00
metricLabels , err := ui . getMetricLabels ( )
if err != nil {
return nil , fmt . Errorf ( "cannot parse metric_labels for unauthorized_user: %w" , err )
}
ui . requests = metrics . GetOrCreateCounter ( ` vmauth_unauthorized_user_requests_total ` + metricLabels )
ui . backendErrors = metrics . GetOrCreateCounter ( ` vmauth_unauthorized_user_request_backend_errors_total ` + metricLabels )
ui . requestsDuration = metrics . GetOrCreateSummary ( ` vmauth_unauthorized_user_request_duration_seconds ` + metricLabels )
2023-04-24 12:57:13 +00:00
ui . concurrencyLimitCh = make ( chan struct { } , ui . getMaxConcurrentRequests ( ) )
2024-01-21 02:40:52 +00:00
ui . concurrencyLimitReached = metrics . GetOrCreateCounter ( ` vmauth_unauthorized_user_concurrent_requests_limit_reached_total ` + metricLabels )
_ = metrics . GetOrCreateGauge ( ` vmauth_unauthorized_user_concurrent_requests_capacity ` + metricLabels , func ( ) float64 {
2023-04-24 12:57:13 +00:00
return float64 ( cap ( ui . concurrencyLimitCh ) )
} )
2024-01-21 02:40:52 +00:00
_ = metrics . GetOrCreateGauge ( ` vmauth_unauthorized_user_concurrent_requests_current ` + metricLabels , func ( ) float64 {
2023-04-24 12:57:13 +00:00
return float64 ( len ( ui . concurrencyLimitCh ) )
} )
2024-01-21 02:40:52 +00:00
2023-11-13 07:23:35 +00: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 12:57:13 +00:00
}
2023-04-20 17:08:27 +00:00
return & ac , nil
}
func parseAuthConfigUsers ( ac * AuthConfig ) ( map [ string ] * UserInfo , error ) {
2020-05-05 07:53:42 +00:00
uis := ac . Users
2023-05-18 16:44:01 +00:00
if len ( uis ) == 0 && ac . UnauthorizedUser == nil {
return nil , fmt . Errorf ( "Missing `users` or `unauthorized_user` sections" )
2020-05-05 07:53:42 +00:00
}
2021-04-02 19:14:53 +00:00
byAuthToken := make ( map [ string ] * UserInfo , len ( uis ) )
2020-05-05 07:53:42 +00:00
for i := range uis {
ui := & uis [ i ]
2024-02-12 22:57:53 +00:00
if ui . Username != "" && ui . Password == "" {
// Do not allow setting username without password if there are other auth configs exist.
// This should prevent from typical mis-configuration when access by username without password
// remains open if other authorization schemes are defined.
if ui . BearerToken != "" {
return nil , fmt . Errorf ( "bearer_token=%q and username=%q cannot be set simultaneously" , ui . BearerToken , ui . Username )
}
2021-04-02 19:14:53 +00:00
}
2024-02-12 22:57:53 +00:00
ats := getAuthTokens ( ui . BearerToken , ui . Username , ui . Password )
if len ( ats ) == 0 {
return nil , fmt . Errorf ( "one of bearer_token, username or mtls must be set" )
2021-11-17 11:31:16 +00:00
}
2024-02-12 22:57:53 +00:00
for _ , at := range ats {
if uiOld := byAuthToken [ at ] ; uiOld != nil {
return nil , fmt . Errorf ( "duplicate auth token=%q found for username=%q, name=%q; the previous one is set for username=%q, name=%q" ,
at , ui . Username , ui . Name , uiOld . Username , uiOld . Name )
}
2021-04-02 19:14:53 +00:00
}
2023-12-08 21:27:53 +00:00
if err := ui . initURLs ( ) ; err != nil {
return nil , err
2020-05-05 07:53:42 +00:00
}
2023-12-08 21:27:53 +00:00
2024-01-21 02:40:52 +00:00
if ui . BearerToken != "" && ui . Password != "" {
return nil , fmt . Errorf ( "password shouldn't be set for bearer_token %q" , ui . BearerToken )
2021-04-02 19:14:53 +00:00
}
2024-01-21 02:40:52 +00:00
metricLabels , err := ui . getMetricLabels ( )
if err != nil {
return nil , fmt . Errorf ( "cannot parse metric_labels: %w" , err )
2021-04-02 19:14:53 +00:00
}
2024-01-21 02:40:52 +00:00
ui . requests = metrics . GetOrCreateCounter ( ` vmauth_user_requests_total ` + metricLabels )
ui . backendErrors = metrics . GetOrCreateCounter ( ` vmauth_user_request_backend_errors_total ` + metricLabels )
ui . requestsDuration = metrics . GetOrCreateSummary ( ` vmauth_user_request_duration_seconds ` + metricLabels )
2023-02-11 05:57:49 +00:00
mcr := ui . getMaxConcurrentRequests ( )
ui . concurrencyLimitCh = make ( chan struct { } , mcr )
2024-01-21 02:40:52 +00:00
ui . concurrencyLimitReached = metrics . GetOrCreateCounter ( ` vmauth_user_concurrent_requests_limit_reached_total ` + metricLabels )
_ = metrics . GetOrCreateGauge ( ` vmauth_user_concurrent_requests_capacity ` + metricLabels , func ( ) float64 {
2023-02-11 05:57:49 +00:00
return float64 ( cap ( ui . concurrencyLimitCh ) )
} )
2024-01-21 02:40:52 +00:00
_ = metrics . GetOrCreateGauge ( ` vmauth_user_concurrent_requests_current ` + metricLabels , func ( ) float64 {
2023-02-11 05:57:49 +00:00
return float64 ( len ( ui . concurrencyLimitCh ) )
} )
2023-12-08 21:27:53 +00:00
2023-11-13 07:23:35 +00: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 11:04:17 +00:00
2024-02-12 22:57:53 +00:00
for _ , at := range ats {
byAuthToken [ at ] = ui
}
2020-05-05 07:53:42 +00:00
}
2021-04-02 19:14:53 +00:00
return byAuthToken , nil
}
2024-01-21 02:40:52 +00:00
var labelNameRegexp = regexp . MustCompile ( "^[a-zA-Z_:.][a-zA-Z0-9_:.]*$" )
func ( ui * UserInfo ) getMetricLabels ( ) ( string , error ) {
name := ui . name ( )
if len ( name ) == 0 && len ( ui . MetricLabels ) == 0 {
// fast path
return "" , nil
}
labels := make ( [ ] string , 0 , len ( ui . MetricLabels ) + 1 )
if len ( name ) > 0 {
labels = append ( labels , fmt . Sprintf ( ` username=%q ` , name ) )
}
for k , v := range ui . MetricLabels {
if ! labelNameRegexp . MatchString ( k ) {
return "" , fmt . Errorf ( "incorrect label name=%q, it must match regex=%q for user=%q" , k , labelNameRegexp , name )
}
labels = append ( labels , fmt . Sprintf ( ` %s=%q ` , k , v ) )
}
sort . Strings ( labels )
labelsStr := "{" + strings . Join ( labels , "," ) + "}"
return labelsStr , nil
}
2023-12-08 21:27:53 +00:00
func ( ui * UserInfo ) initURLs ( ) error {
retryStatusCodes := defaultRetryStatusCodes . Values ( )
loadBalancingPolicy := * defaultLoadBalancingPolicy
2023-12-13 23:04:46 +00:00
dropSrcPathPrefixParts := 0
2023-12-08 21:27:53 +00:00
if ui . URLPrefix != nil {
if err := ui . URLPrefix . sanitize ( ) ; err != nil {
return err
}
2023-12-13 23:04:46 +00:00
if ui . RetryStatusCodes != nil {
2023-12-08 21:27:53 +00:00
retryStatusCodes = ui . RetryStatusCodes
}
if ui . LoadBalancingPolicy != "" {
loadBalancingPolicy = ui . LoadBalancingPolicy
}
2023-12-13 23:04:46 +00:00
if ui . DropSrcPathPrefixParts != nil {
dropSrcPathPrefixParts = * ui . DropSrcPathPrefixParts
}
2023-12-08 21:27:53 +00:00
ui . URLPrefix . retryStatusCodes = retryStatusCodes
2023-12-13 23:04:46 +00:00
ui . URLPrefix . dropSrcPathPrefixParts = dropSrcPathPrefixParts
2023-12-08 21:27:53 +00:00
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 {
2023-12-13 22:46:36 +00:00
if len ( e . SrcPaths ) == 0 && len ( e . SrcHosts ) == 0 {
return fmt . Errorf ( "missing `src_paths` and `src_hosts` in `url_map`" )
2023-12-08 21:27:53 +00:00
}
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
2023-12-13 23:04:46 +00:00
dsp := dropSrcPathPrefixParts
if e . RetryStatusCodes != nil {
2023-12-08 21:27:53 +00:00
rscs = e . RetryStatusCodes
}
if e . LoadBalancingPolicy != "" {
lbp = e . LoadBalancingPolicy
}
2023-12-13 23:04:46 +00:00
if e . DropSrcPathPrefixParts != nil {
dsp = * e . DropSrcPathPrefixParts
}
2023-12-08 21:27:53 +00:00
e . URLPrefix . retryStatusCodes = rscs
if err := e . URLPrefix . setLoadBalancingPolicy ( lbp ) ; err != nil {
return err
}
2023-12-13 23:04:46 +00:00
e . URLPrefix . dropSrcPathPrefixParts = dsp
2023-12-08 21:27:53 +00:00
}
if len ( ui . URLMaps ) == 0 && ui . URLPrefix == nil {
return fmt . Errorf ( "missing `url_prefix`" )
}
return nil
}
2023-02-10 04:03:01 +00:00
func ( ui * UserInfo ) name ( ) string {
if ui . Name != "" {
return ui . Name
}
if ui . Username != "" {
return ui . Username
}
if ui . BearerToken != "" {
2024-01-21 02:40:52 +00:00
h := xxhash . Sum64 ( [ ] byte ( ui . BearerToken ) )
return fmt . Sprintf ( "bearer_token:hash:%016X" , h )
2023-02-10 04:03:01 +00:00
}
return ""
}
2024-02-12 22:57:53 +00:00
func getAuthTokens ( bearerToken , username , password string ) [ ] string {
var ats [ ] string
2021-11-17 11:31:16 +00:00
if bearerToken != "" {
// Accept the bearerToken as Basic Auth username with empty password
2024-02-12 22:57:53 +00:00
at1 := getHTTPAuthBearerToken ( bearerToken )
at2 := getHTTPAuthBasicToken ( bearerToken , "" )
ats = append ( ats , at1 , at2 )
} else if username != "" {
at := getHTTPAuthBasicToken ( username , password )
ats = append ( ats , at )
2021-11-17 11:31:16 +00:00
}
2024-02-12 22:57:53 +00:00
return ats
2021-11-17 11:31:16 +00:00
}
2024-02-12 22:57:53 +00:00
func getHTTPAuthBearerToken ( bearerToken string ) string {
return "http_auth:Bearer " + bearerToken
}
func getHTTPAuthBasicToken ( username , password string ) string {
2021-04-02 19:14:53 +00:00
token := username + ":" + password
token64 := base64 . StdEncoding . EncodeToString ( [ ] byte ( token ) )
2024-02-12 22:57:53 +00:00
return "http_auth:Basic " + token64
}
func getAuthTokensFromRequest ( r * http . Request ) [ ] string {
var ats [ ] string
ah := r . Header . Get ( "Authorization" )
if ah == "" {
return ats
}
if strings . HasPrefix ( ah , "Token " ) {
// Handle InfluxDB's proprietary token authentication scheme as a bearer token authentication
// See https://docs.influxdata.com/influxdb/v2.0/api/
ah = strings . Replace ( ah , "Token" , "Bearer" , 1 )
}
at := "http_auth:" + ah
ats = append ( ats , at )
return ats
2020-05-05 07:53:42 +00:00
}
2021-02-11 10:40:59 +00:00
2021-05-28 22:00:23 +00:00
func ( up * URLPrefix ) sanitize ( ) error {
2023-02-11 08:27:40 +00:00
for _ , bu := range up . bus {
puNew , err := sanitizeURLPrefix ( bu . url )
2021-05-28 22:00:23 +00:00
if err != nil {
return err
}
2023-02-11 08:27:40 +00:00
bu . url = puNew
2021-05-28 22:00:23 +00:00
}
return nil
}
2021-04-21 07:55:29 +00:00
func sanitizeURLPrefix ( urlPrefix * url . URL ) ( * url . URL , error ) {
2021-02-11 10:40:59 +00:00
// Remove trailing '/' from urlPrefix
2021-04-21 07:55:29 +00:00
for strings . HasSuffix ( urlPrefix . Path , "/" ) {
urlPrefix . Path = urlPrefix . Path [ : len ( urlPrefix . Path ) - 1 ]
2021-02-11 10:40:59 +00:00
}
// Validate urlPrefix
2021-04-21 07:55:29 +00: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 10:40:59 +00:00
}
2021-04-21 07:55:29 +00:00
if urlPrefix . Host == "" {
return nil , fmt . Errorf ( "missing hostname in `url_prefix %q`" , urlPrefix . Host )
2021-02-11 10:40:59 +00:00
}
return urlPrefix , nil
}