From 001589cd6d2bb1d7c740624ff4699c442fc5fcb2 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Tue, 14 Jun 2022 17:20:13 +1000 Subject: [PATCH] feat(metrics): implement prometheus metrics (#3234) Adds ability to record metrics and gather them for Prometheus. --- cmd/authelia-scripts/cmd_gen.go | 1 + config.template.yml | 15 ++ go.mod | 7 +- go.sum | 159 +++++++++++++++++- internal/commands/helpers.go | 7 + internal/commands/root.go | 61 ++++++- internal/configuration/config.template.yml | 15 ++ internal/configuration/decode_hooks.go | 41 +++++ internal/configuration/provider.go | 1 + .../configuration/schema/configuration.go | 1 + internal/configuration/schema/keys.go | 2 + internal/configuration/schema/telemetry.go | 23 +++ internal/configuration/schema/types.go | 111 ++++++++++++ .../configuration/validator/configuration.go | 2 + internal/configuration/validator/telemetry.go | 14 ++ internal/handlers/const.go | 4 + .../handler_configuration_password_policy.go | 2 +- internal/handlers/handler_firstfactor.go | 2 +- .../handlers/handler_reset_password_step1.go | 2 +- internal/handlers/handler_status.go | 12 ++ internal/handlers/response.go | 6 +- internal/handlers/types.go | 15 +- internal/metrics/metrics.go | 20 +++ internal/metrics/prometheus.go | 132 +++++++++++++++ internal/middlewares/authelia_context.go | 9 + internal/middlewares/identity_verification.go | 2 +- internal/middlewares/log_request.go | 2 +- internal/middlewares/metrics.go | 47 ++++++ internal/middlewares/timing_attack_delay.go | 19 ++- .../middlewares/timing_attack_delay_test.go | 6 +- internal/middlewares/types.go | 5 + internal/middlewares/wrap.go | 14 ++ internal/regulation/regulator.go | 8 +- internal/regulation/types.go | 16 ++ internal/server/handlers.go | 44 +++-- internal/server/server.go | 45 +++-- internal/server/server_test.go | 7 +- internal/suites/Standalone/configuration.yml | 4 + .../example/compose/nginx/portal/nginx.conf | 17 +- internal/suites/suite_standalone_test.go | 28 +++ web/.commitlintrc.js | 1 + 41 files changed, 852 insertions(+), 77 deletions(-) create mode 100644 internal/configuration/schema/telemetry.go create mode 100644 internal/configuration/schema/types.go create mode 100644 internal/configuration/validator/telemetry.go create mode 100644 internal/handlers/handler_status.go create mode 100644 internal/metrics/metrics.go create mode 100644 internal/metrics/prometheus.go create mode 100644 internal/middlewares/metrics.go create mode 100644 internal/middlewares/wrap.go 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 @@ -101,6 +101,21 @@ log: ## Whether to also log to stdout when a log_file_path is defined. # 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 ## diff --git a/go.mod b/go.mod index 8793ecfee..92951108b 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index beca7af51..1a2414524 100644 --- a/go.sum +++ b/go.sum @@ -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) +} - logger.Fatal(s.Serve(listener)) +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) + } + + 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 @@ -101,6 +101,21 @@ log: ## Whether to also log to stdout when a log_file_path is defined. # 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 ## 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\w+)://)?((?P((((25[0-5]|2[0-4]\d|[01]?\d\d?)(\.)){3})(25[0-5]|2[0-4]\d|[01]?\d\d?)))|(\[(?P([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\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.HandleMethodNotAllowed = true + r.MethodNotAllowed = handlers.Status(fasthttp.StatusMethodNotAllowed) r.NotFound = handleNotFound(middleware(serveIndexHandler)) - r.HandleMethodNotAllowed = true - r.MethodNotAllowed = handleMethodNotAllowed - + 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("Found", 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",