diff --git a/.docker/Dockerfile-build b/.docker/Dockerfile-build index 25172323fb..711d4a5837 100644 --- a/.docker/Dockerfile-build +++ b/.docker/Dockerfile-build @@ -1,5 +1,5 @@ # Workaround for https://github.com/GoogleContainerTools/distroless/issues/1342 -FROM golang:1.20-bullseye AS builder +FROM golang:1.21-bullseye AS builder WORKDIR /go/src/github.com/ory/oathkeeper diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 958e355134..8e3bc7271a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: key: ${{ needs.sdk-generate.outputs.sdk-cache-key }} - uses: actions/setup-go@v2 with: - go-version: "1.20" + go-version: "1.21" - run: go list -json > go.list - name: Run nancy uses: sonatype-nexus-community/nancy-github-action@v1.0.2 @@ -70,7 +70,7 @@ jobs: - uses: ory/ci/checkout@master - uses: actions/setup-go@v2 with: - go-version: "1.20" + go-version: "1.21" - run: | ./test/${{ matrix.name }}/run.sh @@ -84,6 +84,9 @@ jobs: with: token: ${{ secrets.ORY_BOT_PAT }} output-dir: docs/oathkeeper/cli + - uses: actions/setup-go@v2 + with: + go-version: "1.21" changelog: name: Generate changelog diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 80515a6172..b59c85d31b 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: "1.20" + go-version: "1.21" - run: make format - name: Indicate formatting issues run: git diff HEAD --exit-code --color diff --git a/.github/workflows/licenses.yml b/.github/workflows/licenses.yml index cab996050b..8871ccb2c5 100644 --- a/.github/workflows/licenses.yml +++ b/.github/workflows/licenses.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: "1.20" + go-version: "1.21" - uses: actions/setup-node@v2 with: node-version: "18" diff --git a/Makefile b/Makefile index c4c1b156fd..3e9d2ef3f9 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,12 @@ install: docker: DOCKER_BUILDKIT=1 DOCKER_CONTENT_TRUST=1 docker build -t oryd/oathkeeper:${IMAGE_TAG} --progress=plain -f .docker/Dockerfile-build . +.PHONY: docker-k3d +docker-k3d: + CGO_ENABLED=0 GOOS=linux go build + DOCKER_BUILDKIT=1 DOCKER_CONTENT_TRUST=1 docker build -t k3d-localhost:5111/oryd/oathkeeper:dev --push -f .docker/Dockerfile-distroless-static . + rm oathkeeper + docs/cli: .bin/clidoc clidoc . diff --git a/api/credential_doc.go b/api/credential_doc.go index fe93c2ec03..73e983f559 100644 --- a/api/credential_doc.go +++ b/api/credential_doc.go @@ -3,6 +3,8 @@ package api +//lint:file-ignore U1000 Used to generate Swagger/OpenAPI definitions. + // swagger:model jsonWebKeySet type swaggerJSONWebKeySet struct { // The value of the "keys" parameter is an array of JWK values. By diff --git a/api/health.go b/api/health.go index da49e1a9e4..c7396ff966 100644 --- a/api/health.go +++ b/api/health.go @@ -3,6 +3,8 @@ package api +//lint:file-ignore U1000 Used to generate Swagger/OpenAPI definitions. + // Alive returns an ok status if the instance is ready to handle HTTP requests. // // swagger:route GET /health/alive api isInstanceAlive diff --git a/api/rule_doc.go b/api/rule_doc.go index a51de1a101..2dd0224e42 100644 --- a/api/rule_doc.go +++ b/api/rule_doc.go @@ -3,6 +3,8 @@ package api +//lint:file-ignore U1000 Used to generate Swagger/OpenAPI definitions. + import "github.com/ory/oathkeeper/rule" // A rule diff --git a/cmd/root_test.go b/cmd/root_test.go index ee2b765408..0339102c22 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -20,30 +20,12 @@ import ( var apiPort, proxyPort int -func freePort() (int, int) { - var err error - r := make([]int, 2) - - if r[0], err = freeport.GetFreePort(); err != nil { - panic(err.Error()) - } - - tries := 0 - for { - r[1], err = freeport.GetFreePort() - if r[0] != r[1] { - break - } - tries++ - if tries > 10 { - panic("Unable to find free port") - } - } - return r[0], r[1] -} - func init() { - apiPort, proxyPort = freePort() + p, err := freeport.GetFreePorts(2) + if err != nil { + panic(err) + } + apiPort, proxyPort = p[0], p[1] os.Setenv("SERVE_API_PORT", fmt.Sprintf("%d", apiPort)) os.Setenv("SERVE_PROXY_PORT", fmt.Sprintf("%d", proxyPort)) diff --git a/credentials/fetcher_default.go b/credentials/fetcher_default.go index f954f4cd7d..052595852f 100644 --- a/credentials/fetcher_default.go +++ b/credentials/fetcher_default.go @@ -14,6 +14,8 @@ import ( "sync" "time" + "go.opentelemetry.io/otel/trace" + "github.com/ory/oathkeeper/internal/cloudstorage" "github.com/pkg/errors" @@ -49,6 +51,11 @@ type FetcherDefault struct { mux *blob.URLMux } +type dependencies interface { + Logger() *logrusx.Logger + Tracer() trace.Tracer +} + type FetcherOption func(f *FetcherDefault) func WithURLMux(mux *blob.URLMux) FetcherOption { @@ -60,15 +67,18 @@ func WithURLMux(mux *blob.URLMux) FetcherOption { // - cancelAfter: If reached, the fetcher will stop waiting for responses and return an error. // - waitForResponse: While the fetcher might stop waiting for responses, we will give the server more time to respond // and add the keys to the registry unless waitForResponse is reached in which case we'll terminate the request. -func NewFetcherDefault(l *logrusx.Logger, cancelAfter time.Duration, ttl time.Duration, opts ...FetcherOption) *FetcherDefault { +func NewFetcherDefault(d dependencies, cancelAfter time.Duration, ttl time.Duration, opts ...FetcherOption) *FetcherDefault { f := &FetcherDefault{ cancelAfter: cancelAfter, - l: l, + l: d.Logger(), ttl: ttl, keys: make(map[string]jose.JSONWebKeySet), fetchedAt: make(map[string]time.Time), - client: httpx.NewResilientClient(httpx.ResilientClientWithConnectionTimeout(15 * time.Second)).StandardClient(), - mux: cloudstorage.NewURLMux(), + client: httpx.NewResilientClient( + httpx.ResilientClientWithConnectionTimeout(15*time.Second), + httpx.ResilientClientWithTracer(d.Tracer()), + ).StandardClient(), + mux: cloudstorage.NewURLMux(), } for _, o := range opts { o(f) @@ -94,8 +104,6 @@ func (s *FetcherDefault) ResolveSets(ctx context.Context, locations []url.URL) ( } func (s *FetcherDefault) fetchParallel(ctx context.Context, locations []url.URL) error { - ctx, cancel := context.WithTimeout(ctx, s.cancelAfter) - defer cancel() errs := make(chan error) done := make(chan struct{}) @@ -112,12 +120,14 @@ func (s *FetcherDefault) fetchParallel(ctx context.Context, locations []url.URL) } }() - go s.resolveAll(done, errs, locations) + go s.resolveAll(ctx, done, errs, locations) select { case <-ctx.Done(): - s.l.WithError(ctx.Err()).Errorf("Ignoring JSON Web Keys from at least one URI because the request timed out waiting for a response.") return ctx.Err() + case <-time.After(s.cancelAfter): + s.l.Errorf("Ignoring JSON Web Keys from at least one URI because the request timed out waiting for a response.") + return context.DeadlineExceeded case <-done: // We're done! return nil @@ -183,17 +193,17 @@ func (s *FetcherDefault) set(locations []url.URL, staleKeyAcceptable bool) []jos } func (s *FetcherDefault) isKeyExpired(expiredKeyAcceptable bool, fetchedAt time.Time) bool { - return expiredKeyAcceptable == false && - fetchedAt.Add(s.ttl).Before(time.Now().UTC()) + return !expiredKeyAcceptable && time.Since(fetchedAt) > s.ttl } -func (s *FetcherDefault) resolveAll(done chan struct{}, errs chan error, locations []url.URL) { +func (s *FetcherDefault) resolveAll(ctx context.Context, done chan struct{}, errs chan error, locations []url.URL) { + ctx = context.WithoutCancel(ctx) var wg sync.WaitGroup for _, l := range locations { l := l wg.Add(1) - go s.resolve(&wg, errs, l) + go s.resolve(ctx, &wg, errs, l) } wg.Wait() @@ -201,7 +211,7 @@ func (s *FetcherDefault) resolveAll(done chan struct{}, errs chan error, locatio close(errs) } -func (s *FetcherDefault) resolve(wg *sync.WaitGroup, errs chan error, location url.URL) { +func (s *FetcherDefault) resolve(ctx context.Context, wg *sync.WaitGroup, errs chan error, location url.URL) { defer wg.Done() var ( reader io.ReadCloser @@ -210,7 +220,6 @@ func (s *FetcherDefault) resolve(wg *sync.WaitGroup, errs chan error, location u switch location.Scheme { case "azblob", "gs", "s3": - ctx := context.Background() bucket, err := s.mux.OpenBucket(ctx, location.Scheme+"://"+location.Host) if err != nil { errs <- errors.WithStack(herodot. @@ -255,7 +264,19 @@ func (s *FetcherDefault) resolve(wg *sync.WaitGroup, errs chan error, location u defer reader.Close() case "http", "https": - res, err := s.client.Get(location.String()) + req, err := http.NewRequestWithContext(ctx, "GET", location.String(), nil) + if err != nil { + errs <- errors.WithStack(herodot. + ErrInternalServerError. + WithReasonf( + `Unable to fetch JSON Web Keys from location "%s" because "%s".`, + location.String(), + err, + ), + ) + return + } + res, err := s.client.Do(req) if err != nil { errs <- errors.WithStack(herodot. ErrInternalServerError. diff --git a/credentials/fetcher_default_test.go b/credentials/fetcher_default_test.go index b370c2c94d..4e7ef6a95c 100644 --- a/credentials/fetcher_default_test.go +++ b/credentials/fetcher_default_test.go @@ -15,6 +15,7 @@ import ( "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" "github.com/ory/x/logrusx" @@ -33,6 +34,16 @@ var sets = [...]json.RawMessage{ json.RawMessage(`invalid json ¯\_(ツ)_/¯`), } +type reg struct{} + +func (*reg) Logger() *logrusx.Logger { + return logrusx.New("", "", logrusx.ForceLevel(logrus.DebugLevel)) +} + +func (*reg) Tracer() trace.Tracer { + return trace.NewNoopTracerProvider().Tracer("") +} + func TestFetcherDefault(t *testing.T) { t.Parallel() @@ -42,7 +53,7 @@ func TestFetcherDefault(t *testing.T) { l := logrusx.New("", "", logrusx.ForceLevel(logrus.DebugLevel)) w := herodot.NewJSONWriter(l) - s := NewFetcherDefault(l, maxWait, JWKsTTL) + s := NewFetcherDefault(®{}, maxWait, JWKsTTL) timeOutServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { time.Sleep(timeoutServerDelay) @@ -177,7 +188,7 @@ func TestFetcherDefault(t *testing.T) { t.Run("name=should fetch from s3 object storage", func(t *testing.T) { t.Parallel() ctx := context.Background() - s := NewFetcherDefault(l, maxWait, JWKsTTL, WithURLMux(cloudstorage.NewTestURLMux(t))) + s := NewFetcherDefault(®{}, maxWait, JWKsTTL, WithURLMux(cloudstorage.NewTestURLMux(t))) key, err := s.ResolveKey(ctx, []url.URL{ *urlx.ParseOrPanic("s3://oathkeeper-test-bucket/path/prefix/jwks.json"), @@ -189,7 +200,7 @@ func TestFetcherDefault(t *testing.T) { t.Run("name=should fetch from gs object storage", func(t *testing.T) { t.Parallel() ctx := context.Background() - s := NewFetcherDefault(l, maxWait, JWKsTTL, WithURLMux(cloudstorage.NewTestURLMux(t))) + s := NewFetcherDefault(®{}, maxWait, JWKsTTL, WithURLMux(cloudstorage.NewTestURLMux(t))) key, err := s.ResolveKey(ctx, []url.URL{ *urlx.ParseOrPanic("gs://oathkeeper-test-bucket/path/prefix/jwks.json"), @@ -201,7 +212,7 @@ func TestFetcherDefault(t *testing.T) { t.Run("name=should fetch from azure object storage", func(t *testing.T) { t.Parallel() ctx := context.Background() - s := NewFetcherDefault(l, maxWait, JWKsTTL, WithURLMux(cloudstorage.NewTestURLMux(t))) + s := NewFetcherDefault(®{}, maxWait, JWKsTTL, WithURLMux(cloudstorage.NewTestURLMux(t))) jwkKey, err := s.ResolveKey(ctx, []url.URL{ *urlx.ParseOrPanic("azblob://path/prefix/jwks.json"), diff --git a/credentials/signer_default_test.go b/credentials/signer_default_test.go index 10e7b4b95b..3593c249e6 100644 --- a/credentials/signer_default_test.go +++ b/credentials/signer_default_test.go @@ -15,7 +15,6 @@ import ( "github.com/stretchr/testify/require" "github.com/ory/oathkeeper/x" - "github.com/ory/x/logrusx" ) type defaultSignerMockRegistry struct { @@ -23,7 +22,7 @@ type defaultSignerMockRegistry struct { } func newDefaultSignerMockRegistry() *defaultSignerMockRegistry { - return &defaultSignerMockRegistry{f: NewFetcherDefault(logrusx.New("", ""), time.Millisecond*100, time.Millisecond*500)} + return &defaultSignerMockRegistry{f: NewFetcherDefault(®{}, time.Millisecond*100, time.Millisecond*500)} } func (m *defaultSignerMockRegistry) CredentialsFetcher() Fetcher { @@ -42,7 +41,7 @@ func TestSignerDefault(t *testing.T) { token, err := signer.Sign(context.Background(), x.ParseURLOrPanic(src), jwt.MapClaims{"sub": "foo"}) require.NoError(t, err) - fetcher := NewFetcherDefault(logrusx.New("", ""), time.Second, time.Second) + fetcher := NewFetcherDefault(®{}, time.Second, time.Second) _, err = verify(t, token, fetcher, src) require.NoError(t, err) diff --git a/doc_swagger.go b/doc_swagger.go index df30e7bc84..16aef88589 100644 --- a/doc_swagger.go +++ b/doc_swagger.go @@ -3,6 +3,8 @@ package main +//lint:file-ignore U1000 Used to generate Swagger/OpenAPI definitions. + // The standard error format // swagger:model genericError type genericError struct { diff --git a/driver/configuration/provider_koanf_public_test.go b/driver/configuration/provider_koanf_public_test.go index f7a5223618..50cbf91309 100644 --- a/driver/configuration/provider_koanf_public_test.go +++ b/driver/configuration/provider_koanf_public_test.go @@ -13,6 +13,7 @@ import ( "github.com/rs/cors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" "github.com/ory/x/configx" "github.com/ory/x/logrusx" @@ -47,7 +48,7 @@ func TestPipelineConfig(t *testing.T) { p := setup(t) require.NoError(t, p.PipelineConfig("authenticators", "oauth2_introspection", nil, &res)) - assert.JSONEq(t, `{"cache":{"enabled":false, "max_cost":1000},"introspection_url":"https://override/path","pre_authorization":{"client_id":"some_id","client_secret":"some_secret","enabled":true,"audience":"some_audience","scope":["foo","bar"],"token_url":"https://my-website.com/oauth2/token"},"retry":{"max_delay":"100ms", "give_up_after":"1s"},"scope_strategy":"exact"}`, string(res), "%s", res) + assert.JSONEq(t, `{"cache":{"enabled":false, "max_cost":1000},"introspection_url":"https://override/path","preserve_host":false,"pre_authorization":{"client_id":"some_id","client_secret":"some_secret","enabled":true,"audience":"some_audience","scope":["foo","bar"],"token_url":"https://my-website.com/oauth2/token"},"retry":{"max_delay":"100ms", "give_up_after":"1s"},"scope_strategy":"exact"}`, string(res), "%s", res) }) t.Run("case=should setup", func(t *testing.T) { @@ -247,7 +248,7 @@ func TestKoanfProvider(t *testing.T) { }) t.Run("authenticator=cookie_session", func(t *testing.T) { - a := authn.NewAuthenticatorCookieSession(p, nil) + a := authn.NewAuthenticatorCookieSession(p, trace.NewNoopTracerProvider()) assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) require.NoError(t, a.Validate(nil)) @@ -285,7 +286,7 @@ func TestKoanfProvider(t *testing.T) { }) t.Run("authenticator=oauth2_introspection", func(t *testing.T) { - a := authn.NewAuthenticatorOAuth2Introspection(p, logger) + a := authn.NewAuthenticatorOAuth2Introspection(p, logger, trace.NewNoopTracerProvider()) assert.True(t, p.AuthenticatorIsEnabled(a.GetID())) require.NoError(t, a.Validate(nil)) @@ -433,7 +434,7 @@ func TestAuthenticatorOAuth2TokenIntrospectionPreAuthorization(t *testing.T) { {enabled: true, id: "a", secret: "b", turl: "https://some-url", err: false}, } { t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) { - a := authn.NewAuthenticatorOAuth2Introspection(p, logrusx.New("", "")) + a := authn.NewAuthenticatorOAuth2Introspection(p, logrusx.New("", ""), trace.NewNoopTracerProvider()) config, _, err := a.Config(json.RawMessage(fmt.Sprintf(`{ "pre_authorization": { diff --git a/driver/registry_memory.go b/driver/registry_memory.go index 9bcf90ec4d..bcf3d57200 100644 --- a/driver/registry_memory.go +++ b/driver/registry_memory.go @@ -198,7 +198,7 @@ func (r *RegistryMemory) DecisionHandler() *api.DecisionHandler { func (r *RegistryMemory) CredentialsFetcher() credentials.Fetcher { if r.credentialsFetcher == nil { - r.credentialsFetcher = credentials.NewFetcherDefault(r.Logger(), r.c.AuthenticatorJwtJwkMaxWait(), r.c.AuthenticatorJwtJwkTtl()) + r.credentialsFetcher = credentials.NewFetcherDefault(r, r.c.AuthenticatorJwtJwkMaxWait(), r.c.AuthenticatorJwtJwkTtl()) } return r.credentialsFetcher @@ -362,7 +362,7 @@ func (r *RegistryMemory) prepareAuthn() { authn.NewAuthenticatorJWT(r.c, r), authn.NewAuthenticatorNoOp(r.c), authn.NewAuthenticatorOAuth2ClientCredentials(r.c, r.Logger()), - authn.NewAuthenticatorOAuth2Introspection(r.c, r.Logger()), + authn.NewAuthenticatorOAuth2Introspection(r.c, r.Logger(), r.trc.Provider()), authn.NewAuthenticatorUnauthorized(r.c), } diff --git a/go.mod b/go.mod index c623995a5c..52a9029d17 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -go 1.20 +go 1.21 module github.com/ory/oathkeeper diff --git a/go.sum b/go.sum index 5fd364b75c..de530c4e97 100644 --- a/go.sum +++ b/go.sum @@ -158,6 +158,7 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -206,6 +207,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cristalhq/jwt/v4 v4.0.2 h1:g/AD3h0VicDamtlM70GWGElp8kssQEv+5wYd7L9WOhU= +github.com/cristalhq/jwt/v4 v4.0.2/go.mod h1:HnYraSNKDRag1DZP92rYHyrjyQHnVEHPNqesmzs+miQ= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/dave/jennifer v1.4.0 h1:tNJFJmLDVTLu+v05mVZ88RINa3vQqnyyWkTKWYz0CwE= github.com/dave/jennifer v1.4.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -268,6 +270,7 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -347,6 +350,7 @@ github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP github.com/go-swagger/go-swagger v0.30.0 h1:HakSyutD7Ek9ndkR8Fxy6WAoQtgu7UcAmZCTa6SzawA= github.com/go-swagger/go-swagger v0.30.0/go.mod h1:GhZVX/KIBM4VpGp4P7AJOIrlTuBeRVPS+j9kk6rFmfY= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0= +github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/attrs v1.0.3/go.mod h1:KvDJCE0avbufqS0Bw3UV7RQynESY0jjod+572ctX4t8= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= @@ -459,6 +463,7 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -556,6 +561,7 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= @@ -697,6 +703,7 @@ github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPucc github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U= github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0= github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= +github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -801,6 +808,7 @@ 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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -811,6 +819,7 @@ github.com/nyaruka/phonenumbers v1.1.1/go.mod h1:cGaEsOrLjIL0iKGqJR5Rfywy86dSkbA github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= +github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= @@ -1110,6 +1119,7 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -1384,6 +1394,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1588,6 +1599,7 @@ google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99 h1:qA8rMbz1wQ4DOFfM2ouD29DG9aHWBm6ZOy9BGxiUMmY= +google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1609,6 +1621,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/mold.v2 v2.2.0/go.mod h1:XMyyRsGtakkDPbxXbrA5VODo6bUXyvoDjLd5l3T0XoA= @@ -1644,6 +1657,7 @@ gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= +gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/metrics/prometheus.go b/metrics/prometheus.go index f91db51b7e..6ad5634644 100644 --- a/metrics/prometheus.go +++ b/metrics/prometheus.go @@ -43,11 +43,9 @@ type UpdateRequest func(counter *prometheus.CounterVec, service, request, method // PrometheusRepository provides methods to manage prometheus metrics type PrometheusRepository struct { - logger *logrusx.Logger - requestDurationObserve RequestDurationObserve - updateRequest UpdateRequest - Registry *prometheus.Registry - metrics []prometheus.Collector + logger *logrusx.Logger + Registry *prometheus.Registry + metrics []prometheus.Collector } // NewConfigurablePrometheusRepository creates a new prometheus repository with the given settings diff --git a/middleware/grpc_middleware.go b/middleware/grpc_middleware.go index 45fec6380d..1b7b4be6d4 100644 --- a/middleware/grpc_middleware.go +++ b/middleware/grpc_middleware.go @@ -17,6 +17,7 @@ import ( "google.golang.org/grpc/metadata" "github.com/ory/herodot" + "github.com/ory/x/otelx" "github.com/ory/oathkeeper/rule" ) @@ -73,8 +74,8 @@ func (m *middleware) UnaryInterceptor() grpc.UnaryServerInterceptor { } func (m *middleware) unaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { - traceCtx, span := trace.SpanFromContext(ctx).TracerProvider().Tracer("oathkeeper/middleware").Start(ctx, "Oathkeeper.UnaryInterceptor") - defer span.End() + traceCtx, span := trace.SpanFromContext(ctx).TracerProvider().Tracer("oathkeeper/middleware").Start(ctx, "middleware.UnaryInterceptor") + defer otelx.End(span, &err) log := m.Logger().WithField("middleware", "oathkeeper") @@ -122,8 +123,8 @@ func (m *middleware) streamInterceptor( info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) { ctx := stream.Context() - ctx, span := trace.SpanFromContext(ctx).TracerProvider().Tracer("oathkeeper/middleware").Start(ctx, "Oathkeeper.UnaryInterceptor") - defer span.End() + ctx, span := trace.SpanFromContext(ctx).TracerProvider().Tracer("oathkeeper/middleware").Start(ctx, "middleware.StreamInterceptor") + otelx.End(span, &err) log := m.Logger().WithField("middleware", "oathkeeper") diff --git a/pipeline/authn/authenticator_bearer_token.go b/pipeline/authn/authenticator_bearer_token.go index 4f7a43841a..944ff68cb5 100644 --- a/pipeline/authn/authenticator_bearer_token.go +++ b/pipeline/authn/authenticator_bearer_token.go @@ -6,6 +6,7 @@ package authn import ( "encoding/json" "net/http" + "strings" "github.com/pkg/errors" "github.com/tidwall/gjson" @@ -16,6 +17,7 @@ import ( "github.com/ory/oathkeeper/helper" "github.com/ory/oathkeeper/pipeline" "github.com/ory/oathkeeper/x/header" + "github.com/ory/x/otelx" "github.com/ory/x/stringsx" ) @@ -31,6 +33,7 @@ type AuthenticatorBearerTokenFilter struct { type AuthenticatorBearerTokenConfiguration struct { CheckSessionURL string `json:"check_session_url"` BearerTokenLocation *helper.BearerTokenLocation `json:"token_from"` + Prefix string `json:"prefix"` PreserveQuery bool `json:"preserve_query"` PreservePath bool `json:"preserve_path"` PreserveHost bool `json:"preserve_host"` @@ -72,6 +75,7 @@ func (a *AuthenticatorBearerTokenConfiguration) GetForceMethod() string { type AuthenticatorBearerToken struct { c configuration.Provider client *http.Client + tracer trace.Tracer } var _ AuthenticatorForwardConfig = new(AuthenticatorBearerTokenConfiguration) @@ -83,8 +87,9 @@ func NewAuthenticatorBearerToken(c configuration.Provider, provider trace.Tracer Transport: otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithTracerProvider(provider), - otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { return "authn.bearer_token" })), + ), }, + tracer: provider.Tracer("oauthkeeper/pipeline/authn"), } } @@ -121,14 +126,18 @@ func (a *AuthenticatorBearerToken) Config(config json.RawMessage) (*Authenticato return &c, nil } -func (a *AuthenticatorBearerToken) Authenticate(r *http.Request, session *AuthenticationSession, config json.RawMessage, _ pipeline.Rule) error { +func (a *AuthenticatorBearerToken) Authenticate(r *http.Request, session *AuthenticationSession, config json.RawMessage, _ pipeline.Rule) (err error) { + ctx, span := a.tracer.Start(r.Context(), "pipeline.authn.AuthenticatorBearerToken.Authenticate") + defer otelx.End(span, &err) + r = r.WithContext(ctx) + cf, err := a.Config(config) if err != nil { return err } token := helper.BearerTokenFromRequest(r, cf.BearerTokenLocation) - if token == "" { + if token == "" || !strings.HasPrefix(token, cf.Prefix) { return errors.WithStack(ErrAuthenticatorNotResponsible) } diff --git a/pipeline/authn/authenticator_bearer_token_test.go b/pipeline/authn/authenticator_bearer_token_test.go index fcd814c36c..4b02efabfd 100644 --- a/pipeline/authn/authenticator_bearer_token_test.go +++ b/pipeline/authn/authenticator_bearer_token_test.go @@ -64,6 +64,13 @@ func TestAuthenticatorBearerToken(t *testing.T) { expectErr: true, expectExactErr: ErrAuthenticatorNotResponsible, }, + { + d: "should return error saying that authenticator is not responsible for validating the request, as the token does not have the specified prefix", + r: &http.Request{Header: http.Header{"Authorization": {"bearer secret_token"}}}, + config: []byte(`{"prefix": "not_secret"}`), + expectErr: true, + expectExactErr: ErrAuthenticatorNotResponsible, + }, { d: "should fail because session store returned 400", r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}, URL: &url.URL{Path: ""}}, @@ -132,7 +139,7 @@ func TestAuthenticatorBearerToken(t *testing.T) { w.WriteHeader(200) w.Write([]byte(`{"sub": "123"}`)) }, - config: []byte(`{"preserve_path": true, "force_method": "GET"}`), + config: []byte(`{"preserve_path": true, "force_method": "GET", "prefix": "zy"}`), expectErr: false, expectSess: &AuthenticationSession{ Subject: "123", diff --git a/pipeline/authn/authenticator_cookie_session.go b/pipeline/authn/authenticator_cookie_session.go index 1c2cfc6790..33ab9e16bb 100644 --- a/pipeline/authn/authenticator_cookie_session.go +++ b/pipeline/authn/authenticator_cookie_session.go @@ -16,6 +16,7 @@ import ( "github.com/tidwall/gjson" "github.com/ory/oathkeeper/x/header" + "github.com/ory/x/otelx" "github.com/ory/x/stringsx" "github.com/ory/herodot" @@ -78,6 +79,7 @@ func (a *AuthenticatorCookieSessionConfiguration) GetForceMethod() string { type AuthenticatorCookieSession struct { c configuration.Provider client *http.Client + tracer trace.Tracer } var _ AuthenticatorForwardConfig = new(AuthenticatorCookieSessionConfiguration) @@ -89,8 +91,9 @@ func NewAuthenticatorCookieSession(c configuration.Provider, provider trace.Trac Transport: otelhttp.NewTransport( http.DefaultTransport, otelhttp.WithTracerProvider(provider), - otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { return "authn.cookie_session" })), + ), }, + tracer: provider.Tracer("oauthkeeper/pipeline/authn"), } } @@ -127,7 +130,11 @@ func (a *AuthenticatorCookieSession) Config(config json.RawMessage) (*Authentica return &c, nil } -func (a *AuthenticatorCookieSession) Authenticate(r *http.Request, session *AuthenticationSession, config json.RawMessage, _ pipeline.Rule) error { +func (a *AuthenticatorCookieSession) Authenticate(r *http.Request, session *AuthenticationSession, config json.RawMessage, _ pipeline.Rule) (err error) { + ctx, span := a.tracer.Start(r.Context(), "pipeline.authn.AuthenticatorCookieSession.Authenticate") + defer otelx.End(span, &err) + r = r.WithContext(ctx) + cf, err := a.Config(config) if err != nil { return err diff --git a/pipeline/authn/authenticator_jwt.go b/pipeline/authn/authenticator_jwt.go index 76cc9b04b9..8852efc0d4 100644 --- a/pipeline/authn/authenticator_jwt.go +++ b/pipeline/authn/authenticator_jwt.go @@ -11,6 +11,7 @@ import ( "github.com/golang-jwt/jwt/v4" "github.com/pkg/errors" + "go.opentelemetry.io/otel/trace" "github.com/ory/herodot" "github.com/ory/oathkeeper/credentials" @@ -18,10 +19,12 @@ import ( "github.com/ory/oathkeeper/helper" "github.com/ory/oathkeeper/pipeline" "github.com/ory/x/jwtx" + "github.com/ory/x/otelx" ) type AuthenticatorJWTRegistry interface { credentials.VerifierRegistry + Tracer() trace.Tracer } type AuthenticatorOAuth2JWTConfiguration struct { @@ -71,7 +74,11 @@ func (a *AuthenticatorJWT) Config(config json.RawMessage) (*AuthenticatorOAuth2J return &c, nil } -func (a *AuthenticatorJWT) Authenticate(r *http.Request, session *AuthenticationSession, config json.RawMessage, _ pipeline.Rule) error { +func (a *AuthenticatorJWT) Authenticate(r *http.Request, session *AuthenticationSession, config json.RawMessage, _ pipeline.Rule) (err error) { + ctx, span := a.r.Tracer().Start(r.Context(), "pipeline.authn.AuthenticatorJWT.Authenticate") + defer otelx.End(span, &err) + r = r.WithContext(ctx) + cf, err := a.Config(config) if err != nil { return err diff --git a/pipeline/authn/authenticator_oauth2_client_credentials.go b/pipeline/authn/authenticator_oauth2_client_credentials.go index a8226b42b6..e14f2ffa2d 100644 --- a/pipeline/authn/authenticator_oauth2_client_credentials.go +++ b/pipeline/authn/authenticator_oauth2_client_credentials.go @@ -181,7 +181,7 @@ func (a *AuthenticatorOAuth2ClientCredentials) tokenToCache(config *Authenticato return } else if a.cacheTTL != nil { // Allow up-to at most the cache TTL, otherwise use token expiry - ttl := token.Expiry.Sub(time.Now()) + ttl := time.Until(token.Expiry) if ttl > *a.cacheTTL { ttl = *a.cacheTTL } @@ -191,7 +191,7 @@ func (a *AuthenticatorOAuth2ClientCredentials) tokenToCache(config *Authenticato // If token has no expiry apply the same to the cache ttl := time.Duration(0) if !token.Expiry.IsZero() { - ttl = token.Expiry.Sub(time.Now()) + ttl = time.Until(token.Expiry) } a.tokenCache.SetWithTTL(key, v, 1, ttl) diff --git a/pipeline/authn/authenticator_oauth2_introspection.go b/pipeline/authn/authenticator_oauth2_introspection.go index cba1794efb..f55533b201 100644 --- a/pipeline/authn/authenticator_oauth2_introspection.go +++ b/pipeline/authn/authenticator_oauth2_introspection.go @@ -17,14 +17,17 @@ import ( "github.com/dgraph-io/ristretto" "github.com/pkg/errors" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel/trace" "golang.org/x/oauth2/clientcredentials" "github.com/ory/fosite" "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/helper" "github.com/ory/oathkeeper/pipeline" + "github.com/ory/oathkeeper/x/header" "github.com/ory/x/httpx" "github.com/ory/x/logrusx" + "github.com/ory/x/otelx" "github.com/ory/x/stringslice" ) @@ -35,7 +38,9 @@ type AuthenticatorOAuth2IntrospectionConfiguration struct { PreAuth *AuthenticatorOAuth2IntrospectionPreAuthConfiguration `json:"pre_authorization"` ScopeStrategy string `json:"scope_strategy"` IntrospectionURL string `json:"introspection_url"` + PreserveHost bool `json:"preserve_host"` BearerTokenLocation *helper.BearerTokenLocation `json:"token_from"` + Prefix string `json:"prefix"` IntrospectionRequestHeaders map[string]string `json:"introspection_request_headers"` Retry *AuthenticatorOAuth2IntrospectionRetryConfiguration `json:"retry"` Cache cacheConfig `json:"cache"` @@ -70,10 +75,11 @@ type AuthenticatorOAuth2Introspection struct { tokenCache *ristretto.Cache cacheTTL *time.Duration logger *logrusx.Logger + provider trace.TracerProvider } -func NewAuthenticatorOAuth2Introspection(c configuration.Provider, logger *logrusx.Logger) *AuthenticatorOAuth2Introspection { - return &AuthenticatorOAuth2Introspection{c: c, logger: logger, clientMap: make(map[string]*http.Client)} +func NewAuthenticatorOAuth2Introspection(c configuration.Provider, l *logrusx.Logger, p trace.TracerProvider) *AuthenticatorOAuth2Introspection { + return &AuthenticatorOAuth2Introspection{c: c, logger: l, provider: p, clientMap: make(map[string]*http.Client)} } func (a *AuthenticatorOAuth2Introspection) GetID() string { @@ -169,14 +175,19 @@ func (a *AuthenticatorOAuth2Introspection) tokenToCache(config *AuthenticatorOAu } } -func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session *AuthenticationSession, config json.RawMessage, _ pipeline.Rule) error { +func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session *AuthenticationSession, config json.RawMessage, _ pipeline.Rule) (err error) { + tp := trace.SpanFromContext(r.Context()).TracerProvider() + ctx, span := tp.Tracer("oauthkeeper/pipeline/authn").Start(r.Context(), "pipeline.authn.AuthenticatorOAuth2Introspection.Authenticate") + defer otelx.End(span, &err) + r = r.WithContext(ctx) + cf, client, err := a.Config(config) if err != nil { return err } token := helper.BearerTokenFromRequest(r, cf.BearerTokenLocation) - if token == "" { + if token == "" || !strings.HasPrefix(token, cf.Prefix) { return errors.WithStack(ErrAuthenticatorNotResponsible) } @@ -193,7 +204,7 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session body.Add("scope", strings.Join(cf.Scopes, " ")) } - introspectReq, err := http.NewRequest(http.MethodPost, cf.IntrospectionURL, strings.NewReader(body.Encode())) + introspectReq, err := http.NewRequestWithContext(ctx, http.MethodPost, cf.IntrospectionURL, strings.NewReader(body.Encode())) if err != nil { return errors.WithStack(err) } @@ -204,7 +215,11 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session // set/override the content-type header introspectReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, err := client.Do(introspectReq.WithContext(r.Context())) + if cf.PreserveHost { + introspectReq.Header.Set(header.XForwardedHost, r.Host) + } + + resp, err := client.Do(introspectReq) if err != nil { return errors.WithStack(err) } @@ -239,7 +254,7 @@ func (a *AuthenticatorOAuth2Introspection) Authenticate(r *http.Request, session if len(cf.Issuers) > 0 { if !stringslice.Has(cf.Issuers, i.Issuer) { - return errors.WithStack(helper.ErrForbidden.WithReason(fmt.Sprintf("Token issuer does not match any trusted issuer"))) + return errors.WithStack(helper.ErrForbidden.WithReason("Token issuer does not match any trusted issuer")) } } @@ -342,7 +357,7 @@ func (a *AuthenticatorOAuth2Introspection) Config(config json.RawMessage) (*Auth httpx.ResilientClientWithMaxRetryWait(maxWait), httpx.ResilientClientWithConnectionTimeout(timeout), ).StandardClient() - client.Transport = otelhttp.NewTransport(rt) + client.Transport = otelhttp.NewTransport(rt, otelhttp.WithTracerProvider(a.provider)) a.mu.Lock() a.clientMap[clientKey] = client a.mu.Unlock() diff --git a/pipeline/authn/authenticator_oauth2_introspection_cache_test.go b/pipeline/authn/authenticator_oauth2_introspection_cache_test.go index b0c600580e..49b1dced37 100644 --- a/pipeline/authn/authenticator_oauth2_introspection_cache_test.go +++ b/pipeline/authn/authenticator_oauth2_introspection_cache_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" "github.com/ory/fosite" "github.com/ory/oathkeeper/driver/configuration" @@ -30,7 +31,7 @@ func TestCache(t *testing.T) { })) require.NoError(t, err) - a := NewAuthenticatorOAuth2Introspection(c, logger) + a := NewAuthenticatorOAuth2Introspection(c, logger, trace.NewNoopTracerProvider()) assert.Equal(t, "oauth2_introspection", a.GetID()) config, _, err := a.Config(nil) diff --git a/pipeline/authn/authenticator_oauth2_introspection_test.go b/pipeline/authn/authenticator_oauth2_introspection_test.go index 5695a0d4e2..21efa8f405 100644 --- a/pipeline/authn/authenticator_oauth2_introspection_test.go +++ b/pipeline/authn/authenticator_oauth2_introspection_test.go @@ -12,16 +12,17 @@ import ( "testing" "time" + "go.opentelemetry.io/otel/trace" + "github.com/ory/x/assertx" "github.com/ory/x/configx" + "github.com/ory/x/logrusx" "github.com/julienschmidt/httprouter" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/sjson" - "github.com/ory/x/logrusx" - "github.com/ory/oathkeeper/driver/configuration" "github.com/ory/oathkeeper/internal" . "github.com/ory/oathkeeper/pipeline/authn" @@ -77,6 +78,13 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) { expectErr: true, expectExactErr: ErrAuthenticatorNotResponsible, }, + { + d: "should return error saying that authenticator is not responsible for validating the request, as the token does not have the specified prefix", + r: &http.Request{Header: http.Header{"Authorization": {"bearer secret_token"}}}, + config: []byte(`{"prefix": "not_secret"}`), + expectErr: true, + expectExactErr: ErrAuthenticatorNotResponsible, + }, { d: "should return error saying that authenticator is not responsible for validating the request, as the token was not provided in a proper location (custom query parameter)", r: &http.Request{ @@ -118,7 +126,7 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) { RawQuery: "token=" + "token", }, }, - config: []byte(`{"token_from": {"query_parameter": "token"}}`), + config: []byte(`{"token_from": {"query_parameter": "token"}, "prefix": "tok"}`), expectErr: false, setup: func(t *testing.T, m *httprouter.Router) { m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { @@ -165,6 +173,49 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) { }, expectErr: true, }, + { + d: "should pass and set host when preserve_host is true", + r: &http.Request{Host: "some-host", Header: http.Header{"Authorization": {"bearer token"}}, Method: "POST"}, + config: []byte(`{"preserve_host": true}`), + setup: func(t *testing.T, m *httprouter.Router) { + m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + require.NoError(t, r.ParseForm()) + require.Equal(t, "token", r.Form.Get("token")) + require.Equal(t, "some-host", r.Header.Get("X-Forwarded-Host")) + require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{ + Active: true, + Subject: "subject", + Audience: []string{"audience"}, + Issuer: "issuer", + Username: "username", + Extra: map[string]interface{}{"extra": "foo"}, + })) + }) + }, + expectErr: false, + }, { + d: "should pass additional headers to introspection endpoint ", + r: &http.Request{Host: "some-host", Header: http.Header{"Authorization": {"bearer token"}}, Method: "POST"}, + config: []byte(`{"preserve_host": true, "introspection_request_headers": {"X-Test": "test123", "X-Forwarded-For": "some-other-host", "Z-Test": "test987"}}`), + setup: func(t *testing.T, m *httprouter.Router) { + m.POST("/oauth2/introspect", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + require.NoError(t, r.ParseForm()) + require.Equal(t, "token", r.Form.Get("token")) + require.Equal(t, "some-host", r.Header.Get("X-Forwarded-Host"), "preserve_host takes precedence over introspection_request_headers") + require.Equal(t, "test123", r.Header.Get("X-Test"), "value configured in introspection_request_headers is set") + require.Equal(t, "test987", r.Header.Get("Z-Test"), "value configured in introspection_request_headers is set") + require.NoError(t, json.NewEncoder(w).Encode(&AuthenticatorOAuth2IntrospectionResult{ + Active: true, + Subject: "subject", + Audience: []string{"audience"}, + Issuer: "issuer", + Username: "username", + Extra: map[string]interface{}{"extra": "foo"}, + })) + }) + }, + expectErr: false, + }, { d: "should pass because active and no issuer / audience expected", r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}}, @@ -791,7 +842,7 @@ func TestAuthenticatorOAuth2Introspection(t *testing.T) { t.Run("method=config", func(t *testing.T) { logger := logrusx.New("test", "1") - authenticator := NewAuthenticatorOAuth2Introspection(conf, logger) + authenticator := NewAuthenticatorOAuth2Introspection(conf, logger, trace.NewNoopTracerProvider()) noPreauthConfig := []byte(`{ "introspection_url":"http://localhost/oauth2/token" }`) preAuthConfigOne := []byte(`{ "introspection_url":"http://localhost/oauth2/token","pre_authorization":{"token_url":"http://localhost/oauth2/token","client_id":"some_id","client_secret":"some_secret","enabled":true} }`) diff --git a/pipeline/authz/keto_engine_acp_ory.go b/pipeline/authz/keto_engine_acp_ory.go index 18a938ec66..3faf20f2d7 100644 --- a/pipeline/authz/keto_engine_acp_ory.go +++ b/pipeline/authz/keto_engine_acp_ory.go @@ -97,7 +97,7 @@ func (a *AuthorizerKetoEngineACPORY) WithContextCreator(f authorizerKetoWardenCo } func (a *AuthorizerKetoEngineACPORY) Authorize(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, _ pipeline.Rule) (err error) { - ctx, span := a.tracer.Start(r.Context(), "authz.keto_engine_acp_ory") + ctx, span := a.tracer.Start(r.Context(), "pipeline.authz.AuthorizerKetoEngineACPORY.Authorize") defer otelx.End(span, &err) r = r.WithContext(ctx) diff --git a/pipeline/authz/remote.go b/pipeline/authz/remote.go index 011ac926ba..37dad4f0bc 100644 --- a/pipeline/authz/remote.go +++ b/pipeline/authz/remote.go @@ -65,7 +65,7 @@ func (a *AuthorizerRemote) GetID() string { // Authorize implements the Authorizer interface. func (a *AuthorizerRemote) Authorize(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, rl pipeline.Rule) (err error) { - ctx, span := a.tracer.Start(r.Context(), "authz.remote") + ctx, span := a.tracer.Start(r.Context(), "pipeline.authz.AuthorizerRemote.Authorize") defer otelx.End(span, &err) r = r.WithContext(ctx) diff --git a/pipeline/authz/remote_json.go b/pipeline/authz/remote_json.go index c2f60abe29..5ddd1dc40b 100644 --- a/pipeline/authz/remote_json.go +++ b/pipeline/authz/remote_json.go @@ -70,7 +70,7 @@ func (a *AuthorizerRemoteJSON) GetID() string { // Authorize implements the Authorizer interface. func (a *AuthorizerRemoteJSON) Authorize(r *http.Request, session *authn.AuthenticationSession, config json.RawMessage, _ pipeline.Rule) (err error) { - ctx, span := a.tracer.Start(r.Context(), "authz.remote_json") + ctx, span := a.tracer.Start(r.Context(), "pipeline.authz.AuthorizerRemoteJSON.Authorize") defer otelx.End(span, &err) r = r.WithContext(ctx) diff --git a/pipeline/authz/remote_test.go b/pipeline/authz/remote_test.go index 993b5b565a..1c49cd8a03 100644 --- a/pipeline/authz/remote_test.go +++ b/pipeline/authz/remote_test.go @@ -6,7 +6,7 @@ package authz_test import ( "context" "encoding/json" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" @@ -83,7 +83,7 @@ func TestAuthorizerRemoteAuthorize(t *testing.T) { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Contains(t, r.Header, "Content-Type") assert.Contains(t, r.Header["Content-Type"], "text/plain") - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) require.NoError(t, err) assert.Equal(t, "", string(body)) w.WriteHeader(http.StatusOK) @@ -99,7 +99,7 @@ func TestAuthorizerRemoteAuthorize(t *testing.T) { assert.Contains(t, r.Header, "Content-Type") assert.Contains(t, r.Header["Content-Type"], "text/plain") assert.Nil(t, r.Header["Authorization"]) - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) require.NoError(t, err) assert.Equal(t, "testtest", string(body)) w.WriteHeader(http.StatusOK) @@ -112,7 +112,7 @@ func TestAuthorizerRemoteAuthorize(t *testing.T) { name: "ok with large body", setup: func(t *testing.T) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) require.NoError(t, err) assert.True(t, strings.Repeat("1", 1024*1024) == string(body)) w.WriteHeader(http.StatusOK) @@ -186,7 +186,7 @@ func TestAuthorizerRemoteAuthorize(t *testing.T) { }, } if tt.body != "" { - r.Body = ioutil.NopCloser(strings.NewReader(tt.body)) + r.Body = io.NopCloser(strings.NewReader(tt.body)) } if err := a.Authorize(r, tt.session, tt.config, &rule.Rule{}); (err != nil) != tt.wantErr { t.Errorf("Authorize() error = %v, wantErr %v", err, tt.wantErr) diff --git a/pipeline/authz/utils.go b/pipeline/authz/utils.go index ef621779da..b265039aed 100644 --- a/pipeline/authz/utils.go +++ b/pipeline/authz/utils.go @@ -6,7 +6,6 @@ package authz import ( "bytes" "io" - "io/ioutil" "net/http" ) @@ -21,6 +20,6 @@ func pipeRequestBody(r *http.Request, w io.Writer) error { if err != nil { return err } - r.Body = ioutil.NopCloser(&body) + r.Body = io.NopCloser(&body) return err } diff --git a/pipeline/context.go b/pipeline/context.go deleted file mode 100644 index bce151adff..0000000000 --- a/pipeline/context.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2023 Ory Corp -// SPDX-License-Identifier: Apache-2.0 - -package pipeline - -import ( - "net/http" - "net/url" -) - -type Context struct { -} - -type ContextRequest struct { - http.Request - Header http.Header `json:"header"` - MatchContext MatchContext `json:"match"` - Method string -} - -type ContextResponse struct { - URL string `json:"url"` - Header http.Header `json:"header"` - Proto string `json:"proto"` - Host string `json:"host"` - RemoteAddr string `json:"remote_address"` - - RegexpCaptureGroups []string `json:"regexp_capture_groups"` -} - -type AuthenticationSession struct { - Subject string `json:"subject"` - Extra map[string]interface{} `json:"extra"` - Header http.Header `json:"header"` - MatchContext MatchContext `json:"match_context"` -} - -type MatchContext struct { - RegexpCaptureGroups []string `json:"regexp_capture_groups"` - URL *url.URL `json:"url"` -} diff --git a/proxy/request_handler_test.go b/proxy/request_handler_test.go index 8f8d3f4830..4fe5a0ea6f 100644 --- a/proxy/request_handler_test.go +++ b/proxy/request_handler_test.go @@ -10,7 +10,6 @@ import ( "net/http/httptest" "testing" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/ory/herodot" @@ -252,25 +251,6 @@ errors: } } -type testWriter struct{ t *testing.T } - -func (tw *testWriter) Write(p []byte) (n int, err error) { - tw.t.Log(string(p)) - return len(p), nil -} - -type logHook struct{ t *testing.T } - -func (l *logHook) Levels() []logrus.Level { return logrus.AllLevels } -func (l *logHook) Fire(e *logrus.Entry) error { - s, err := e.String() - if err != nil { - return err - } - l.t.Log(s) - return nil -} - func TestRequestHandler(t *testing.T) { for k, tc := range []struct { d string diff --git a/rule/fetcher_default.go b/rule/fetcher_default.go index 278b2e6d4e..879ec0aa41 100644 --- a/rule/fetcher_default.go +++ b/rule/fetcher_default.go @@ -18,6 +18,7 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" + "go.opentelemetry.io/otel/trace" "gocloud.dev/blob" _ "gocloud.dev/blob/azureblob" _ "gocloud.dev/blob/gcsblob" @@ -37,6 +38,7 @@ var _ Fetcher = new(FetcherDefault) type fetcherRegistry interface { x.RegistryLogger RuleRepository() Repository + Tracer() trace.Tracer } type FetcherDefault struct { @@ -57,10 +59,13 @@ func NewFetcherDefault( registry fetcherRegistry, ) *FetcherDefault { return &FetcherDefault{ - registry: registry, - config: config, - mux: cloudstorage.NewURLMux(), - hc: httpx.NewResilientClient(httpx.ResilientClientWithConnectionTimeout(15 * time.Second)).StandardClient(), + registry: registry, + config: config, + mux: cloudstorage.NewURLMux(), + hc: httpx.NewResilientClient( + httpx.ResilientClientWithConnectionTimeout(15*time.Second), + httpx.ResilientClientWithTracer(registry.Tracer()), + ).StandardClient(), cache: make(map[string][]Rule), cancelWatchers: make(map[string]context.CancelFunc), events: make(chan watcherx.Event), diff --git a/rule/validator_test.go b/rule/validator_test.go index cf70902759..f69c196f62 100644 --- a/rule/validator_test.go +++ b/rule/validator_test.go @@ -169,9 +169,3 @@ func TestValidateRule(t *testing.T) { }) } } - -func assertReason(t *testing.T, err error, sub string) { - require.Error(t, err) - reason := errors.Cause(err).(*herodot.DefaultError).ReasonField - assert.True(t, strings.Contains(reason, sub), "%s", reason) -} diff --git a/spec/config.schema.json b/spec/config.schema.json index c036e98bb6..0c91e21283 100644 --- a/spec/config.schema.json +++ b/spec/config.schema.json @@ -483,6 +483,11 @@ } ] }, + "prefix": { + "title": "Token Prefix", + "type": "string", + "description": "The token is considered only if it starts with this string." + }, "preserve_query": { "title": "Preserve Query", "type": "boolean", @@ -822,6 +827,17 @@ "type": "string" } }, + "prefix": { + "title": "Token Prefix", + "type": "string", + "description": "The token is considered only if it starts with this string." + }, + "preserve_host": { + "title": "Preserve Host", + "type": "boolean", + "description": "When set to true the HTTP Header X-Forwarded-Host will be set to the original HTTP host.", + "default": false + }, "introspection_request_headers": { "title": "Introspection Request Headers", "description": "Additional headers to be added to the introspection request.", diff --git a/test/e2e/okclient/main.go b/test/e2e/okclient/main.go index a2c9613eba..c8324fdc90 100644 --- a/test/e2e/okclient/main.go +++ b/test/e2e/okclient/main.go @@ -7,7 +7,7 @@ import ( "bytes" "crypto/rsa" "encoding/json" - "io/ioutil" + "io" "net/http" "os" "time" @@ -85,7 +85,7 @@ func requestWithJWT(token string) (*http.Response, string) { res, err := http.DefaultClient.Do(req) cmdx.Must(err, "%s", err) defer res.Body.Close() - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) cmdx.Must(err, "%s", err) return res, string(body) } @@ -98,7 +98,7 @@ func decisionWithJWT(token string) (*http.Response, string) { res, err := http.DefaultClient.Do(req) cmdx.Must(err, "%s", err) defer res.Body.Close() - body, err := ioutil.ReadAll(res.Body) + body, err := io.ReadAll(res.Body) cmdx.Must(err, "%s", err) return res, string(body) }