diff options
41 files changed, 852 insertions, 77 deletions
diff --git a/cmd/authelia-scripts/cmd_gen.go b/cmd/authelia-scripts/cmd_gen.go index cc8bfa943..f57e662e3 100644 --- a/cmd/authelia-scripts/cmd_gen.go +++ b/cmd/authelia-scripts/cmd_gen.go @@ -109,6 +109,7 @@ var decodedTypes = []reflect.Type{  	reflect.TypeOf(regexp.Regexp{}),  	reflect.TypeOf(url.URL{}),  	reflect.TypeOf(time.Duration(0)), +	reflect.TypeOf(schema.Address{}),  }  func containsType(needle reflect.Type, haystack []reflect.Type) (contains bool) { diff --git a/config.template.yml b/config.template.yml index 1e2e5bbf2..e38dbda84 100644 --- a/config.template.yml +++ b/config.template.yml @@ -102,6 +102,21 @@ log:    # keep_stdout: false  ## +## Telemetry Configuration +## +telemetry: + +  ## +  ## Metrics Configuration +  ## +  metrics: +    ## Enable Metrics. +    enabled: false + +    ## The address to listen on for metrics. This should be on a different port to the main server.port value. +    address: '0.0.0.0:9959' + +##  ## TOTP Configuration  ##  ## Parameters used for TOTP generation. @@ -27,6 +27,7 @@ require (  	github.com/otiai10/copy v1.7.0  	github.com/pkg/errors v0.9.1  	github.com/pquerna/otp v1.3.0 +	github.com/prometheus/client_golang v1.12.1  	github.com/simia-tech/crypt v0.5.1  	github.com/sirupsen/logrus v1.8.1  	github.com/spf13/cobra v1.4.0 @@ -42,6 +43,7 @@ require (  require (  	github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e // indirect  	github.com/andybalholm/brotli v1.0.4 // indirect +	github.com/beorn7/perks v1.0.1 // indirect  	github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect  	github.com/cespare/xxhash/v2 v2.1.2 // indirect  	github.com/davecgh/go-spew v1.1.1 // indirect @@ -70,6 +72,7 @@ require (  	github.com/klauspost/compress v1.15.0 // indirect  	github.com/magiconair/properties v1.8.5 // indirect  	github.com/mattn/goveralls v0.0.6 // indirect +	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect  	github.com/mitchellh/copystructure v1.2.0 // indirect  	github.com/mitchellh/reflectwalk v1.0.2 // indirect  	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -81,6 +84,9 @@ require (  	github.com/pelletier/go-toml v1.9.4 // indirect  	github.com/philhofer/fwd v1.1.1 // indirect  	github.com/pmezard/go-difflib v1.0.0 // indirect +	github.com/prometheus/client_model v0.2.0 // indirect +	github.com/prometheus/common v0.32.1 // indirect +	github.com/prometheus/procfs v0.7.3 // indirect  	github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d // indirect  	github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect  	github.com/spf13/afero v1.6.0 // indirect @@ -100,7 +106,6 @@ require (  	golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect  	golang.org/x/tools v0.1.7 // indirect  	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect -	google.golang.org/appengine v1.6.6 // indirect  	google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect  	google.golang.org/grpc v1.42.0 // indirect  	google.golang.org/protobuf v1.27.1 // indirect @@ -7,11 +7,32 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A  cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=  cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=  cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=  cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=  cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=  cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=  cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=  cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=  dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=  github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=  github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= @@ -80,6 +101,7 @@ github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDy  github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=  github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=  github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=  github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=  github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=  github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= @@ -216,6 +238,8 @@ github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF  github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=  github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=  github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=  github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=  github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=  github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= @@ -568,9 +592,15 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU  github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=  github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=  github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=  github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=  github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=  github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=  github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=  github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=  github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= @@ -579,6 +609,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y  github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=  github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=  github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=  github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=  github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=  github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -598,7 +630,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a  github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=  github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=  github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=  github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=  github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=  github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=  github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -608,8 +642,14 @@ github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt  github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=  github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=  github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=  github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=  github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=  github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=  github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=  github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -684,6 +724,7 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe  github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=  github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=  github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=  github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=  github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=  github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -791,7 +832,10 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV  github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=  github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=  github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=  github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=  github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=  github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=  github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -905,6 +949,7 @@ github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4  github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=  github.com/mattn/goveralls v0.0.6 h1:cr8Y0VMo/MnEZBjxNN/vh6G90SZ7IMb6lms1dzMoO+Y=  github.com/mattn/goveralls v0.0.6/go.mod h1:h8b4ow6FxSPMQHF6o2ve3qsclnffZjYTNEKmLesRwqw= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=  github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=  github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=  github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= @@ -938,6 +983,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ  github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=  github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=  github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=  github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=  github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=  github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= @@ -1088,11 +1134,15 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn  github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=  github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=  github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=  github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=  github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=  github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=  github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=  github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=  github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=  github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=  github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1101,6 +1151,9 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8  github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=  github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=  github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=  github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=  github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=  github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -1110,6 +1163,9 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa  github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=  github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=  github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=  github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=  github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=  github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -1298,7 +1354,9 @@ github.com/ysmood/gson v0.7.1 h1:zKL2MTGtynxdBdlZjyGsvEOZ7dkxaY5TH6QhAbTgz0Q=  github.com/ysmood/gson v0.7.1/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=  github.com/ysmood/leakless v0.7.0 h1:XCGdaPExyoreoQd+H5qgxM3ReNbSPFsEXpSKwbXbwQw=  github.com/ysmood/leakless v0.7.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=  github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=  github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=  github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=  github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -1325,6 +1383,8 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=  go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=  go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=  go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=  go.opentelemetry.io/contrib v0.18.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=  go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=  go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.13.0/go.mod h1:TwTkyRaTam1pOIb2wxcAiC2hkMVbokXkt6DEt5nDkD8= @@ -1408,6 +1468,11 @@ golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxT  golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=  golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=  golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=  golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=  golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=  golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1418,13 +1483,16 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl  golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=  golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=  golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=  golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=  golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=  golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=  golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=  golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=  golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=  golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=  golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=  golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=  golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -1464,19 +1532,28 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL  golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=  golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=  golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=  golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=  golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=  golang.org/x/net v0.0.0-20200219183655-46282727080f/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=  golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=  golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=  golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=  golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=  golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=  golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=  golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=  golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=  golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=  golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=  golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=  golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=  golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= @@ -1485,8 +1562,10 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG  golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=  golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=  golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=  golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=  golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=  golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=  golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1495,7 +1574,9 @@ golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJ  golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=  golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=  golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=  golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=  golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=  golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=  golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1542,6 +1623,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w  golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1549,36 +1631,50 @@ golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7w  golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=  golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=  golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=  golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=  golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=  golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=  golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=  golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=  golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=  golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=  golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=  golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=  golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=  golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -1652,15 +1748,38 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn  golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=  golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=  golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=  golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=  golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=  golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=  golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=  golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=  golang.org/x/tools v0.0.0-20200203215610-ab391d50b528/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=  golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=  golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=  golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=  golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=  golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=  golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -1686,6 +1805,17 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E  google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=  google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=  google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=  google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=  google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=  google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1707,9 +1837,28 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98  google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=  google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=  google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=  google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=  google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=  google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=  google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=  google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=  google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= @@ -1727,6 +1876,10 @@ google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac  google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=  google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=  google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=  google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=  google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=  google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= @@ -1816,6 +1969,8 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh  honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=  honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=  honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=  howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=  modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=  modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= @@ -1824,5 +1979,7 @@ modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs  modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=  rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=  rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=  sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=  sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/internal/commands/helpers.go b/internal/commands/helpers.go index 178dde8ef..4adfa05bc 100644 --- a/internal/commands/helpers.go +++ b/internal/commands/helpers.go @@ -3,6 +3,7 @@ package commands  import (  	"github.com/authelia/authelia/v4/internal/authentication"  	"github.com/authelia/authelia/v4/internal/authorization" +	"github.com/authelia/authelia/v4/internal/metrics"  	"github.com/authelia/authelia/v4/internal/middlewares"  	"github.com/authelia/authelia/v4/internal/notification"  	"github.com/authelia/authelia/v4/internal/ntp" @@ -73,12 +74,18 @@ func getProviders() (providers middlewares.Providers, warnings []error, errors [  	ppolicyProvider := middlewares.NewPasswordPolicyProvider(config.PasswordPolicy) +	var metricsProvider metrics.Provider +	if config.Telemetry.Metrics.Enabled { +		metricsProvider = metrics.NewPrometheus() +	} +  	return middlewares.Providers{  		Authorizer:      authorizer,  		UserProvider:    userProvider,  		Regulator:       regulator,  		OpenIDConnect:   oidcProvider,  		StorageProvider: storageProvider, +		Metrics:         metricsProvider,  		NTP:             ntpProvider,  		Notifier:        notifier,  		SessionProvider: sessionProvider, diff --git a/internal/commands/root.go b/internal/commands/root.go index d64e99c2c..0fd3082ef 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -4,6 +4,7 @@ import (  	"fmt"  	"os"  	"strings" +	"sync"  	"github.com/sirupsen/logrus"  	"github.com/spf13/cobra" @@ -77,9 +78,65 @@ func cmdRootRun(_ *cobra.Command, _ []string) {  	doStartupChecks(config, &providers) -	s, listener := server.CreateServer(*config, providers) +	runServers(config, providers, logger) +} + +func runServers(config *schema.Configuration, providers middlewares.Providers, logger *logrus.Logger) { +	wg := new(sync.WaitGroup) + +	wg.Add(2) + +	go func() { +		err := startDefaultServer(config, providers) +		if err != nil { +			logger.Fatal(err) +		} + +		wg.Done() +	}() + +	go func() { +		err := startMetricsServer(config, providers) +		if err != nil { +			logger.Fatal(err) +		} +	}() + +	wg.Wait() +} + +func startDefaultServer(config *schema.Configuration, providers middlewares.Providers) (err error) { +	svr, listener, err := server.CreateDefaultServer(*config, providers) + +	switch err { +	case nil: +		if err = svr.Serve(listener); err != nil { +			return fmt.Errorf("error occurred during default server operation: %w", err) +		} +	default: +		return fmt.Errorf("error occurred during default server startup: %w", err) +	} + +	return nil +} + +func startMetricsServer(config *schema.Configuration, providers middlewares.Providers) (err error) { +	if providers.Metrics == nil { +		return nil +	} + +	svr, listener, err := server.CreateMetricsServer(config.Telemetry.Metrics) + +	switch err { +	case nil: +		if err = svr.Serve(listener); err != nil { +			return fmt.Errorf("error occurred during metrics server operation: %w", err) +		} +	default: +		return fmt.Errorf("error occurred during metrics server startup: %w", err) +	} -	logger.Fatal(s.Serve(listener)) +	return nil  }  func doStartupChecks(config *schema.Configuration, providers *middlewares.Providers) { diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index 1e2e5bbf2..e38dbda84 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -102,6 +102,21 @@ log:    # keep_stdout: false  ## +## Telemetry Configuration +## +telemetry: + +  ## +  ## Metrics Configuration +  ## +  metrics: +    ## Enable Metrics. +    enabled: false + +    ## The address to listen on for metrics. This should be on a different port to the main server.port value. +    address: '0.0.0.0:9959' + +##  ## TOTP Configuration  ##  ## Parameters used for TOTP generation. diff --git a/internal/configuration/decode_hooks.go b/internal/configuration/decode_hooks.go index cd1dd2ee6..406933f96 100644 --- a/internal/configuration/decode_hooks.go +++ b/internal/configuration/decode_hooks.go @@ -10,6 +10,7 @@ import (  	"github.com/mitchellh/mapstructure" +	"github.com/authelia/authelia/v4/internal/configuration/schema"  	"github.com/authelia/authelia/v4/internal/utils"  ) @@ -211,3 +212,43 @@ func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType {  		return *result, nil  	}  } + +// StringToAddressHookFunc decodes a string into an Address or *Address. +func StringToAddressHookFunc() mapstructure.DecodeHookFuncType { +	return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) { +		var ptr bool + +		if f.Kind() != reflect.String { +			return data, nil +		} + +		kindStr := "Address" + +		if t.Kind() == reflect.Ptr { +			ptr = true +			kindStr = "*" + kindStr +		} + +		expectedType := reflect.TypeOf(schema.Address{}) + +		if ptr && t.Elem() != expectedType { +			return data, nil +		} else if !ptr && t != expectedType { +			return data, nil +		} + +		dataStr := data.(string) + +		var result *schema.Address + +		if result, err = schema.NewAddressFromString(dataStr); err != nil { +			return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err) +		} + +		if ptr { +			return result, nil +		} + +		return *result, nil +	} +} diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go index caec00297..b64bb8bc1 100644 --- a/internal/configuration/provider.go +++ b/internal/configuration/provider.go @@ -47,6 +47,7 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o inte  				StringToMailAddressHookFunc(),  				StringToURLHookFunc(),  				StringToRegexpHookFunc(), +				StringToAddressHookFunc(),  				ToTimeDurationHookFunc(),  			),  			Metadata:         nil, diff --git a/internal/configuration/schema/configuration.go b/internal/configuration/schema/configuration.go index 47a318d30..27ed0f142 100644 --- a/internal/configuration/schema/configuration.go +++ b/internal/configuration/schema/configuration.go @@ -20,6 +20,7 @@ type Configuration struct {  	Storage               StorageConfiguration               `koanf:"storage"`  	Notifier              NotifierConfiguration              `koanf:"notifier"`  	Server                ServerConfiguration                `koanf:"server"` +	Telemetry             TelemetryConfig                    `koanf:"telemetry"`  	Webauthn              WebauthnConfiguration              `koanf:"webauthn"`  	PasswordPolicy        PasswordPolicyConfiguration        `koanf:"password_policy"`  } diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go index f51f2e6cb..4d0f4ace6 100644 --- a/internal/configuration/schema/keys.go +++ b/internal/configuration/schema/keys.go @@ -181,6 +181,8 @@ var Keys = []string{  	"server.tls.key",  	"server.tls.client_certificates",  	"server.headers.csp_template", +	"telemetry.metrics.enabled", +	"telemetry.metrics.address",  	"webauthn.disable",  	"webauthn.display_name",  	"webauthn.attestation_conveyance_preference", diff --git a/internal/configuration/schema/telemetry.go b/internal/configuration/schema/telemetry.go new file mode 100644 index 000000000..c37f7bcf1 --- /dev/null +++ b/internal/configuration/schema/telemetry.go @@ -0,0 +1,23 @@ +package schema + +import ( +	"net" +) + +// TelemetryConfig represents the telemetry config. +type TelemetryConfig struct { +	Metrics TelemetryMetricsConfig `koanf:"metrics"` +} + +// TelemetryMetricsConfig represents the telemetry metrics config. +type TelemetryMetricsConfig struct { +	Enabled bool    `koanf:"enabled"` +	Address Address `koanf:"address"` +} + +// DefaultTelemetryConfig is the default telemetry configuration. +var DefaultTelemetryConfig = TelemetryConfig{ +	Metrics: TelemetryMetricsConfig{ +		Address: NewAddress("tcp", net.ParseIP("0.0.0.0"), 9959), +	}, +} diff --git a/internal/configuration/schema/types.go b/internal/configuration/schema/types.go new file mode 100644 index 000000000..a6dcb0b92 --- /dev/null +++ b/internal/configuration/schema/types.go @@ -0,0 +1,111 @@ +package schema + +import ( +	"fmt" +	"net" +	"regexp" +	"strconv" +) + +var regexpAddress = regexp.MustCompile(`^((?P<Scheme>\w+)://)?((?P<IPv4>((((25[0-5]|2[0-4]\d|[01]?\d\d?)(\.)){3})(25[0-5]|2[0-4]\d|[01]?\d\d?)))|(\[(?P<IPv6>([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d)|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d))\]):)?(?P<Port>\d+)$`) + +const tcp = "tcp" + +// NewAddress produces a valid address from input. +func NewAddress(scheme string, ip net.IP, port int) Address { +	return Address{ +		valid:  true, +		Scheme: scheme, +		IP:     ip, +		Port:   port, +	} +} + +// NewAddressFromString parses a string and returns an *Address or error. +func NewAddressFromString(addr string) (address *Address, err error) { +	if addr == "" { +		return &Address{}, nil +	} + +	if !regexpAddress.MatchString(addr) { +		return nil, fmt.Errorf("the string '%s' does not appear to be a valid address", addr) +	} + +	address = &Address{ +		valid: true, +	} + +	submatches := regexpAddress.FindStringSubmatch(addr) + +	var ip, port string + +	for i, name := range regexpAddress.SubexpNames() { +		switch name { +		case "Scheme": +			address.Scheme = submatches[i] +		case "IPv4": +			ip = submatches[i] + +			if address.Scheme == "" || address.Scheme == tcp { +				address.Scheme = "tcp4" +			} +		case "IPv6": +			ip = submatches[i] + +			if address.Scheme == "" || address.Scheme == tcp { +				address.Scheme = "tcp6" +			} +		case "Port": +			port = submatches[i] +		} +	} + +	if address.IP = net.ParseIP(ip); address.IP == nil { +		return nil, fmt.Errorf("failed to parse '%s' as an IP address", ip) +	} + +	address.Port, _ = strconv.Atoi(port) + +	if address.Port <= 0 || address.Port > 65535 { +		return nil, fmt.Errorf("failed to parse address port '%d' is invalid: ports must be between 1 and 65535", address.Port) +	} + +	return address, nil +} + +// Address represents an address. +type Address struct { +	valid bool + +	Scheme string +	net.IP +	Port int +} + +// Valid returns true if the Address is valid. +func (a Address) Valid() bool { +	return a.valid +} + +// String returns a string representation of the Address. +func (a Address) String() string { +	if !a.valid { +		return "" +	} + +	return fmt.Sprintf("%s://%s:%d", a.Scheme, a.IP.String(), a.Port) +} + +// HostPort returns a string representation of the Address with just the host and port. +func (a Address) HostPort() string { +	if !a.valid { +		return "" +	} + +	return fmt.Sprintf("%s:%d", a.IP.String(), a.Port) +} + +// Listener creates and returns a net.Listener. +func (a Address) Listener() (net.Listener, error) { +	return net.Listen(a.Scheme, a.HostPort()) +} diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go index e58ccca41..703054942 100644 --- a/internal/configuration/validator/configuration.go +++ b/internal/configuration/validator/configuration.go @@ -57,6 +57,8 @@ func ValidateConfiguration(config *schema.Configuration, validator *schema.Struc  	ValidateServer(config, validator) +	ValidateTelemetry(config, validator) +  	ValidateStorage(config.Storage, validator)  	ValidateNotifier(&config.Notifier, validator) diff --git a/internal/configuration/validator/telemetry.go b/internal/configuration/validator/telemetry.go new file mode 100644 index 000000000..9612d882a --- /dev/null +++ b/internal/configuration/validator/telemetry.go @@ -0,0 +1,14 @@ +package validator + +import ( +	"github.com/authelia/authelia/v4/internal/configuration/schema" +) + +// ValidateTelemetry validates the telemetry configuration. +func ValidateTelemetry(config *schema.Configuration, validator *schema.StructValidator) { +	if config.Telemetry.Metrics.Enabled { +		if config.Telemetry.Metrics.Address.String() == "" { +			config.Telemetry.Metrics.Address = schema.DefaultTelemetryConfig.Metrics.Address +		} +	} +} diff --git a/internal/handlers/const.go b/internal/handlers/const.go index a43fa75b9..0398cffe6 100644 --- a/internal/handlers/const.go +++ b/internal/handlers/const.go @@ -28,6 +28,10 @@ var (  	headerRemoteEmail     = []byte("Remote-Email")  ) +var ( +	headerContentTypeValueDefault = []byte("text/plain; charset=utf-8") +) +  const (  	// Forbidden means the user is forbidden the access to a resource.  	Forbidden authorizationMatching = iota diff --git a/internal/handlers/handler_configuration_password_policy.go b/internal/handlers/handler_configuration_password_policy.go index 3423eec54..f5014465d 100644 --- a/internal/handlers/handler_configuration_password_policy.go +++ b/internal/handlers/handler_configuration_password_policy.go @@ -6,7 +6,7 @@ import (  // PasswordPolicyConfigurationGET get the password policy configuration.  func PasswordPolicyConfigurationGET(ctx *middlewares.AutheliaCtx) { -	policyResponse := PassworPolicyBody{ +	policyResponse := PasswordPolicyBody{  		Mode: "disabled",  	} diff --git a/internal/handlers/handler_firstfactor.go b/internal/handlers/handler_firstfactor.go index 97d90aeaf..5628ea5a9 100644 --- a/internal/handlers/handler_firstfactor.go +++ b/internal/handlers/handler_firstfactor.go @@ -19,7 +19,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re  		requestTime := time.Now()  		if delayFunc != nil { -			defer delayFunc(ctx.Logger, requestTime, &successful) +			defer delayFunc(ctx, requestTime, &successful)  		}  		bodyJSON := firstFactorRequestBody{} diff --git a/internal/handlers/handler_reset_password_step1.go b/internal/handlers/handler_reset_password_step1.go index 5b242b94e..e6717f9b4 100644 --- a/internal/handlers/handler_reset_password_step1.go +++ b/internal/handlers/handler_reset_password_step1.go @@ -42,7 +42,7 @@ var ResetPasswordIdentityStart = middlewares.IdentityVerificationStart(middlewar  	TargetEndpoint:        "/reset-password/step2",  	ActionClaim:           ActionResetPassword,  	IdentityRetrieverFunc: identityRetrieverFromStorage, -}, middlewares.TimingAttackDelay(10, 250, 85, time.Millisecond*500)) +}, middlewares.TimingAttackDelay(10, 250, 85, time.Millisecond*500, false))  func resetPasswordIdentityFinish(ctx *middlewares.AutheliaCtx, username string) {  	userSession := ctx.GetSession() diff --git a/internal/handlers/handler_status.go b/internal/handlers/handler_status.go new file mode 100644 index 000000000..5a3255e09 --- /dev/null +++ b/internal/handlers/handler_status.go @@ -0,0 +1,12 @@ +package handlers + +import ( +	"github.com/valyala/fasthttp" +) + +// Status handles basic status responses. +func Status(statusCode int) fasthttp.RequestHandler { +	return func(ctx *fasthttp.RequestCtx) { +		SetStatusCodeResponse(ctx, statusCode) +	} +} diff --git a/internal/handlers/response.go b/internal/handlers/response.go index f7bd919a2..263633236 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -219,7 +219,7 @@ func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, ba  		}  	} -	if err = ctx.Providers.Regulator.Mark(ctx, successful, bannedUntil != nil, username, requestURI, requestMethod, authType, ctx.RemoteIP()); err != nil { +	if err = ctx.Providers.Regulator.Mark(ctx, successful, bannedUntil != nil, username, requestURI, requestMethod, authType); err != nil {  		ctx.Logger.Errorf("Unable to mark %s authentication attempt by user '%s': %+v", authType, username, err)  		return err @@ -248,7 +248,9 @@ func respondUnauthorized(ctx *middlewares.AutheliaCtx, message string) {  // SetStatusCodeResponse writes a response status code and an appropriate body on either a  // *fasthttp.RequestCtx or *middlewares.AutheliaCtx. -func SetStatusCodeResponse(ctx responseWriter, statusCode int) { +func SetStatusCodeResponse(ctx *fasthttp.RequestCtx, statusCode int) { +	ctx.Response.Reset() +	ctx.SetContentTypeBytes(headerContentTypeValueDefault)  	ctx.SetStatusCode(statusCode)  	ctx.SetBodyString(fmt.Sprintf("%d %s", statusCode, fasthttp.StatusMessage(statusCode)))  } diff --git a/internal/handlers/types.go b/internal/handlers/types.go index 33e096c25..4431c49da 100644 --- a/internal/handlers/types.go +++ b/internal/handlers/types.go @@ -1,8 +1,6 @@  package handlers  import ( -	"io" -  	"github.com/authelia/authelia/v4/internal/authentication"  ) @@ -115,8 +113,8 @@ type resetPasswordStep2RequestBody struct {  	Password string `json:"password"`  } -// PassworPolicyBody represents the response sent by the password reset step 2. -type PassworPolicyBody struct { +// PasswordPolicyBody represents the response sent by the password reset step 2. +type PasswordPolicyBody struct {  	Mode             string `json:"mode"`  	MinLength        int    `json:"min_length"`  	MaxLength        int    `json:"max_length"` @@ -126,12 +124,3 @@ type PassworPolicyBody struct {  	RequireNumber    bool   `json:"require_number"`  	RequireSpecial   bool   `json:"require_special"`  } - -type responseWriter interface { -	SetStatusCode(statusCode int) -	SetBodyString(body string) -	SetBody(body []byte) -	SetContentType(contentType string) -	SetContentTypeBytes(contentType []byte) -	SetBodyStream(bodyStream io.Reader, bodySize int) -} diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 000000000..82b490edf --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,20 @@ +package metrics + +import ( +	"time" + +	"github.com/authelia/authelia/v4/internal/regulation" +) + +// Provider implementation. +type Provider interface { +	Recorder +	regulation.MetricsRecorder +} + +// Recorder of metrics. +type Recorder interface { +	RecordRequest(statusCode, requestMethod string, elapsed time.Duration) +	RecordVerifyRequest(statusCode string) +	RecordAuthenticationDuration(success bool, elapsed time.Duration) +} diff --git a/internal/metrics/prometheus.go b/internal/metrics/prometheus.go new file mode 100644 index 000000000..c096e090f --- /dev/null +++ b/internal/metrics/prometheus.go @@ -0,0 +1,132 @@ +package metrics + +import ( +	"strconv" +	"time" + +	"github.com/prometheus/client_golang/prometheus" +	"github.com/prometheus/client_golang/prometheus/promauto" +) + +// NewPrometheus returns a new Prometheus metrics recorder. +func NewPrometheus() (provider *Prometheus) { +	provider = &Prometheus{} + +	provider.register() + +	return provider +} + +// Prometheus is a middleware for recording prometheus metrics. +type Prometheus struct { +	authDuration     *prometheus.HistogramVec +	reqDuration      *prometheus.HistogramVec +	reqCounter       *prometheus.CounterVec +	reqVerifyCounter *prometheus.CounterVec +	auth1FACounter   *prometheus.CounterVec +	auth2FACounter   *prometheus.CounterVec +} + +// RecordRequest takes the statusCode string, requestMethod string, and the elapsed time.Duration to record the request and request duration metrics. +func (p *Prometheus) RecordRequest(statusCode, requestMethod string, elapsed time.Duration) { +	if p.reqCounter == nil || p.reqDuration == nil { +		return +	} + +	p.reqCounter.WithLabelValues(statusCode, requestMethod).Inc() +	p.reqDuration.WithLabelValues(statusCode).Observe(elapsed.Seconds()) +} + +// RecordVerifyRequest takes the statusCode string to record the verify endpoint request metrics. +func (p *Prometheus) RecordVerifyRequest(statusCode string) { +	if p.reqVerifyCounter == nil { +		return +	} + +	p.reqVerifyCounter.WithLabelValues(statusCode).Inc() +} + +// RecordAuthentication takes the success and regulated booleans and a method string to record the authentication metrics. +func (p *Prometheus) RecordAuthentication(success, banned bool, authType string) { +	switch authType { +	case "1fa", "": +		if p.auth1FACounter == nil { +			return +		} + +		p.auth1FACounter.WithLabelValues(strconv.FormatBool(success), strconv.FormatBool(banned)).Inc() +	default: +		if p.auth2FACounter == nil { +			return +		} + +		p.auth2FACounter.WithLabelValues(strconv.FormatBool(success), strconv.FormatBool(banned), authType).Inc() +	} +} + +// RecordAuthenticationDuration takes the statusCode string, requestMethod string, and the elapsed time.Duration to record the request and request duration metrics. +func (p *Prometheus) RecordAuthenticationDuration(success bool, elapsed time.Duration) { +	if p.authDuration == nil { +		return +	} + +	p.authDuration.WithLabelValues(strconv.FormatBool(success)).Observe(elapsed.Seconds()) +} + +func (p *Prometheus) register() { +	p.authDuration = promauto.NewHistogramVec( +		prometheus.HistogramOpts{ +			Subsystem: "authelia", +			Name:      "authentication_duration_seconds", +			Help:      "The time an authentication attempt takes in seconds.", +			Buckets:   []float64{.0005, .00075, .001, .005, .01, .025, .05, .075, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 0.9, 1, 5, 10, 15, 30, 60}, +		}, +		[]string{"success"}, +	) + +	p.reqDuration = promauto.NewHistogramVec( +		prometheus.HistogramOpts{ +			Subsystem: "authelia", +			Name:      "request_duration_seconds", +			Help:      "The time a HTTP request takes to process in seconds.", +			Buckets:   []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 15, 20, 30, 40, 50, 60}, +		}, +		[]string{"code"}, +	) + +	p.reqCounter = promauto.NewCounterVec( +		prometheus.CounterOpts{ +			Subsystem: "authelia", +			Name:      "requests_total", +			Help:      "The number of HTTP requests processed.", +		}, +		[]string{"code", "method"}, +	) + +	p.reqVerifyCounter = promauto.NewCounterVec( +		prometheus.CounterOpts{ +			Subsystem: "authelia", +			Name:      "verify_requests_total", +			Help:      "The number of verify requests processed.", +		}, +		[]string{"code"}, +	) + +	p.auth1FACounter = promauto.NewCounterVec( +		prometheus.CounterOpts{ +			Subsystem: "authelia", +			Name:      "authentication_first_factor_total", +			Help:      "The number of 1FA authentications processed.", +		}, +		[]string{"success", "banned"}, +	) + +	p.auth2FACounter = promauto.NewCounterVec( +		prometheus.CounterOpts{ +			Subsystem: "authelia", +			Name:      "authentication_second_factor_total", +			Help:      "The number of 2FA authentications processed.", +		}, +		[]string{"success", "banned", "method"}, +	) +} diff --git a/internal/middlewares/authelia_context.go b/internal/middlewares/authelia_context.go index 28edbd3fd..8f73106c9 100644 --- a/internal/middlewares/authelia_context.go +++ b/internal/middlewares/authelia_context.go @@ -344,3 +344,12 @@ func (ctx *AutheliaCtx) SpecialRedirect(uri string, statusCode int) {  	fasthttp.ReleaseURI(u)  } + +// RecordAuthentication records authentication metrics. +func (ctx *AutheliaCtx) RecordAuthentication(success, regulated bool, method string) { +	if ctx.Providers.Metrics == nil { +		return +	} + +	ctx.Providers.Metrics.RecordAuthentication(success, regulated, method) +} diff --git a/internal/middlewares/identity_verification.go b/internal/middlewares/identity_verification.go index 2c53d354b..94c2abd94 100644 --- a/internal/middlewares/identity_verification.go +++ b/internal/middlewares/identity_verification.go @@ -24,7 +24,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim  		success := false  		if delayFunc != nil { -			defer delayFunc(ctx.Logger, requestTime, &success) +			defer delayFunc(ctx, requestTime, &success)  		}  		identity, err := args.IdentityRetrieverFunc(ctx) diff --git a/internal/middlewares/log_request.go b/internal/middlewares/log_request.go index 1790ba7dc..c632a03b6 100644 --- a/internal/middlewares/log_request.go +++ b/internal/middlewares/log_request.go @@ -4,7 +4,7 @@ import (  	"github.com/valyala/fasthttp"  ) -// LogRequest logs the query that is being treated. +// LogRequest provides trace logging for all requests.  func LogRequest(next fasthttp.RequestHandler) fasthttp.RequestHandler {  	return func(ctx *fasthttp.RequestCtx) {  		autheliaCtx := &AutheliaCtx{RequestCtx: ctx} diff --git a/internal/middlewares/metrics.go b/internal/middlewares/metrics.go new file mode 100644 index 000000000..f908cc4ec --- /dev/null +++ b/internal/middlewares/metrics.go @@ -0,0 +1,47 @@ +package middlewares + +import ( +	"strconv" +	"time" + +	"github.com/valyala/fasthttp" + +	"github.com/authelia/authelia/v4/internal/metrics" +) + +// NewMetricsRequest returns a middleware if provided with a metrics.Recorder, otherwise it returns nil. +func NewMetricsRequest(metrics metrics.Recorder) (middleware Basic) { +	if metrics == nil { +		return nil +	} + +	return func(next fasthttp.RequestHandler) (handler fasthttp.RequestHandler) { +		return func(ctx *fasthttp.RequestCtx) { +			started := time.Now() + +			next(ctx) + +			statusCode := strconv.Itoa(ctx.Response.StatusCode()) +			requestMethod := string(ctx.Method()) + +			metrics.RecordRequest(statusCode, requestMethod, time.Since(started)) +		} +	} +} + +// NewMetricsVerifyRequest returns a middleware if provided with a metrics.Recorder, otherwise it returns nil. +func NewMetricsVerifyRequest(metrics metrics.Recorder) (middleware Basic) { +	if metrics == nil { +		return nil +	} + +	return func(next fasthttp.RequestHandler) (handler fasthttp.RequestHandler) { +		return func(ctx *fasthttp.RequestCtx) { +			next(ctx) + +			statusCode := strconv.Itoa(ctx.Response.StatusCode()) + +			metrics.RecordVerifyRequest(statusCode) +		} +	} +} diff --git a/internal/middlewares/timing_attack_delay.go b/internal/middlewares/timing_attack_delay.go index 60339b0ce..163f8752c 100644 --- a/internal/middlewares/timing_attack_delay.go +++ b/internal/middlewares/timing_attack_delay.go @@ -6,15 +6,13 @@ import (  	"math/big"  	"sync"  	"time" - -	"github.com/sirupsen/logrus"  )  // TimingAttackDelayFunc describes a function for preventing timing attacks via a delay. -type TimingAttackDelayFunc func(logger *logrus.Entry, requestTime time.Time, successful *bool) +type TimingAttackDelayFunc func(ctx *AutheliaCtx, requestTime time.Time, successful *bool)  // TimingAttackDelay creates a new standard timing delay func. -func TimingAttackDelay(history int, minDelayMs float64, maxRandomMs int64, initialDelay time.Duration) TimingAttackDelayFunc { +func TimingAttackDelay(history int, minDelayMs float64, maxRandomMs int64, initialDelay time.Duration, record bool) TimingAttackDelayFunc {  	var (  		mutex  = &sync.Mutex{}  		cursor = 0 @@ -26,15 +24,20 @@ func TimingAttackDelay(history int, minDelayMs float64, maxRandomMs int64, initi  		execDurationMovingAverage[i] = initialDelay  	} -	return func(logger *logrus.Entry, requestTime time.Time, successful *bool) { +	return func(ctx *AutheliaCtx, requestTime time.Time, successful *bool) {  		successfulValue := false  		if successful != nil {  			successfulValue = *successful  		}  		execDuration := time.Since(requestTime) + +		if record && ctx.Providers.Metrics != nil { +			ctx.Providers.Metrics.RecordAuthenticationDuration(successfulValue, execDuration) +		} +  		execDurationAvgMs := movingAverageIteration(execDuration, history, successfulValue, &cursor, &execDurationMovingAverage, mutex) -		actualDelayMs := calculateActualDelay(logger, execDuration, execDurationAvgMs, minDelayMs, maxRandomMs, successfulValue) +		actualDelayMs := calculateActualDelay(ctx, execDuration, execDurationAvgMs, minDelayMs, maxRandomMs, successfulValue)  		time.Sleep(time.Duration(actualDelayMs) * time.Millisecond)  	}  } @@ -58,7 +61,7 @@ func movingAverageIteration(value time.Duration, history int, successful bool, c  	return float64(sum / int64(history))  } -func calculateActualDelay(logger *logrus.Entry, execDuration time.Duration, execDurationAvgMs, minDelayMs float64, maxRandomMs int64, successful bool) (actualDelayMs float64) { +func calculateActualDelay(ctx *AutheliaCtx, execDuration time.Duration, execDurationAvgMs, minDelayMs float64, maxRandomMs int64, successful bool) (actualDelayMs float64) {  	randomDelayMs, err := rand.Int(rand.Reader, big.NewInt(maxRandomMs))  	if err != nil {  		return float64(maxRandomMs) @@ -66,7 +69,7 @@ func calculateActualDelay(logger *logrus.Entry, execDuration time.Duration, exec  	totalDelayMs := math.Max(execDurationAvgMs, minDelayMs) + float64(randomDelayMs.Int64())  	actualDelayMs = math.Max(totalDelayMs-float64(execDuration.Milliseconds()), 1.0) -	logger.Tracef("Timing Attack Delay successful: %t, exec duration: %d, avg execution duration: %d, random delay ms: %d, total delay ms: %d, actual delay ms: %d", successful, execDuration.Milliseconds(), int64(execDurationAvgMs), randomDelayMs.Int64(), int64(totalDelayMs), int64(actualDelayMs)) +	ctx.Logger.Tracef("Timing Attack Delay successful: %t, exec duration: %d, avg execution duration: %d, random delay ms: %d, total delay ms: %d, actual delay ms: %d", successful, execDuration.Milliseconds(), int64(execDurationAvgMs), randomDelayMs.Int64(), int64(totalDelayMs), int64(actualDelayMs))  	return actualDelayMs  } diff --git a/internal/middlewares/timing_attack_delay_test.go b/internal/middlewares/timing_attack_delay_test.go index 88243de12..d7428a1b0 100644 --- a/internal/middlewares/timing_attack_delay_test.go +++ b/internal/middlewares/timing_attack_delay_test.go @@ -45,10 +45,10 @@ func TestTimingAttackDelayCalculations(t *testing.T) {  	avgExecDurationMs := 1000.0  	expectedMinimumDelayMs := avgExecDurationMs - float64(execDuration.Milliseconds()) -	logger := logging.Logger().WithFields(logrus.Fields{}) +	ctx := &AutheliaCtx{Logger: logging.Logger().WithFields(logrus.Fields{})}  	for i := 0; i < 100; i++ { -		delay := calculateActualDelay(logger, execDuration, avgExecDurationMs, 250, 85, false) +		delay := calculateActualDelay(ctx, execDuration, avgExecDurationMs, 250, 85, false)  		assert.True(t, delay >= expectedMinimumDelayMs)  		assert.True(t, delay <= expectedMinimumDelayMs+float64(85))  	} @@ -58,7 +58,7 @@ func TestTimingAttackDelayCalculations(t *testing.T) {  	expectedMinimumDelayMs = 250 - float64(execDuration.Milliseconds())  	for i := 0; i < 100; i++ { -		delay := calculateActualDelay(logger, execDuration, avgExecDurationMs, 250, 85, false) +		delay := calculateActualDelay(ctx, execDuration, avgExecDurationMs, 250, 85, false)  		assert.True(t, delay >= expectedMinimumDelayMs)  		assert.True(t, delay <= expectedMinimumDelayMs+float64(250))  	} diff --git a/internal/middlewares/types.go b/internal/middlewares/types.go index f485e0c3f..0e2f172e8 100644 --- a/internal/middlewares/types.go +++ b/internal/middlewares/types.go @@ -7,6 +7,7 @@ import (  	"github.com/authelia/authelia/v4/internal/authentication"  	"github.com/authelia/authelia/v4/internal/authorization"  	"github.com/authelia/authelia/v4/internal/configuration/schema" +	"github.com/authelia/authelia/v4/internal/metrics"  	"github.com/authelia/authelia/v4/internal/notification"  	"github.com/authelia/authelia/v4/internal/ntp"  	"github.com/authelia/authelia/v4/internal/oidc" @@ -34,6 +35,7 @@ type Providers struct {  	SessionProvider *session.Provider  	Regulator       *regulation.Regulator  	OpenIDConnect   oidc.OpenIDConnectProvider +	Metrics         metrics.Provider  	NTP             *ntp.Provider  	UserProvider    authentication.UserProvider  	StorageProvider storage.Provider @@ -63,6 +65,9 @@ type BridgeBuilder struct {  	postMiddlewares []AutheliaMiddleware  } +// Basic represents a middleware applied to a fasthttp.RequestHandler. +type Basic func(next fasthttp.RequestHandler) (handler fasthttp.RequestHandler) +  // IdentityVerificationStartArgs represent the arguments used to customize the starting phase  // of the identity verification process.  type IdentityVerificationStartArgs struct { diff --git a/internal/middlewares/wrap.go b/internal/middlewares/wrap.go new file mode 100644 index 000000000..d4f07d1fe --- /dev/null +++ b/internal/middlewares/wrap.go @@ -0,0 +1,14 @@ +package middlewares + +import ( +	"github.com/valyala/fasthttp" +) + +// Wrap a handler with another middleware if it isn't nil. +func Wrap(middleware Basic, next fasthttp.RequestHandler) (handler fasthttp.RequestHandler) { +	if middleware == nil { +		return next +	} + +	return middleware(next) +} diff --git a/internal/regulation/regulator.go b/internal/regulation/regulator.go index 4e66fef23..e7e2ba37a 100644 --- a/internal/regulation/regulator.go +++ b/internal/regulation/regulator.go @@ -2,7 +2,7 @@ package regulation  import (  	"context" -	"net" +	"strings"  	"time"  	"github.com/authelia/authelia/v4/internal/configuration/schema" @@ -23,14 +23,16 @@ func NewRegulator(config schema.RegulationConfiguration, provider storage.Regula  // Mark an authentication attempt.  // We split Mark and Regulate in order to avoid timing attacks. -func (r *Regulator) Mark(ctx context.Context, successful, banned bool, username, requestURI, requestMethod, authType string, remoteIP net.IP) error { +func (r *Regulator) Mark(ctx Context, successful, banned bool, username, requestURI, requestMethod, authType string) error { +	ctx.RecordAuthentication(successful, banned, strings.ToLower(authType)) +  	return r.storageProvider.AppendAuthenticationLog(ctx, model.AuthenticationAttempt{  		Time:          r.clock.Now(),  		Successful:    successful,  		Banned:        banned,  		Username:      username,  		Type:          authType, -		RemoteIP:      model.NewNullIP(remoteIP), +		RemoteIP:      model.NewNullIP(ctx.RemoteIP()),  		RequestURI:    requestURI,  		RequestMethod: requestMethod,  	}) diff --git a/internal/regulation/types.go b/internal/regulation/types.go index 510ac2226..3e902a78d 100644 --- a/internal/regulation/types.go +++ b/internal/regulation/types.go @@ -1,6 +1,9 @@  package regulation  import ( +	"context" +	"net" +  	"github.com/authelia/authelia/v4/internal/configuration/schema"  	"github.com/authelia/authelia/v4/internal/storage"  	"github.com/authelia/authelia/v4/internal/utils" @@ -17,3 +20,16 @@ type Regulator struct {  	clock utils.Clock  } + +// Context represents a regulator context. +type Context interface { +	context.Context +	MetricsRecorder + +	RemoteIP() (ip net.IP) +} + +// MetricsRecorder represents the methods used to record regulation. +type MetricsRecorder interface { +	RecordAuthentication(success, banned bool, authType string) +} diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 5e696630e..a7f5798c9 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -1,7 +1,6 @@  package server  import ( -	"fmt"  	"net"  	"os"  	"strconv" @@ -10,9 +9,11 @@ import (  	duoapi "github.com/duosecurity/duo_api_golang"  	"github.com/fasthttp/router" +	"github.com/prometheus/client_golang/prometheus/promhttp"  	"github.com/sirupsen/logrus"  	"github.com/valyala/fasthttp"  	"github.com/valyala/fasthttp/expvarhandler" +	"github.com/valyala/fasthttp/fasthttpadaptor"  	"github.com/valyala/fasthttp/pprofhandler"  	"github.com/authelia/authelia/v4/internal/configuration/schema" @@ -70,10 +71,7 @@ func handleError() func(ctx *fasthttp.RequestCtx, err error) {  			"status_code": statusCode,  		}).WithError(err).Error(message) -		ctx.Response.Reset() -		ctx.SetStatusCode(statusCode) -		ctx.SetContentType("text/plain; charset=utf-8") -		ctx.SetBodyString(fmt.Sprintf("%d %s", statusCode, fasthttp.StatusMessage(statusCode))) +		handlers.SetStatusCodeResponse(ctx, statusCode)  	}  } @@ -93,10 +91,6 @@ func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler {  	}  } -func handleMethodNotAllowed(ctx *fasthttp.RequestCtx) { -	handlers.SetStatusCodeResponse(ctx, fasthttp.StatusMethodNotAllowed) -} -  func handleRouter(config schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {  	rememberMe := strconv.FormatBool(config.Session.RememberMeDuration != schema.RememberMeDisabled)  	resetPassword := strconv.FormatBool(!config.AuthenticationBackend.DisableResetPassword) @@ -168,12 +162,14 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)  	r.GET("/api/configuration/password-policy", middlewareAPI(handlers.PasswordPolicyConfigurationGET)) -	r.GET("/api/verify", middlewareAPI(handlers.VerifyGET(config.AuthenticationBackend))) -	r.HEAD("/api/verify", middlewareAPI(handlers.VerifyGET(config.AuthenticationBackend))) +	metricsVRMW := middlewares.NewMetricsVerifyRequest(providers.Metrics) + +	r.GET("/api/verify", middlewares.Wrap(metricsVRMW, middleware(handlers.VerifyGET(config.AuthenticationBackend)))) +	r.HEAD("/api/verify", middlewares.Wrap(metricsVRMW, middleware(handlers.VerifyGET(config.AuthenticationBackend))))  	r.POST("/api/checks/safe-redirection", middlewareAPI(handlers.CheckSafeRedirectionPOST)) -	delayFunc := middlewares.TimingAttackDelay(10, 250, 85, time.Second) +	delayFunc := middlewares.TimingAttackDelay(10, 250, 85, time.Second, true)  	r.POST("/api/firstfactor", middlewareAPI(handlers.FirstFactorPOST(delayFunc)))  	r.POST("/api/logout", middlewareAPI(handlers.LogoutPOST)) @@ -324,14 +320,28 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)  		r.POST("/api/oidc/revoke", policyCORSRevocation.Middleware(middlewareOIDC(middlewares.NewHTTPToAutheliaHandlerAdaptor(handlers.OAuthRevocationPOST))))  	} -	r.NotFound = handleNotFound(middleware(serveIndexHandler)) -  	r.HandleMethodNotAllowed = true -	r.MethodNotAllowed = handleMethodNotAllowed +	r.MethodNotAllowed = handlers.Status(fasthttp.StatusMethodNotAllowed) +	r.NotFound = handleNotFound(middleware(serveIndexHandler)) +	handler := middlewares.LogRequest(r.Handler)  	if config.Server.Path != "" { -		return middlewares.StripPath(config.Server.Path)(middlewares.LogRequest(r.Handler)) +		handler = middlewares.StripPath(config.Server.Path)(handler)  	} -	return middlewares.LogRequest(r.Handler) +	handler = middlewares.Wrap(middlewares.NewMetricsRequest(providers.Metrics), handler) + +	return handler +} + +func handleMetrics() fasthttp.RequestHandler { +	r := router.New() + +	r.GET("/metrics", fasthttpadaptor.NewFastHTTPHandler(promhttp.Handler())) + +	r.HandleMethodNotAllowed = true +	r.MethodNotAllowed = handlers.Status(fasthttp.StatusMethodNotAllowed) +	r.NotFound = handlers.Status(fasthttp.StatusNotFound) + +	return r.Handler  } diff --git a/internal/server/server.go b/internal/server/server.go index cdc450c04..a71a36498 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -3,6 +3,7 @@ package server  import (  	"crypto/tls"  	"crypto/x509" +	"fmt"  	"net"  	"os"  	"strconv" @@ -14,9 +15,9 @@ import (  	"github.com/authelia/authelia/v4/internal/middlewares"  ) -// CreateServer Create Authelia's internal webserver with the given configuration and providers. -func CreateServer(config schema.Configuration, providers middlewares.Providers) (*fasthttp.Server, net.Listener) { -	server := &fasthttp.Server{ +// CreateDefaultServer Create Authelia's internal webserver with the given configuration and providers. +func CreateDefaultServer(config schema.Configuration, providers middlewares.Providers) (server *fasthttp.Server, listener net.Listener, err error) { +	server = &fasthttp.Server{  		ErrorHandler:          handleError(),  		Handler:               handleRouter(config, providers),  		NoDefaultServerHeader: true, @@ -24,13 +25,9 @@ func CreateServer(config schema.Configuration, providers middlewares.Providers)  		WriteBufferSize:       config.Server.WriteBufferSize,  	} -	logger := logging.Logger() -  	address := net.JoinHostPort(config.Server.Host, strconv.Itoa(config.Server.Port))  	var ( -		listener         net.Listener -		err              error  		connectionType   string  		connectionScheme string  	) @@ -39,16 +36,17 @@ func CreateServer(config schema.Configuration, providers middlewares.Providers)  		connectionType, connectionScheme = "TLS", schemeHTTPS  		if err = server.AppendCert(config.Server.TLS.Certificate, config.Server.TLS.Key); err != nil { -			logger.Fatalf("unable to load certificate: %v", err) +			return nil, nil, fmt.Errorf("unable to load tls server certificate '%s' or private key '%s': %w", config.Server.TLS.Certificate, config.Server.TLS.Key, err)  		}  		if len(config.Server.TLS.ClientCertificates) > 0 {  			caCertPool := x509.NewCertPool() +			var cert []byte +  			for _, path := range config.Server.TLS.ClientCertificates { -				cert, err := os.ReadFile(path) -				if err != nil { -					logger.Fatalf("Cannot read client TLS certificate %s: %s", path, err) +				if cert, err = os.ReadFile(path); err != nil { +					return nil, nil, fmt.Errorf("unable to load tls client certificate '%s': %w", path, err)  				}  				caCertPool.AppendCertsFromPEM(cert) @@ -61,26 +59,43 @@ func CreateServer(config schema.Configuration, providers middlewares.Providers)  		}  		if listener, err = tls.Listen("tcp", address, server.TLSConfig.Clone()); err != nil { -			logger.Fatalf("Error initializing listener: %s", err) +			return nil, nil, fmt.Errorf("unable to initialize tcp listener: %w", err)  		}  	} else {  		connectionType, connectionScheme = "non-TLS", schemeHTTP  		if listener, err = net.Listen("tcp", address); err != nil { -			logger.Fatalf("Error initializing listener: %s", err) +			return nil, nil, fmt.Errorf("unable to initialize tcp listener: %w", err)  		}  	}  	if err = writeHealthCheckEnv(config.Server.DisableHealthcheck, connectionScheme, config.Server.Host,  		config.Server.Path, config.Server.Port); err != nil { -		logger.Fatalf("Could not configure healthcheck: %v", err) +		return nil, nil, fmt.Errorf("unable to configure healthcheck: %w", err)  	} +	logger := logging.Logger() +  	if config.Server.Path == "" {  		logger.Infof("Initializing server for %s connections on '%s' path '/'", connectionType, listener.Addr().String())  	} else {  		logger.Infof("Initializing server for %s connections on '%s' paths '/' and '%s'", connectionType, listener.Addr().String(), config.Server.Path)  	} -	return server, listener +	return server, listener, nil +} + +// CreateMetricsServer creates a metrics server. +func CreateMetricsServer(config schema.TelemetryMetricsConfig) (server *fasthttp.Server, listener net.Listener, err error) { +	if listener, err = config.Address.Listener(); err != nil { +		return nil, nil, err +	} + +	server = &fasthttp.Server{ +		ErrorHandler:          handleError(), +		NoDefaultServerHeader: true, +		Handler:               handleMetrics(), +	} + +	return server, listener, nil  } diff --git a/internal/server/server_test.go b/internal/server/server_test.go index ef166833b..ad5bf174d 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -137,7 +137,12 @@ type TLSServerContext struct {  func NewTLSServerContext(configuration schema.Configuration) (*TLSServerContext, error) {  	serverContext := new(TLSServerContext) -	s, listener := CreateServer(configuration, middlewares.Providers{}) +	s, listener, err := CreateDefaultServer(configuration, middlewares.Providers{}) + +	if err != nil { +		return nil, err +	} +  	serverContext.server = s  	go func() { diff --git a/internal/suites/Standalone/configuration.yml b/internal/suites/Standalone/configuration.yml index 919f26931..aaddbd831 100644 --- a/internal/suites/Standalone/configuration.yml +++ b/internal/suites/Standalone/configuration.yml @@ -11,6 +11,10 @@ server:      certificate: /config/ssl/cert.pem      key: /config/ssl/key.pem +telemetry: +  metrics: +    enabled: true +  log:    level: debug diff --git a/internal/suites/example/compose/nginx/portal/nginx.conf b/internal/suites/example/compose/nginx/portal/nginx.conf index 5d7e4f641..9354f6f32 100644 --- a/internal/suites/example/compose/nginx/portal/nginx.conf +++ b/internal/suites/example/compose/nginx/portal/nginx.conf @@ -15,6 +15,7 @@ http {          resolver 127.0.0.11 ipv6=off;          set $frontend_endpoint http://authelia-frontend:3000;          set $backend_endpoint https://authelia-backend:9091; +        set $metrics_endpoint http://authelia-backend:9959;          ssl_certificate     /etc/ssl/server.cert;          ssl_certificate_key /etc/ssl/server.key; @@ -99,6 +100,10 @@ http {              proxy_pass        $backend_endpoint;          } +        location /metrics { +            proxy_pass        $metrics_endpoint; +        } +          # Serves the portal application.          location / {              # Allow websockets for webpack to auto-reload. @@ -135,7 +140,7 @@ http {      # Example configuration of domains protected by Authelia.      server { -        listen 8080 ssl;  +        listen 8080 ssl;          server_name     public.example.com                          admin.example.com                          secure.example.com @@ -160,10 +165,10 @@ http {          # to the virtual endpoint introduced by nginx and declared in the next block.          location / {              auth_request /auth_verify; -             +              auth_request_set            $user $upstream_http_remote_user;              proxy_set_header            Remote-User $user; -             +              auth_request_set            $groups $upstream_http_remote_groups;              proxy_set_header            Remote-Groups $groups; @@ -229,7 +234,7 @@ http {              auth_request_set            $user $upstream_http_remote_user;              proxy_set_header            Remote-User $user; -             +              auth_request_set            $groups $upstream_http_remote_groups;              proxy_set_header            Remote-Groups $groups; @@ -248,7 +253,7 @@ http {          # Example configuration of domains protected by Authelia.      server { -        listen 8080 ssl;  +        listen 8080 ssl;          server_name     oidc.example.com                          oidc-public.example.com; @@ -300,7 +305,7 @@ http {              proxy_set_header            X-Forwarded-Proto $scheme;              proxy_set_header            X-Forwarded-Host $http_host;              proxy_set_header            X-Forwarded-URI $request_uri; -             +              proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;              # Authelia can receive Proxy-Authorization to authenticate however most of the clients diff --git a/internal/suites/suite_standalone_test.go b/internal/suites/suite_standalone_test.go index acce29856..d5678537f 100644 --- a/internal/suites/suite_standalone_test.go +++ b/internal/suites/suite_standalone_test.go @@ -263,6 +263,34 @@ func (s *StandaloneSuite) TestShouldVerifyAPIVerifyRedirectFromXOriginalHostURI(  	s.Assert().Equal(fmt.Sprintf("<a href=\"%s\">Found</a>", utils.StringHTMLEscape(fmt.Sprintf("%s/?rd=%s", GetLoginBaseURL(), urlEncodedAdminURL))), string(body))  } +func (s *StandaloneSuite) TestShouldRecordMetrics() { +	client := NewHTTPClient() + +	req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/health", LoginBaseURL), nil) +	s.Require().NoError(err) + +	res, err := client.Do(req) +	s.Require().NoError(err) +	s.Assert().Equal(res.StatusCode, 200) + +	req, err = http.NewRequest("GET", fmt.Sprintf("%s/metrics", LoginBaseURL), nil) +	s.Require().NoError(err) + +	res, err = client.Do(req) +	s.Require().NoError(err) +	s.Assert().Equal(res.StatusCode, 200) + +	body, err := io.ReadAll(res.Body) +	s.Require().NoError(err) + +	metrics := string(body) + +	s.Assert().Contains(metrics, "authelia_request_duration_seconds_bucket{") +	s.Assert().Contains(metrics, "authelia_request_duration_seconds_sum{") +	s.Assert().Contains(metrics, "go_gc_cycles_forced_gc_cycles_total") +	s.Assert().Contains(metrics, "go_gc_cycles_total_gc_cycles_total") +} +  func (s *StandaloneSuite) TestStandaloneWebDriverScenario() {  	suite.Run(s.T(), NewStandaloneWebDriverSuite())  } diff --git a/web/.commitlintrc.js b/web/.commitlintrc.js index d788032f2..7b4eeb7f0 100644 --- a/web/.commitlintrc.js +++ b/web/.commitlintrc.js @@ -31,6 +31,7 @@ module.exports = {                  "golangci-lint",                  "handlers",                  "logging", +                "metrics",                  "middlewares",                  "mocks",                  "model",  | 
