From 0ef29021f9e210cfa9cb6b8d930e21afb43510ec Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Fri, 8 Mar 2024 09:39:08 +0000 Subject: [PATCH] Make Middleware Common (#7) This is shared across all server components, so rationalize it in one place. --- charts/core/Chart.yaml | 4 +- go.mod | 52 +-- go.sum | 133 ++++---- pkg/authorization/oauth2/claims/context.go | 57 ++++ pkg/authorization/oauth2/claims/types.go | 36 +++ pkg/authorization/oauth2/scope/scope.go | 57 ++++ pkg/server/middleware/cors/cors.go | 101 ++++++ .../middleware/openapi/authorization.go | 168 ++++++++++ pkg/server/middleware/openapi/openapi.go | 191 ++++++++++++ pkg/server/middleware/openapi/schema.go | 72 +++++ .../middleware/opentelemetry/opentelemetry.go | 295 ++++++++++++++++++ pkg/server/middleware/timeout/timeout.go | 36 +++ 12 files changed, 1122 insertions(+), 80 deletions(-) create mode 100644 pkg/authorization/oauth2/claims/context.go create mode 100644 pkg/authorization/oauth2/claims/types.go create mode 100644 pkg/authorization/oauth2/scope/scope.go create mode 100644 pkg/server/middleware/cors/cors.go create mode 100644 pkg/server/middleware/openapi/authorization.go create mode 100644 pkg/server/middleware/openapi/openapi.go create mode 100644 pkg/server/middleware/openapi/schema.go create mode 100644 pkg/server/middleware/opentelemetry/opentelemetry.go create mode 100644 pkg/server/middleware/timeout/timeout.go diff --git a/charts/core/Chart.yaml b/charts/core/Chart.yaml index 8669a0e..400ddab 100644 --- a/charts/core/Chart.yaml +++ b/charts/core/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart for deploying Unikorn Core type: application -version: v0.1.6 -appVersion: v0.1.6 +version: v0.1.7 +appVersion: v0.1.7 icon: https://assets.unikorn-cloud.org/images/logos/dark-on-light/icon.svg diff --git a/go.mod b/go.mod index c948fec..2187fb5 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,21 @@ module github.com/unikorn-cloud/core go 1.21.1 require ( - github.com/getkin/kin-openapi v0.122.0 + github.com/coreos/go-oidc/v3 v3.9.0 + github.com/getkin/kin-openapi v0.123.0 + github.com/go-jose/go-jose/v3 v3.0.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/sdk v1.22.0 + go.opentelemetry.io/otel/trace v1.24.0 go.uber.org/mock v0.4.0 - golang.org/x/sync v0.5.0 - k8s.io/api v0.29.0 - k8s.io/apimachinery v0.29.0 - k8s.io/client-go v0.29.0 - k8s.io/klog/v2 v2.120.0 - sigs.k8s.io/controller-runtime v0.16.3 + golang.org/x/sync v0.6.0 + k8s.io/api v0.29.1 + k8s.io/apimachinery v0.29.1 + k8s.io/client-go v0.29.1 + k8s.io/klog/v2 v2.120.1 + sigs.k8s.io/controller-runtime v0.17.0 sigs.k8s.io/structured-merge-diff/v4 v4.4.1 sigs.k8s.io/yaml v1.4.0 ) @@ -21,28 +26,29 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch v5.7.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.7.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.2 // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect - github.com/go-openapi/swag v0.22.7 // indirect + github.com/go-openapi/swag v0.22.9 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/invopop/yaml v0.2.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -52,16 +58,18 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/oauth2 v0.15.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/term v0.15.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect @@ -70,8 +78,8 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.29.0 // indirect - k8s.io/kube-openapi v0.0.0-20231214164306-ab13479f8bf8 // indirect - k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect + k8s.io/component-base v0.29.1 // indirect + k8s.io/kube-openapi v0.0.0-20240126223410-2919ad4fcfec // indirect + k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect ) diff --git a/go.sum b/go.sum index 4c8b1b7..44b2e71 100644 --- a/go.sum +++ b/go.sum @@ -2,29 +2,36 @@ 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/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= +github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= -github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/emicklei/go-restful/v3 v3.11.2 h1:1onLa9DcsMYO9P+CXaL0dStDqQ2EHHXLiz+BtnqkLAU= +github.com/emicklei/go-restful/v3 v3.11.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10= -github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8= +github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= -github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= -github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= @@ -39,6 +46,7 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -48,8 +56,10 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= @@ -66,8 +76,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -77,10 +85,10 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= +github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -91,16 +99,17 @@ github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+ github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= @@ -108,8 +117,16 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -117,11 +134,14 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= +golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -131,16 +151,16 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL 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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= 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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -148,12 +168,12 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -167,8 +187,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -189,27 +209,28 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= -k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= -k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= -k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= -k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= -k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= -k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= -k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= -k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= -k8s.io/klog/v2 v2.120.0 h1:z+q5mfovBj1fKFxiRzsa2DsJLPIVMk/KFL81LMOfK+8= -k8s.io/klog/v2 v2.120.0/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20231214164306-ab13479f8bf8 h1:yHNkNuLjht7iq95pO9QmbjOWCguvn8mDe3lT78nqPkw= -k8s.io/kube-openapi v0.0.0-20231214164306-ab13479f8bf8/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= -k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= -sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= +k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= +k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= +k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= +k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= +k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= +k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= +k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= +k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240126223410-2919ad4fcfec h1:iGTel2aR8vCZdxJDgmbeY0zrlXy9Qcvyw4R2sB4HLrA= +k8s.io/kube-openapi v0.0.0-20240126223410-2919ad4fcfec/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.17.0 h1:fjJQf8Ukya+VjogLO6/bNX9HE6Y2xpsO5+fyS26ur/s= +sigs.k8s.io/controller-runtime v0.17.0/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/pkg/authorization/oauth2/claims/context.go b/pkg/authorization/oauth2/claims/context.go new file mode 100644 index 0000000..1d105b6 --- /dev/null +++ b/pkg/authorization/oauth2/claims/context.go @@ -0,0 +1,57 @@ +/* +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package claims + +import ( + "context" + "errors" + "fmt" +) + +var ( + // ErrContextError is raised when a required value cannot be retrieved + // from a context. + ErrContextError = errors.New("value missing from context") +) + +// contextKey defines a new context key type unique to this package. +type contextKey int + +const ( + // claimsKey is used to store claims in a context. + claimsKey contextKey = iota +) + +// NewContext injects the given claims into a new context. +func NewContext(ctx context.Context, claims *Claims) context.Context { + return context.WithValue(ctx, claimsKey, claims) +} + +// FromContext extracts the claims from a context. +func FromContext(ctx context.Context) (*Claims, error) { + value := ctx.Value(claimsKey) + if value == nil { + return nil, fmt.Errorf("%w: unable to find claims", ErrContextError) + } + + claims, ok := value.(*Claims) + if !ok { + return nil, fmt.Errorf("%w: unable to assert claims", ErrContextError) + } + + return claims, nil +} diff --git a/pkg/authorization/oauth2/claims/types.go b/pkg/authorization/oauth2/claims/types.go new file mode 100644 index 0000000..96d813c --- /dev/null +++ b/pkg/authorization/oauth2/claims/types.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package claims + +import ( + "github.com/go-jose/go-jose/v3/jwt" + + "github.com/unikorn-cloud/core/pkg/authorization/oauth2/scope" +) + +// Claims is an application specific set of claims. +// TODO: this technically isn't conformant to oauth2 in that we don't specify +// the client_id claim, and there are probably others. +type Claims struct { + jwt.Claims `json:",inline"` + + // Organization is the top level organization the user belongs to. + Organization string `json:"org"` + + // Scope is the oauth2 scope of the token. + Scope scope.Scope `json:"scope,omitempty"` +} diff --git a/pkg/authorization/oauth2/scope/scope.go b/pkg/authorization/oauth2/scope/scope.go new file mode 100644 index 0000000..3ce396b --- /dev/null +++ b/pkg/authorization/oauth2/scope/scope.go @@ -0,0 +1,57 @@ +/* +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scope + +import ( + "encoding/json" + "strings" +) + +// Scope defines a list of scopes. +type Scope []string + +// Ensure the correct interfaces are implemented. +var _ json.Marshaler = &Scope{} +var _ json.Unmarshaler = &Scope{} + +// NewScope creates a new scopes object. +func NewScope(s string) Scope { + return strings.Split(s, " ") +} + +// MarshalJSON implements json.Marshaller. +func (l *Scope) MarshalJSON() ([]byte, error) { + data, err := json.Marshal(strings.Join(*l, " ")) + if err != nil { + return nil, err + } + + return data, nil +} + +// UnmarshalJSON implments json.Unmarshaller. +func (l *Scope) UnmarshalJSON(value []byte) error { + var list string + + if err := json.Unmarshal(value, &list); err != nil { + return err + } + + *l = strings.Split(list, " ") + + return nil +} diff --git a/pkg/server/middleware/cors/cors.go b/pkg/server/middleware/cors/cors.go new file mode 100644 index 0000000..ff32245 --- /dev/null +++ b/pkg/server/middleware/cors/cors.go @@ -0,0 +1,101 @@ +/* +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cors + +import ( + "net/http" + "slices" + "strconv" + "strings" + + "github.com/spf13/pflag" + + "github.com/unikorn-cloud/core/pkg/server/errors" + "github.com/unikorn-cloud/core/pkg/server/middleware/openapi" + "github.com/unikorn-cloud/core/pkg/util" +) + +type Options struct { + AllowedOrigins []string + MaxAge int +} + +func (o *Options) AddFlags(f *pflag.FlagSet) { + f.StringSliceVar(&o.AllowedOrigins, "cors-allow-origin", []string{"*"}, "CORS allowed origins") + f.IntVar(&o.MaxAge, "cors-max-age", 86400, "CORS maximum age (may be overridden by the browser)") +} + +func setAllowOrigin(w http.ResponseWriter, r *http.Request, allowedOrigins []string) { + if origin := r.Header.Get("Origin"); origin != "" { + if index := slices.IndexFunc(allowedOrigins, func(s string) bool { return s == origin }); index >= 0 { + w.Header().Add("Access-Control-Allow-Origin", origin) + return + } + } + + w.Header().Add("Access-Control-Allow-Origin", allowedOrigins[0]) +} + +func Middleware(schema *openapi.Schema, options *Options) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // All requests get the allow origin header. BUT only one! + setAllowOrigin(w, r, options.AllowedOrigins) + + // For normal requests handle them. + if r.Method != http.MethodOptions { + next.ServeHTTP(w, r) + return + } + + // Handle preflight + method := r.Header.Get("Access-Control-Request-Method") + if method == "" { + errors.HandleError(w, r, errors.OAuth2InvalidRequest("OPTIONS missing Access-Control-Request-Method header")) + return + } + + request := r.Clone(r.Context()) + request.Method = method + + route, _, err := schema.FindRoute(request) + if err != nil { + errors.HandleError(w, r, err) + return + } + + // TODO: add OPTIONS to the schema? + methods := util.Keys(route.PathItem.Operations()) + methods = append(methods, http.MethodOptions) + + // TODO: I've tried adding them to the schema, but the generator + // adds them to the hander function signatures, which is superfluous + // to requirements. + headers := []string{ + "Authorization", + "Content-Type", + "traceparent", + "tracestate", + } + + w.Header().Add("Access-Control-Allow-Methods", strings.Join(methods, ", ")) + w.Header().Add("Access-Control-Allow-Headers", strings.Join(headers, ", ")) + w.Header().Add("Access-Control-Max-Age", strconv.Itoa(options.MaxAge)) + w.WriteHeader(http.StatusNoContent) + }) + } +} diff --git a/pkg/server/middleware/openapi/authorization.go b/pkg/server/middleware/openapi/authorization.go new file mode 100644 index 0000000..335784e --- /dev/null +++ b/pkg/server/middleware/openapi/authorization.go @@ -0,0 +1,168 @@ +/* +Copyright 2022-2024 EscherCloud. +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openapi + +import ( + "crypto/tls" + "crypto/x509" + "net/http" + "slices" + "strings" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/getkin/kin-openapi/openapi3" + "github.com/spf13/pflag" + + "github.com/unikorn-cloud/core/pkg/authorization/oauth2/claims" + "github.com/unikorn-cloud/core/pkg/server/errors" +) + +// authorizationContext is passed through the middleware to propagate +// information back to the top level handler. +type authorizationContext struct { + // err allows us to return a verbose error, unwrapped by whatever + // the openapi validaiton is doing. + err error + + // claims contains all claims defined in the token. + claims claims.Claims +} + +type Options struct { + // issuer is used to perform OIDC discovery and verify access tokens + // using the JWKS endpoint. + issuer string + + // issuerCA is the root CA of the identity endpoint. + issuerCA []byte +} + +func (o *Options) AddFlags(f *pflag.FlagSet) { + f.StringVar(&o.issuer, "oidc-issuer", "", "OIDC issuer URL to use for token validation.") + f.BytesBase64Var(&o.issuerCA, "oidc-issuer-ca", nil, "base64 OIDC endpoint CA certificate.") +} + +// Authorizer provides OpenAPI based authorization middleware. +type Authorizer struct { + options *Options +} + +// NewAuthorizer returns a new authorizer with required parameters. +func NewAuthorizer(options *Options) *Authorizer { + return &Authorizer{ + options: options, + } +} + +// getHTTPAuthenticationScheme grabs the scheme and token from the HTTP +// Authorization header. +func getHTTPAuthenticationScheme(r *http.Request) (string, string, error) { + header := r.Header.Get("Authorization") + if header == "" { + return "", "", errors.OAuth2InvalidRequest("authorization header missing") + } + + parts := strings.Split(header, " ") + if len(parts) != 2 { + return "", "", errors.OAuth2InvalidRequest("authorization header malformed") + } + + return parts[0], parts[1], nil +} + +// authorizeOAuth2 checks APIs that require and oauth2 bearer token. +func (a *Authorizer) authorizeOAuth2(authContext *authorizationContext, r *http.Request, scopes []string) error { + authorizationScheme, rawToken, err := getHTTPAuthenticationScheme(r) + if err != nil { + return err + } + + if !strings.EqualFold(authorizationScheme, "bearer") { + return errors.OAuth2InvalidRequest("authorization scheme not allowed").WithValues("scheme", authorizationScheme) + } + + // Handle non-public CA certiifcates used in development. + ctx := r.Context() + + if a.options.issuerCA != nil { + certPool := x509.NewCertPool() + + if ok := certPool.AppendCertsFromPEM(a.options.issuerCA); !ok { + return errors.OAuth2InvalidRequest("failed to parse oidc issuer CA cert") + } + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: certPool, + MinVersion: tls.VersionTLS13, + }, + }, + } + + ctx = oidc.ClientContext(ctx, client) + } + + // Note: although we are talking about ID tokens, the identity service uses + // the same data structures and algorithms for access tokens. The raitonale here + // is to avoid sending sensitive information in the token, and avoid using JWE as + // we'd need to get access to the private key in order to decrypt it, which brings + // it's own challenges. + provider, err := oidc.NewProvider(ctx, a.options.issuer) + if err != nil { + return errors.OAuth2ServerError("oidc service discovery failed").WithError(err) + } + + config := &oidc.Config{ + SkipClientIDCheck: true, + } + + verifier := provider.Verifier(config) + + token, err := verifier.Verify(ctx, rawToken) + if err != nil { + return errors.OAuth2AccessDenied("access token validation failed").WithError(err) + } + + var claims claims.Claims + + if err := token.Claims(&claims); err != nil { + return errors.OAuth2ServerError("access token claims extraction failed").WithError(err) + } + + // Check the token is authorized to do what the schema says. + for _, scope := range scopes { + if !slices.Contains(claims.Scope, scope) { + return errors.OAuth2InvalidScope("token missing required scope").WithValues("scope", scope) + } + } + + // Set the claims in the context for use by the handlers. + authContext.claims = claims + + return nil +} + +// authorizeScheme requires the individual scheme to match. +func (a *Authorizer) authorizeScheme(ctx *authorizationContext, r *http.Request, scheme *openapi3.SecurityScheme, scopes []string) error { + if scheme.Type == "oauth2" { + return a.authorizeOAuth2(ctx, r, scopes) + } + + return errors.OAuth2InvalidRequest("authorization scheme unsupported").WithValues("scheme", scheme.Type) +} diff --git a/pkg/server/middleware/openapi/openapi.go b/pkg/server/middleware/openapi/openapi.go new file mode 100644 index 0000000..d2213d1 --- /dev/null +++ b/pkg/server/middleware/openapi/openapi.go @@ -0,0 +1,191 @@ +/* +Copyright 2022-2024 EscherCloud. +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openapi + +import ( + "bytes" + "context" + "io" + "net/http" + + "github.com/getkin/kin-openapi/openapi3filter" + + "github.com/unikorn-cloud/core/pkg/authorization/oauth2/claims" + "github.com/unikorn-cloud/core/pkg/server/errors" + + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// Validator provides Schema validation of request and response codes, +// media, and schema validation of payloads to ensure we are meeting the +// specification. +type Validator struct { + // next defines the next HTTP handler in the chain. + next http.Handler + + // authorizer provides security policy enforcement. + authorizer *Authorizer + + // openapi caches the Schema schema. + openapi *Schema +} + +// Ensure this implements the required interfaces. +var _ http.Handler = &Validator{} + +// NewValidator returns an initialized validator middleware. +func NewValidator(authorizer *Authorizer, next http.Handler, openapi *Schema) *Validator { + return &Validator{ + authorizer: authorizer, + next: next, + openapi: openapi, + } +} + +// bufferingResponseWriter saves the response code and body so that we can +// validate them. +type bufferingResponseWriter struct { + // next is the parent handler. + next http.ResponseWriter + + // code is the HTTP status code. + code int + + // body is a copy of the HTTP response body. + // This valus will be nil if no body was written. + body io.ReadCloser +} + +// Ensure the correct interfaces are implmeneted. +var _ http.ResponseWriter = &bufferingResponseWriter{} + +// Header returns the HTTP headers. +func (w *bufferingResponseWriter) Header() http.Header { + return w.next.Header() +} + +// Write writes out a body, if WriteHeader has not been called this will +// be done with a 200 status code. +func (w *bufferingResponseWriter) Write(body []byte) (int, error) { + buf := &bytes.Buffer{} + buf.Write(body) + + w.body = io.NopCloser(buf) + + return w.next.Write(body) +} + +// WriteHeader writes out the HTTP headers with the provided status code. +func (w *bufferingResponseWriter) WriteHeader(statusCode int) { + w.code = statusCode + + w.next.WriteHeader(statusCode) +} + +// StatusCode calculates the status code returned to the client. +func (w *bufferingResponseWriter) StatusCode() int { + if w.code == 0 { + return http.StatusOK + } + + return w.code +} + +func (v *Validator) validateRequest(r *http.Request, authContext *authorizationContext) (*openapi3filter.ResponseValidationInput, error) { + route, params, err := v.openapi.FindRoute(r) + if err != nil { + return nil, errors.OAuth2ServerError("route lookup failure").WithError(err) + } + + authorizationFunc := func(ctx context.Context, input *openapi3filter.AuthenticationInput) error { + err := v.authorizer.authorizeScheme(authContext, input.RequestValidationInput.Request, input.SecurityScheme, input.Scopes) + + authContext.err = err + + return err + } + + options := &openapi3filter.Options{ + IncludeResponseStatus: true, + AuthenticationFunc: authorizationFunc, + } + + requestValidationInput := &openapi3filter.RequestValidationInput{ + Request: r, + PathParams: params, + Route: route, + Options: options, + } + + if err := openapi3filter.ValidateRequest(r.Context(), requestValidationInput); err != nil { + if authContext.err != nil { + return nil, authContext.err + } + + return nil, errors.OAuth2InvalidRequest("request body invalid").WithError(err) + } + + responseValidationInput := &openapi3filter.ResponseValidationInput{ + RequestValidationInput: requestValidationInput, + Options: options, + } + + return responseValidationInput, nil +} + +func (v *Validator) validateResponse(w *bufferingResponseWriter, r *http.Request, responseValidationInput *openapi3filter.ResponseValidationInput) { + responseValidationInput.Status = w.StatusCode() + responseValidationInput.Header = w.Header() + responseValidationInput.Body = w.body + + if err := openapi3filter.ValidateResponse(r.Context(), responseValidationInput); err != nil { + log.FromContext(r.Context()).Error(err, "response openapi schema validation failure") + } +} + +// ServeHTTP implements the http.Handler interface. +func (v *Validator) ServeHTTP(w http.ResponseWriter, r *http.Request) { + authContext := &authorizationContext{} + + responseValidationInput, err := v.validateRequest(r, authContext) + if err != nil { + errors.HandleError(w, r, err) + + return + } + + // Add any contextual information to bubble up to the handler. + r = r.WithContext(claims.NewContext(r.Context(), &authContext.claims)) + + // Override the writer so we can inspect the contents and status. + writer := &bufferingResponseWriter{ + next: w, + } + + v.next.ServeHTTP(writer, r) + + v.validateResponse(writer, r, responseValidationInput) +} + +// Middleware returns a function that generates per-request +// middleware functions. +func Middleware(authorizer *Authorizer, openapi *Schema) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return NewValidator(authorizer, next, openapi) + } +} diff --git a/pkg/server/middleware/openapi/schema.go b/pkg/server/middleware/openapi/schema.go new file mode 100644 index 0000000..74b743f --- /dev/null +++ b/pkg/server/middleware/openapi/schema.go @@ -0,0 +1,72 @@ +/* +Copyright 2022-2024 EscherCloud. +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package openapi + +import ( + "net/http" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers" + "github.com/getkin/kin-openapi/routers/gorillamux" + + "github.com/unikorn-cloud/core/pkg/server/errors" +) + +// Schema abstracts schema access and validation. +type Schema struct { + // spec is the full specification. + spec *openapi3.T + + // router is a router able to process requests and return the + // route from the spec. + router routers.Router +} + +// SchemaGetter allows clients to get their schema from wherever. +type SchemaGetter func() (*openapi3.T, error) + +// NewOpenRpi extracts the swagger document. +// NOTE: this is surprisingly slow, make sure you cache it and reuse it. +func NewSchema(get SchemaGetter) (*Schema, error) { + spec, err := get() + if err != nil { + return nil, err + } + + router, err := gorillamux.NewRouter(spec) + if err != nil { + return nil, err + } + + s := &Schema{ + spec: spec, + router: router, + } + + return s, nil +} + +// FindRoute looks up the route from the specification. +func (s *Schema) FindRoute(r *http.Request) (*routers.Route, map[string]string, error) { + route, params, err := s.router.FindRoute(r) + if err != nil { + return nil, nil, errors.OAuth2ServerError("unable to find route").WithError(err) + } + + return route, params, nil +} diff --git a/pkg/server/middleware/opentelemetry/opentelemetry.go b/pkg/server/middleware/opentelemetry/opentelemetry.go new file mode 100644 index 0000000..0c91e6a --- /dev/null +++ b/pkg/server/middleware/opentelemetry/opentelemetry.go @@ -0,0 +1,295 @@ +/* +Copyright 2022-2024 EscherCloud. +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package opentelemetry + +import ( + "context" + "net/http" + "slices" + "strconv" + "strings" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/propagation" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.22.0" + "go.opentelemetry.io/otel/trace" + + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// loggingResponseWriter is the ubiquitous reimplementation of a response +// writer that allows access to the HTTP status code in middleware. +type loggingResponseWriter struct { + next http.ResponseWriter + code int + contentLength int +} + +// Check the correct interface is implmented. +var _ http.ResponseWriter = &loggingResponseWriter{} + +func (w *loggingResponseWriter) Header() http.Header { + return w.next.Header() +} + +func (w *loggingResponseWriter) Write(body []byte) (int, error) { + w.contentLength += len(body) + + return w.next.Write(body) +} + +func (w *loggingResponseWriter) WriteHeader(statusCode int) { + w.code = statusCode + w.next.WriteHeader(statusCode) +} + +func (w *loggingResponseWriter) StatusCode() int { + if w.code == 0 { + return http.StatusOK + } + + return w.code +} + +// logValuesFromSpan gets a generic set of key/value pairs from a span for logging. +func logValuesFromSpanContext(name string, s trace.SpanContext) []interface{} { + return []interface{}{ + "span.name", name, + "span.id", s.SpanID().String(), + "trace.id", s.TraceID().String(), + } +} + +// logValuesFromSpan gets a generic set of key/value pairs from a span for logging. +func logValuesFromSpan(s sdktrace.ReadOnlySpan) []interface{} { + values := logValuesFromSpanContext(s.Name(), s.SpanContext()) + + for _, attribute := range s.Attributes() { + values = append(values, string(attribute.Key), attribute.Value.Emit()) + } + + return values +} + +// LoggingSpanProcessor is a OpenTelemetry span processor that logs to standard out +// in whatever format is defined by the logger. +type LoggingSpanProcessor struct{} + +// Check the correct interface is implmented. +var _ sdktrace.SpanProcessor = &LoggingSpanProcessor{} + +func (*LoggingSpanProcessor) OnStart(ctx context.Context, s sdktrace.ReadWriteSpan) { + log.Log.Info("span start", logValuesFromSpan(s)...) +} + +func (*LoggingSpanProcessor) OnEnd(s sdktrace.ReadOnlySpan) { + log.Log.Info("span end", logValuesFromSpan(s)...) +} + +func (*LoggingSpanProcessor) Shutdown(ctx context.Context) error { + return nil +} + +func (*LoggingSpanProcessor) ForceFlush(ctx context.Context) error { + return nil +} + +// headerBlackList are headers we shouldn't collect, or are covered somewhere +// other than http.request.header ot http.response.header. +func headerBlackList() []string { + return []string{ + "authorization", + "user-agent", + } +} + +func httpHeaderAttributes(header http.Header, prefix string) []attribute.KeyValue { + attr := make([]attribute.KeyValue, 0, len(header)) + + for key, values := range header { + normalizedKey := strings.ToLower(key) + + // DO NOT EXPOSE PRIVATE INFORMATION. + if slices.Contains(headerBlackList(), normalizedKey) { + continue + } + + key := attribute.Key(prefix + "." + normalizedKey) + + if len(values) == 1 { + attr = append(attr, key.String(values[0])) + } else { + attr = append(attr, key.StringSlice(values)) + } + } + + return attr +} + +// httpRequestAttributes gets all the attr it can from a request. +// This is done on a best effort basis! +// +//nolint:cyclop +func httpRequestAttributes(r *http.Request) []attribute.KeyValue { + var attr []attribute.KeyValue + + /* Protocol Processing */ + protoVersion := strings.Split(r.Proto, "/") + + attr = append(attr, semconv.NetworkProtocolName(protoVersion[0])) + attr = append(attr, semconv.NetworkProtocolVersion(protoVersion[1])) + + /* HTTP Processing */ + switch r.Method { + case http.MethodConnect: + attr = append(attr, semconv.HTTPRequestMethodConnect) + case http.MethodDelete: + attr = append(attr, semconv.HTTPRequestMethodDelete) + case http.MethodGet: + attr = append(attr, semconv.HTTPRequestMethodGet) + case http.MethodHead: + attr = append(attr, semconv.HTTPRequestMethodHead) + case http.MethodOptions: + attr = append(attr, semconv.HTTPRequestMethodOptions) + case http.MethodPatch: + attr = append(attr, semconv.HTTPRequestMethodPatch) + case http.MethodPost: + attr = append(attr, semconv.HTTPRequestMethodPost) + case http.MethodPut: + attr = append(attr, semconv.HTTPRequestMethodPut) + case http.MethodTrace: + attr = append(attr, semconv.HTTPRequestMethodTrace) + default: + attr = append(attr, semconv.HTTPRequestMethodOther) + } + + attr = append(attr, semconv.HTTPRequestBodySize(int(r.ContentLength))) + attr = append(attr, httpHeaderAttributes(r.Header, "http.request.header")...) + + // User Agent Processing. + if userAgent := r.UserAgent(); userAgent != "" { + attr = append(attr, semconv.UserAgentOriginal(userAgent)) + } + + /* URL Processing */ + scheme := "http" + + if r.URL.Scheme != "" { + scheme = r.URL.Scheme + } + + attr = append(attr, semconv.URLScheme(scheme)) + attr = append(attr, semconv.URLPath(r.URL.Path)) + + if r.URL.RawQuery != "" { + attr = append(attr, semconv.URLQuery(r.URL.RawQuery)) + } + + if r.URL.Fragment != "" { + attr = append(attr, semconv.URLFragment(r.URL.Fragment)) + } + + /* Server processing */ + serverHostPort := strings.Split(r.URL.Host, ":") + + serverPort := 80 + + if len(serverHostPort) > 1 { + t, err := strconv.Atoi(serverHostPort[1]) + if err == nil { + serverPort = t + } + } + + attr = append(attr, semconv.ServerAddress(serverHostPort[0])) + attr = append(attr, semconv.ServerPort(serverPort)) + + /* Client processing */ + clientHostPort := strings.Split(r.RemoteAddr, ":") + attr = append(attr, semconv.ClientAddress(clientHostPort[0])) + + if clientPort, err := strconv.Atoi(clientHostPort[1]); err == nil { + attr = append(attr, semconv.ClientPort(clientPort)) + } + + return attr +} + +func httpResponseAttributes(w *loggingResponseWriter) []attribute.KeyValue { + var attr []attribute.KeyValue + + attr = append(attr, semconv.HTTPResponseStatusCode(w.StatusCode())) + attr = append(attr, semconv.HTTPResponseBodySize(w.contentLength)) + attr = append(attr, httpHeaderAttributes(w.next.Header(), "http.response.header")...) + + return attr +} + +func httpStatusToOtelCode(status int) (codes.Code, string) { + code := codes.Ok + + if status >= 400 { + code = codes.Error + } + + return code, http.StatusText(status) +} + +// Middleware attaches logging context to the request. +func Middleware(serviceName, version, application string) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Extract the tracing information from the HTTP headers. + ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header)) + + // Add in service information. + var attr []attribute.KeyValue + + attr = append(attr, semconv.ServiceName(serviceName)) + attr = append(attr, semconv.ServiceVersion(version)) + attr = append(attr, httpRequestAttributes(r)...) + + tracer := otel.GetTracerProvider().Tracer("opentelemetry middleware") + + // Begin the span processing. + name := r.URL.Path + + ctx, span := tracer.Start(ctx, name, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attr...)) + defer span.End() + + // Setup logging. + ctx = log.IntoContext(ctx, log.Log.WithValues(logValuesFromSpanContext(name, span.SpanContext())...)) + + // Create a new request with any contextual information the tracer has added. + request := r.WithContext(ctx) + + writer := &loggingResponseWriter{ + next: w, + } + + next.ServeHTTP(writer, request) + + // Extract HTTP response information for logging purposes. + span.SetAttributes(httpResponseAttributes(writer)...) + span.SetStatus(httpStatusToOtelCode(writer.StatusCode())) + }) + } +} diff --git a/pkg/server/middleware/timeout/timeout.go b/pkg/server/middleware/timeout/timeout.go new file mode 100644 index 0000000..152fad0 --- /dev/null +++ b/pkg/server/middleware/timeout/timeout.go @@ -0,0 +1,36 @@ +/* +Copyright 2022-2024 EscherCloud. +Copyright 2024 the Unikorn Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package timeout + +import ( + "context" + "net/http" + "time" +) + +// Middleware adds a timeout to requests. +func Middleware(timeout time.Duration) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), timeout) + defer cancel() + + next.ServeHTTP(w, r.Clone(ctx)) + }) + } +}