Repository: distribution/distribution
Pull request #4306 (2024-03-19 10:05:50)

Replace custom Redis config struct with go-redis UniversalOptions (adds sentinel & cluster support)
Less code more features! :-D

This PR replaces the custom `Redis` config struct with `UniversalOptions` from `go-redis`, which implicitly adds support for both redis sentinel and redis clustering.

I have two failing tests that I can't quite figure out how to fix:
```
    --- FAIL: TestConfigSuite/TestMarshalRoundtrip (0.00s)
    --- FAIL: TestConfigSuite/TestParseInvalidVersion (0.00s)
```

<details><summary>Associated stack trace</summary>

```
    suite.go:87: test panicked: cannot marshal type: func(context.Context, string, string) (net.Conn, error)
        goroutine 18 [running]:
        runtime/debug.Stack()
        	/usr/lib/go-1.21/src/runtime/debug/stack.go:24 +0x5e
        github.com/stretchr/testify/suite.failOnPanic(0xc0002b8820, {0x8ebe40, 0xc0002c5850})
        	/home/aim/Workspace/forks/distribution/vendor/github.com/stretchr/testify/suite/suite.go:87 +0x39
        github.com/stretchr/testify/suite.Run.func1.1()
        	/home/aim/Workspace/forks/distribution/vendor/github.com/stretchr/testify/suite/suite.go:183 +0x24c
        panic({0x8ebe40?, 0xc0002c5850?})
        	/usr/lib/go-1.21/src/runtime/panic.go:914 +0x21f
        gopkg.in/yaml%2ev2.handleErr(0xc0001513c8)
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/yaml.go:249 +0x6d
        panic({0x8ebe40?, 0xc0002c5850?})
        	/usr/lib/go-1.21/src/runtime/panic.go:914 +0x21f
        gopkg.in/yaml%2ev2.(*encoder).marshal(0xc000220b00, {0x0, 0x0}, {0x913e00?, 0xc0002de618?, 0x0?})
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:183 +0xa34
        gopkg.in/yaml%2ev2.(*encoder).structv.func1()
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:226 +0x1fc
        gopkg.in/yaml%2ev2.(*encoder).mappingv(0xc000220b00, {0x0?, 0x0}, 0xc000150c58)
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:256 +0x14a
        gopkg.in/yaml%2ev2.(*encoder).structv(0xc000220b00, {0x0, 0x0}, {0x9778a0?, 0xc0002de5e8?, 0xc0002de6c8?})
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:213 +0xe5
        gopkg.in/yaml%2ev2.(*encoder).marshal(0xc000220b00, {0x0, 0x0}, {0x9778a0?, 0xc0002de5e8?, 0x91b7e0?})
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:160 +0x945
        gopkg.in/yaml%2ev2.(*encoder).structv.func1()
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:226 +0x1fc
        gopkg.in/yaml%2ev2.(*encoder).mappingv(0xc000220b00, {0x0?, 0x0}, 0xc00013d028)
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:256 +0x14a
        gopkg.in/yaml%2ev2.(*encoder).structv(0xc000220b00, {0x0, 0x0}, {0x96d600?, 0xc0002de400?, 0x18?})
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:213 +0xe5
        gopkg.in/yaml%2ev2.(*encoder).marshal(0xc000220b00, {0x0, 0x0}, {0x96d600?, 0xc0002de400?, 0x96d7c0?})
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:160 +0x945
        gopkg.in/yaml%2ev2.(*encoder).marshal(0xc000220b00, {0x0, 0x0}, {0x8dcb80?, 0xc0002de400?, 0xc00013d348?})
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:154 +0x74b
        gopkg.in/yaml%2ev2.(*encoder).marshalDoc(0xc000220b00, {0x0, 0x0}, {0x8dcb80?, 0xc0002de400?, 0xc00013d3c8?})
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/encode.go:93 +0xcb
        gopkg.in/yaml%2ev2.Marshal({0x8dcb80, 0xc0002de400})
        	/home/aim/Workspace/forks/distribution/vendor/gopkg.in/yaml.v2/yaml.go:203 +0x319
        github.com/distribution/distribution/v3/configuration.(*ConfigSuite).TestParseInvalidVersion(0xc00007e4b0)
        	/home/aim/Workspace/forks/distribution/configuration/configuration_test.go:397 +0xfa
        reflect.Value.call({0xc00007de80?, 0xc0000550b8?, 0x0?}, {0x99472c, 0x4}, {0xc000065e70, 0x1, 0xc000065d78?})
        	/usr/lib/go-1.21/src/reflect/value.go:596 +0xce7
        reflect.Value.Call({0xc00007de80?, 0xc0000550b8?, 0xc00007e4b0?}, {0xc000065e70?, 0x5214cd?, 0xc69e78?})
        	/usr/lib/go-1.21/src/reflect/value.go:380 +0xb9
        github.com/stretchr/testify/suite.Run.func1(0xc0002b8820)
        	/home/aim/Workspace/forks/distribution/vendor/github.com/stretchr/testify/suite/suite.go:197 +0x467
        testing.tRunner(0xc0002b8820, 0xc00019c630)
        	/usr/lib/go-1.21/src/testing/testing.go:1595 +0xff
        created by testing.(*T).Run in goroutine 6
        	/usr/lib/go-1.21/src/testing/testing.go:1648 +0x3ad
```

</details>

As v3 is still in alpha I was hoping that there would still be time for this to make it into the release.

Jamstah: Please sign your pr as per https://github.com/distribution/distribution/blob/main/CONTRIBUTING.md#contributing-code
andsens: @Jamstah done.  
@milosgajdos, I understand the issue. I mean, we could just replicate the entire struct I guess? In practice the options struct [seems to be quite stable](https://github.com/redis/go-redis/blame/master/universal.go#L12) and well thought out. The tests will catch any incompatible changes when bumping and then we can decide from there what to do about it, rather than doing a bunch of work that might not be necessary.

p.s.: I figured out the test failure: The marshaller is trying to serialize the `Dialer` function I believe, but I'm not familiar enough with the rest of the code to determine how to fix that.
andsens: Also, it looks like `redis.tls.enabled` had no effect previously? I don't see it used anywhere...
andsens: @milosgajdos, replicating the struct actually fixes the marshalling sooo :man_shrugging: fixed :-D
Jamstah: We're already using the go module to provide the client itself, so why not also use it to provide the config struct?

If we ever change go module it would need rewriting anyway.

I like the idea of matching our config to be more "standard".
andsens: *ping after the holidays. I would love if we could figure out how to progress with this.
andsens: Here are the structs with all the options specified:

* Simple mode: https://github.com/redis/go-redis/blob/v9.1.0/options.go#L31-L139
* Sentinel/failover mode: https://github.com/redis/go-redis/blob/v9.1.0/sentinel.go#L21-L81
* Cluster mode: https://github.com/redis/go-redis/blob/v9.1.0/cluster.go#L28-L87

I'm starting to think that using the redis-go UnviversalOptions struct directly is the better option here. It cuts down on maintenance and documentation. We can simply link out to e.g. https://pkg.go.dev/github.com/go-redis/redis/v9#UniversalOptions.

All that is needed for that is figuring out how to get around the marshalling issue.
milosgajdos: > I'm starting to think that using the redis-go UniversalOptions struct directly is the better option here. It cuts down on maintenance and documentation. We can simply link out to e.g. https://pkg.go.dev/github.com/go-redis/redis/v9#UniversalOptions.

I think that might be the best course of action should we decide to go forth with this.
milosgajdos: @andsens can you sign your commit, please
andsens: @milosgajdos ah crap, sry. I reverted to the previous version and forgot to amend. Will do when I get back to my work PC. In the meantime, what do we do about the failed marshalling of the config?  
It's not used anywhere except during testing. I guess it's a useful feature for debugging.
milosgajdos: There is some silly reflection stuff in `configuration` package to enable the ENV var shortcuts via cli...we should try fixing it
andsens: *bump  
Is there any chance someone can help me with this? I don't have the expertise to get those two tests to pass.  
We have been using this version in production for 2 months now, and it's been working like a charm. The failover on redis with sentinel happens seamlessly and through it all parts of distribution are now HA.
milosgajdos: Yeah, the problem is `UniversalOptions` contain _functions_ as fields 🫠 which is ....not great in...like...any Go code for that matter. 

The configuration marshaler naturally barfs its guts encountering them....this also complicates Registry's env var configuration -- which frankly I wouldn't mind ditching, but many are relying on passing config via env vars.

I need to have a think about this...
milosgajdos: this is the diff of what it _could_ look like @andsens ; this patch still doesn't pass tests but it no longer panics, so you'll have to massage the tests if you go with this...might need a bit more refactoring/thinking maybe 😄 

<details><summary>Toggle me!</summary>

```diff
diff --git configuration/configuration.go configuration/configuration.go
index 26216078..b38d21b8 100644
--- configuration/configuration.go
+++ configuration/configuration.go
@@ -175,7 +175,7 @@ type Configuration struct {
 	Notifications Notifications `yaml:"notifications,omitempty"`
 
 	// Redis configures the redis pool available to the registry webapp.
-	Redis redis.UniversalOptions `yaml:"redis,omitempty"`
+	Redis Redis `yaml:"redis,omitempty"`
 
 	Health  Health  `yaml:"health,omitempty"`
 	Catalog Catalog `yaml:"catalog,omitempty"`
@@ -652,3 +652,28 @@ func Parse(rd io.Reader) (*Configuration, error) {
 
 	return config, nil
 }
+
+type Redis struct {
+	redis.UniversalOptions
+}
+
+func (c Redis) MarshalYAML() (interface{}, error) {
+	fields := make(map[string]interface{})
+
+	val := reflect.ValueOf(c.UniversalOptions)
+	typ := val.Type()
+
+	for i := 0; i < val.NumField(); i++ {
+		field := typ.Field(i)
+		fieldValue := val.Field(i)
+
+		// ignore imports and funcs
+		if field.PkgPath != "" || fieldValue.Kind() == reflect.Func {
+			continue
+		}
+
+		fields[field.Name] = fieldValue.Interface()
+	}
+
+	return fields, nil
+}
diff --git configuration/configuration_test.go configuration/configuration_test.go
index 3a1e0d09..5360b129 100644
--- configuration/configuration_test.go
+++ configuration/configuration_test.go
@@ -131,17 +131,19 @@ var configStruct = Configuration{
 			Enabled: true,
 		},
 	},
-	Redis: redis.UniversalOptions{
-		Addrs:     []string{"localhost:6379"},
-		Username: "alice",
-		Password: "123456",
-		DB:       1,
-		MaxIdleConns: 16,
-		PoolSize: 64,
-		ConnMaxIdleTime: time.Second * 300,
-		DialTimeout:  time.Millisecond * 10,
-		ReadTimeout:  time.Millisecond * 10,
-		WriteTimeout: time.Millisecond * 10,
+	Redis: Redis{
+		redis.UniversalOptions{
+			Addrs:           []string{"localhost:6379"},
+			Username:        "alice",
+			Password:        "123456",
+			DB:              1,
+			MaxIdleConns:    16,
+			PoolSize:        64,
+			ConnMaxIdleTime: time.Second * 300,
+			DialTimeout:     time.Millisecond * 10,
+			ReadTimeout:     time.Millisecond * 10,
+			WriteTimeout:    time.Millisecond * 10,
+		},
 	},
 }
 
@@ -263,7 +265,7 @@ func (suite *ConfigSuite) TestParseSimple() {
 func (suite *ConfigSuite) TestParseInmemory() {
 	suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
 	suite.expectedConfig.Log.Fields = nil
-	suite.expectedConfig.Redis = redis.UniversalOptions{}
+	suite.expectedConfig.Redis = Redis{}
 
 	config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1)))
 	suite.Require().NoError(err)
@@ -283,7 +285,7 @@ func (suite *ConfigSuite) TestParseIncomplete() {
 	suite.expectedConfig.Auth = Auth{"silly": Parameters{"realm": "silly"}}
 	suite.expectedConfig.Notifications = Notifications{}
 	suite.expectedConfig.HTTP.Headers = nil
-	suite.expectedConfig.Redis = redis.UniversalOptions{}
+	suite.expectedConfig.Redis = Redis{}
 
 	// Note: this also tests that REGISTRY_STORAGE and
 	// REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY can be used together
diff --git registry/handlers/app.go registry/handlers/app.go
index 373d28c1..e108dc2e 100644
--- registry/handlers/app.go
+++ registry/handlers/app.go
@@ -492,7 +492,7 @@ func (app *App) configureRedis(cfg *configuration.Configuration) {
 		return
 	}
 
-	app.redis = app.createPool(cfg.Redis)
+	app.redis = app.createPool(cfg.Redis.UniversalOptions)
 
 	// Enable metrics instrumentation.
 	if err := redisotel.InstrumentMetrics(app.redis); err != nil {
@@ -518,8 +518,8 @@ func (app *App) createPool(cfg redis.UniversalOptions) redis.UniversalClient {
 	cfg.OnConnect = func(ctx context.Context, cn *redis.Conn) error {
 		res := cn.Ping(ctx)
 		return res.Err()
-	};
-	return redis.NewUniversalClient(&cfg);
+	}
+	return redis.NewUniversalClient(&cfg)
 }
 
 // configureLogHook prepares logging hook parameters.
```
</details>
andsens: Brilliant! Thank you @milosgajdos! I'll see if I can find the time this week to apply it and get the tests to pass :-)
milosgajdos: @andsens how are you getting on with this PR? If you don't have any bandwidth I might look into it this week. 
andsens: @milosgajdos could you?  
I have actually made the time for it now, but after trying to get unmarshalling to work so the tests pass I have to admit that I have way too little experience with Go to understand how that entire show is run.  
I can see that we also need to introduce some defaults. Lifting them from [options.go in go-redis](https://github.com/redis/go-redis/blob/f752b9a9d5cc158381c2ffe5b13c531037426b39/options.go#L158-L222) seems like the most sensible thing to do (a bit annoying they don't have it for the `UniversalOptions` variant).
milosgajdos: So I tried taking this over and pushing a patch to your branch but don't have the perms, @andsens 

I think the issue is your branch is on an ORG repo rather than a PERSONAL repo so I can't push there...because "GH reasons"

If you have time you could apply the patch below which works and fixes all the tests.

**TL;DR:** I had to add an `UnmarshalYAML` func that handles unmarshalling because we have a nested imported struct there...so...can't easily add `yaml` struct tags...anyways, the patch is below

<details><summary>Toggle me!</summary>

```diff
diff --git configuration/configuration.go configuration/configuration.go
index 26216078..884552da 100644
--- configuration/configuration.go
+++ configuration/configuration.go
@@ -175,7 +175,7 @@ type Configuration struct {
 	Notifications Notifications `yaml:"notifications,omitempty"`
 
 	// Redis configures the redis pool available to the registry webapp.
-	Redis redis.UniversalOptions `yaml:"redis,omitempty"`
+	Redis Redis `yaml:"redis,omitempty"`
 
 	Health  Health  `yaml:"health,omitempty"`
 	Catalog Catalog `yaml:"catalog,omitempty"`
@@ -652,3 +652,124 @@ func Parse(rd io.Reader) (*Configuration, error) {
 
 	return config, nil
 }
+
+type Redis struct {
+	redis.UniversalOptions
+}
+
+func (c Redis) MarshalYAML() (interface{}, error) {
+	fields := make(map[string]interface{})
+
+	val := reflect.ValueOf(c.UniversalOptions)
+	typ := val.Type()
+
+	for i := 0; i < val.NumField(); i++ {
+		field := typ.Field(i)
+		fieldValue := val.Field(i)
+
+		// ignore imports and funcs
+		if field.PkgPath != "" || fieldValue.Kind() == reflect.Func {
+			continue
+		}
+
+		fields[strings.ToLower(field.Name)] = fieldValue.Interface()
+	}
+
+	return fields, nil
+}
+
+func (c *Redis) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	var fields map[string]interface{}
+	err := unmarshal(&fields)
+	if err != nil {
+		return err
+	}
+
+	val := reflect.ValueOf(&c.UniversalOptions).Elem()
+	typ := val.Type()
+
+	for i := 0; i < typ.NumField(); i++ {
+		field := typ.Field(i)
+		fieldName := strings.ToLower(field.Name)
+
+		if value, ok := fields[fieldName]; ok {
+			fieldValue := val.Field(i)
+			if fieldValue.CanSet() {
+				switch field.Type {
+				case reflect.TypeOf(time.Duration(0)):
+					durationStr, ok := value.(string)
+					if !ok {
+						return fmt.Errorf("invalid duration value for field: %s", fieldName)
+					}
+					duration, err := time.ParseDuration(durationStr)
+					if err != nil {
+						return fmt.Errorf("failed to parse duration for field: %s, error: %v", fieldName, err)
+					}
+					fieldValue.Set(reflect.ValueOf(duration))
+				default:
+					if err := setFieldValue(fieldValue, value); err != nil {
+						return fmt.Errorf("failed to set value for field: %s, error: %v", fieldName, err)
+					}
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+func setFieldValue(field reflect.Value, value interface{}) error {
+	if value == nil {
+		return nil
+	}
+
+	switch field.Kind() {
+	case reflect.String:
+		stringValue, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("failed to convert value to string")
+		}
+		field.SetString(stringValue)
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		intValue, ok := value.(int)
+		if !ok {
+			return fmt.Errorf("failed to convert value to integer")
+		}
+		field.SetInt(int64(intValue))
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		uintValue, ok := value.(uint)
+		if !ok {
+			return fmt.Errorf("failed to convert value to unsigned integer")
+		}
+		field.SetUint(uint64(uintValue))
+	case reflect.Float32, reflect.Float64:
+		floatValue, ok := value.(float64)
+		if !ok {
+			return fmt.Errorf("failed to convert value to float")
+		}
+		field.SetFloat(floatValue)
+	case reflect.Bool:
+		boolValue, ok := value.(bool)
+		if !ok {
+			return fmt.Errorf("failed to convert value to boolean")
+		}
+		field.SetBool(boolValue)
+	case reflect.Slice:
+		slice := reflect.MakeSlice(field.Type(), 0, 0)
+		valueSlice, ok := value.([]interface{})
+		if !ok {
+			return fmt.Errorf("failed to convert value to slice")
+		}
+		for _, item := range valueSlice {
+			sliceValue := reflect.New(field.Type().Elem()).Elem()
+			if err := setFieldValue(sliceValue, item); err != nil {
+				return err
+			}
+			slice = reflect.Append(slice, sliceValue)
+		}
+		field.Set(slice)
+	default:
+		return fmt.Errorf("unsupported field type: %v", field.Type())
+	}
+	return nil
+}
diff --git configuration/configuration_test.go configuration/configuration_test.go
index 3a1e0d09..b7018807 100644
--- configuration/configuration_test.go
+++ configuration/configuration_test.go
@@ -131,17 +131,19 @@ var configStruct = Configuration{
 			Enabled: true,
 		},
 	},
-	Redis: redis.UniversalOptions{
-		Addrs:     []string{"localhost:6379"},
-		Username: "alice",
-		Password: "123456",
-		DB:       1,
-		MaxIdleConns: 16,
-		PoolSize: 64,
-		ConnMaxIdleTime: time.Second * 300,
-		DialTimeout:  time.Millisecond * 10,
-		ReadTimeout:  time.Millisecond * 10,
-		WriteTimeout: time.Millisecond * 10,
+	Redis: Redis{
+		redis.UniversalOptions{
+			Addrs:           []string{"localhost:6379"},
+			Username:        "alice",
+			Password:        "123456",
+			DB:              1,
+			MaxIdleConns:    16,
+			PoolSize:        64,
+			ConnMaxIdleTime: time.Second * 300,
+			DialTimeout:     time.Millisecond * 10,
+			ReadTimeout:     time.Millisecond * 10,
+			WriteTimeout:    time.Millisecond * 10,
+		},
 	},
 }
 
@@ -187,7 +189,7 @@ http:
 redis:
   addrs: [localhost:6379]
   username: alice
-  password: 123456
+  password: "123456"
   db: 1
   maxidleconns: 16
   poolsize: 64
@@ -263,7 +265,7 @@ func (suite *ConfigSuite) TestParseSimple() {
 func (suite *ConfigSuite) TestParseInmemory() {
 	suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
 	suite.expectedConfig.Log.Fields = nil
-	suite.expectedConfig.Redis = redis.UniversalOptions{}
+	suite.expectedConfig.Redis = Redis{}
 
 	config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1)))
 	suite.Require().NoError(err)
@@ -283,7 +285,7 @@ func (suite *ConfigSuite) TestParseIncomplete() {
 	suite.expectedConfig.Auth = Auth{"silly": Parameters{"realm": "silly"}}
 	suite.expectedConfig.Notifications = Notifications{}
 	suite.expectedConfig.HTTP.Headers = nil
-	suite.expectedConfig.Redis = redis.UniversalOptions{}
+	suite.expectedConfig.Redis = Redis{}
 
 	// Note: this also tests that REGISTRY_STORAGE and
 	// REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY can be used together
diff --git registry/handlers/app.go registry/handlers/app.go
index 373d28c1..e108dc2e 100644
--- registry/handlers/app.go
+++ registry/handlers/app.go
@@ -492,7 +492,7 @@ func (app *App) configureRedis(cfg *configuration.Configuration) {
 		return
 	}
 
-	app.redis = app.createPool(cfg.Redis)
+	app.redis = app.createPool(cfg.Redis.UniversalOptions)
 
 	// Enable metrics instrumentation.
 	if err := redisotel.InstrumentMetrics(app.redis); err != nil {
@@ -518,8 +518,8 @@ func (app *App) createPool(cfg redis.UniversalOptions) redis.UniversalClient {
 	cfg.OnConnect = func(ctx context.Context, cn *redis.Conn) error {
 		res := cn.Ping(ctx)
 		return res.Err()
-	};
-	return redis.NewUniversalClient(&cfg);
+	}
+	return redis.NewUniversalClient(&cfg)
 }
 
 // configureLogHook prepares logging hook parameters.

```
</details>
 
If you grab that diff save it in a file like `foo.patch` and then do
```
git apply foo.patch
```

That should get your tests and lining issues sorted 🤞 

Patch:
diff --git a/Dockerfile b/Dockerfile
index ac9edb79..6a937206 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -52,9 +52,9 @@ COPY --from=releaser /out /
 
 FROM alpine:${ALPINE_VERSION}
 RUN apk add --no-cache ca-certificates
-COPY cmd/registry/config-dev.yml /etc/distribution/config.yml
+COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
 COPY --from=binary /registry /bin/registry
 VOLUME ["/var/lib/registry"]
 EXPOSE 5000
 ENTRYPOINT ["registry"]
-CMD ["serve", "/etc/distribution/config.yml"]
+CMD ["serve", "/etc/docker/registry/config.yml"]
diff --git a/cmd/registry/config-cache.yml b/cmd/registry/config-cache.yml
index 72da9d60..b553ad5b 100644
--- a/cmd/registry/config-cache.yml
+++ b/cmd/registry/config-cache.yml
@@ -12,8 +12,6 @@ storage:
     maintenance:
         uploadpurging:
             enabled: false
-    tag:
-      concurrencylimit: 8
 http:
     addr: :5000
     secret: asecretforlocaldevelopment
@@ -22,11 +20,10 @@ http:
     headers:
         X-Content-Type-Options: [nosniff]
 redis:
-  addr: localhost:6379
-  pool:
-    maxidle: 16
-    maxactive: 64
-    idletimeout: 300s
+  addrs: [localhost:6379]
+  maxidleconns: 16
+  poolsize: 64
+  connmaxidletime: 300s
   dialtimeout: 10ms
   readtimeout: 10ms
   writetimeout: 10ms
diff --git a/cmd/registry/config-dev.yml b/cmd/registry/config-dev.yml
index 68fd83c8..9bf36583 100644
--- a/cmd/registry/config-dev.yml
+++ b/cmd/registry/config-dev.yml
@@ -14,8 +14,6 @@ storage:
     maintenance:
         uploadpurging:
             enabled: false
-    tag:
-      concurrencylimit: 8
 http:
     addr: :5000
     debug:
diff --git a/cmd/registry/config-example.yml b/cmd/registry/config-example.yml
index 2cb40206..c760cd56 100644
--- a/cmd/registry/config-example.yml
+++ b/cmd/registry/config-example.yml
@@ -7,8 +7,6 @@ storage:
     blobdescriptor: inmemory
   filesystem:
     rootdirectory: /var/lib/registry
-  tag:
-    concurrencylimit: 8
 http:
   addr: :5000
   headers:
diff --git a/configuration/configuration.go b/configuration/configuration.go
index 0dbf68c0..783cd2ea 100644
--- a/configuration/configuration.go
+++ b/configuration/configuration.go
@@ -8,6 +8,8 @@ import (
 	"reflect"
 	"strings"
 	"time"
+
+	"github.com/redis/go-redis/v9"
 )
 
 // Configuration is a versioned registry configuration, intended to be provided by a yaml file, and
@@ -181,7 +183,25 @@ type Configuration struct {
 	Proxy Proxy `yaml:"proxy,omitempty"`
 
 	// Validation configures validation options for the registry.
-	Validation Validation `yaml:"validation,omitempty"`
+	Validation struct {
+		// Enabled enables the other options in this section. This field is
+		// deprecated in favor of Disabled.
+		Enabled bool `yaml:"enabled,omitempty"`
+		// Disabled disables the other options in this section.
+		Disabled bool `yaml:"disabled,omitempty"`
+		// Manifests configures manifest validation.
+		Manifests struct {
+			// URLs configures validation for URLs in pushed manifests.
+			URLs struct {
+				// Allow specifies regular expressions (https://godoc.org/regexp/syntax)
+				// that URLs in pushed manifests must match.
+				Allow []string `yaml:"allow,omitempty"`
+				// Deny specifies regular expressions (https://godoc.org/regexp/syntax)
+				// that URLs in pushed manifests must not match.
+				Deny []string `yaml:"deny,omitempty"`
+			} `yaml:"urls,omitempty"`
+		} `yaml:"manifests,omitempty"`
+	} `yaml:"validation,omitempty"`
 
 	// Policy configures registry policy options.
 	Policy struct {
@@ -259,44 +279,6 @@ type FileChecker struct {
 	Threshold int `yaml:"threshold,omitempty"`
 }
 
-// Redis configures the redis pool available to the registry webapp.
-type Redis struct {
-	// Addr specifies the redis instance available to the application.
-	Addr string `yaml:"addr,omitempty"`
-
-	// Usernames can be used as a finer-grained permission control since the introduction of the redis 6.0.
-	Username string `yaml:"username,omitempty"`
-
-	// Password string to use when making a connection.
-	Password string `yaml:"password,omitempty"`
-
-	// DB specifies the database to connect to on the redis instance.
-	DB int `yaml:"db,omitempty"`
-
-	// TLS configures settings for redis in-transit encryption
-	TLS struct {
-		Enabled bool `yaml:"enabled,omitempty"`
-	} `yaml:"tls,omitempty"`
-
-	DialTimeout  time.Duration `yaml:"dialtimeout,omitempty"`  // timeout for connect
-	ReadTimeout  time.Duration `yaml:"readtimeout,omitempty"`  // timeout for reads of data
-	WriteTimeout time.Duration `yaml:"writetimeout,omitempty"` // timeout for writes of data
-
-	// Pool configures the behavior of the redis connection pool.
-	Pool struct {
-		// MaxIdle sets the maximum number of idle connections.
-		MaxIdle int `yaml:"maxidle,omitempty"`
-
-		// MaxActive sets the maximum number of connections that should be
-		// opened before blocking a connection request.
-		MaxActive int `yaml:"maxactive,omitempty"`
-
-		// IdleTimeout sets the amount time to wait before closing
-		// inactive connections.
-		IdleTimeout time.Duration `yaml:"idletimeout,omitempty"`
-	} `yaml:"pool,omitempty"`
-}
-
 // HTTPChecker is a type of entry in the health section for checking HTTP URIs.
 type HTTPChecker struct {
 	// Timeout is the duration to wait before timing out the HTTP request
@@ -348,13 +330,6 @@ type Health struct {
 	} `yaml:"storagedriver,omitempty"`
 }
 
-type Platform struct {
-	// Architecture is the architecture for this platform
-	Architecture string `yaml:"architecture,omitempty"`
-	// OS is the operating system for this platform
-	OS string `yaml:"os,omitempty"`
-}
-
 // v0_1Configuration is a Version 0.1 Configuration struct
 // This is currently aliased to Configuration, as it is the current version
 type v0_1Configuration Configuration
@@ -430,8 +405,6 @@ func (storage Storage) Type() string {
 			// allow configuration of delete
 		case "redirect":
 			// allow configuration of redirect
-		case "tag":
-			// allow configuration of tag
 		default:
 			storageType = append(storageType, k)
 		}
@@ -445,19 +418,6 @@ func (storage Storage) Type() string {
 	return ""
 }
 
-// TagParameters returns the Parameters map for a Storage tag configuration
-func (storage Storage) TagParameters() Parameters {
-	return storage["tag"]
-}
-
-// setTagParameter changes the parameter at the provided key to the new value
-func (storage Storage) setTagParameter(key string, value interface{}) {
-	if _, ok := storage["tag"]; !ok {
-		storage["tag"] = make(Parameters)
-	}
-	storage["tag"][key] = value
-}
-
 // Parameters returns the Parameters map for a Storage configuration
 func (storage Storage) Parameters() Parameters {
 	return storage[storage.Type()]
@@ -486,8 +446,6 @@ func (storage *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error {
 					// allow configuration of delete
 				case "redirect":
 					// allow configuration of redirect
-				case "tag":
-					// allow configuration of tag
 				default:
 					types = append(types, k)
 				}
@@ -642,62 +600,6 @@ type Proxy struct {
 	TTL *time.Duration `yaml:"ttl,omitempty"`
 }
 
-type Validation struct {
-	// Enabled enables the other options in this section. This field is
-	// deprecated in favor of Disabled.
-	Enabled bool `yaml:"enabled,omitempty"`
-	// Disabled disables the other options in this section.
-	Disabled bool `yaml:"disabled,omitempty"`
-	// Manifests configures manifest validation.
-	Manifests ValidationManifests `yaml:"manifests,omitempty"`
-}
-
-type ValidationManifests struct {
-	// URLs configures validation for URLs in pushed manifests.
-	URLs struct {
-		// Allow specifies regular expressions (https://godoc.org/regexp/syntax)
-		// that URLs in pushed manifests must match.
-		Allow []string `yaml:"allow,omitempty"`
-		// Deny specifies regular expressions (https://godoc.org/regexp/syntax)
-		// that URLs in pushed manifests must not match.
-		Deny []string `yaml:"deny,omitempty"`
-	} `yaml:"urls,omitempty"`
-	// ImageIndexes configures validation of image indexes
-	Indexes ValidationIndexes `yaml:"indexes,omitempty"`
-}
-
-type ValidationIndexes struct {
-	// Platforms configures the validation applies to the platform images included in an image index
-	Platforms Platforms `yaml:"platforms"`
-	// PlatformList filters the set of platforms to validate for image existence.
-	PlatformList []Platform `yaml:"platformlist,omitempty"`
-}
-
-// Platforms configures the validation applies to the platform images included in an image index
-// This can be all, none, or list
-type Platforms string
-
-// UnmarshalYAML implements the yaml.Umarshaler interface
-// Unmarshals a string into a Platforms option, lowercasing the string and validating that it represents a
-// valid option
-func (platforms *Platforms) UnmarshalYAML(unmarshal func(interface{}) error) error {
-	var platformsString string
-	err := unmarshal(&platformsString)
-	if err != nil {
-		return err
-	}
-
-	platformsString = strings.ToLower(platformsString)
-	switch platformsString {
-	case "all", "none", "list":
-	default:
-		return fmt.Errorf("invalid platforms option %s Must be one of [all, none, list]", platformsString)
-	}
-
-	*platforms = Platforms(platformsString)
-	return nil
-}
-
 // Parse parses an input configuration yaml document into a Configuration struct
 // This should generally be capable of handling old configuration format versions
 //
@@ -750,3 +652,172 @@ func Parse(rd io.Reader) (*Configuration, error) {
 
 	return config, nil
 }
+
+type RedisOptions = redis.UniversalOptions
+
+type RedisTLSOptions struct {
+	Certificate string   `yaml:"certificate,omitempty"`
+	Key         string   `yaml:"key,omitempty"`
+	ClientCAs   []string `yaml:"clientcas,omitempty"`
+}
+
+type Redis struct {
+	Options RedisOptions    `yaml:",inline"`
+	TLS     RedisTLSOptions `yaml:"tls,omitempty"`
+}
+
+func (c Redis) MarshalYAML() (interface{}, error) {
+	fields := make(map[string]interface{})
+
+	val := reflect.ValueOf(c.Options)
+	typ := val.Type()
+
+	for i := 0; i < val.NumField(); i++ {
+		field := typ.Field(i)
+		fieldValue := val.Field(i)
+
+		// ignore funcs fields in redis.UniversalOptions
+		if fieldValue.Kind() == reflect.Func {
+			continue
+		}
+
+		fields[strings.ToLower(field.Name)] = fieldValue.Interface()
+	}
+
+	// Add TLS fields if they're not empty
+	if c.TLS.Certificate != "" || c.TLS.Key != "" || len(c.TLS.ClientCAs) > 0 {
+		fields["tls"] = c.TLS
+	}
+
+	return fields, nil
+}
+
+func (c *Redis) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	var fields map[string]interface{}
+	err := unmarshal(&fields)
+	if err != nil {
+		return err
+	}
+
+	val := reflect.ValueOf(&c.Options).Elem()
+	typ := val.Type()
+
+	for i := 0; i < typ.NumField(); i++ {
+		field := typ.Field(i)
+		fieldName := strings.ToLower(field.Name)
+
+		if value, ok := fields[fieldName]; ok {
+			fieldValue := val.Field(i)
+			if fieldValue.CanSet() {
+				switch field.Type {
+				case reflect.TypeOf(time.Duration(0)):
+					durationStr, ok := value.(string)
+					if !ok {
+						return fmt.Errorf("invalid duration value for field: %s", fieldName)
+					}
+					duration, err := time.ParseDuration(durationStr)
+					if err != nil {
+						return fmt.Errorf("failed to parse duration for field: %s, error: %v", fieldName, err)
+					}
+					fieldValue.Set(reflect.ValueOf(duration))
+				default:
+					if err := setFieldValue(fieldValue, value); err != nil {
+						return fmt.Errorf("failed to set value for field: %s, error: %v", fieldName, err)
+					}
+				}
+			}
+		}
+	}
+
+	// Handle TLS fields
+	if tlsData, ok := fields["tls"]; ok {
+		tlsMap, ok := tlsData.(map[interface{}]interface{})
+		if !ok {
+			return fmt.Errorf("invalid TLS data structure")
+		}
+
+		if cert, ok := tlsMap["certificate"]; ok {
+			var isString bool
+			c.TLS.Certificate, isString = cert.(string)
+			if !isString {
+				return fmt.Errorf("Redis TLS certificate must be a string")
+			}
+		}
+		if key, ok := tlsMap["key"]; ok {
+			var isString bool
+			c.TLS.Key, isString = key.(string)
+			if !isString {
+				return fmt.Errorf("Redis TLS (private) key must be a string")
+			}
+		}
+		if cas, ok := tlsMap["clientcas"]; ok {
+			caList, ok := cas.([]interface{})
+			if !ok {
+				return fmt.Errorf("invalid clientcas data structure")
+			}
+			for _, ca := range caList {
+				if caStr, ok := ca.(string); ok {
+					c.TLS.ClientCAs = append(c.TLS.ClientCAs, caStr)
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+func setFieldValue(field reflect.Value, value interface{}) error {
+	if value == nil {
+		return nil
+	}
+
+	switch field.Kind() {
+	case reflect.String:
+		stringValue, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("failed to convert value to string")
+		}
+		field.SetString(stringValue)
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		intValue, ok := value.(int)
+		if !ok {
+			return fmt.Errorf("failed to convert value to integer")
+		}
+		field.SetInt(int64(intValue))
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		uintValue, ok := value.(uint)
+		if !ok {
+			return fmt.Errorf("failed to convert value to unsigned integer")
+		}
+		field.SetUint(uint64(uintValue))
+	case reflect.Float32, reflect.Float64:
+		floatValue, ok := value.(float64)
+		if !ok {
+			return fmt.Errorf("failed to convert value to float")
+		}
+		field.SetFloat(floatValue)
+	case reflect.Bool:
+		boolValue, ok := value.(bool)
+		if !ok {
+			return fmt.Errorf("failed to convert value to boolean")
+		}
+		field.SetBool(boolValue)
+	case reflect.Slice:
+		slice := reflect.MakeSlice(field.Type(), 0, 0)
+		valueSlice, ok := value.([]interface{})
+		if !ok {
+			return fmt.Errorf("failed to convert value to slice")
+		}
+		for _, item := range valueSlice {
+			sliceValue := reflect.New(field.Type().Elem()).Elem()
+			if err := setFieldValue(sliceValue, item); err != nil {
+				return err
+			}
+			slice = reflect.Append(slice, sliceValue)
+		}
+		field.Set(slice)
+	default:
+		return fmt.Errorf("unsupported field type: %v", field.Type())
+	}
+	return nil
+}
diff --git a/docs/content/about/configuration.md b/docs/content/about/configuration.md
index 77977c19..88f1a195 100644
--- a/docs/content/about/configuration.md
+++ b/docs/content/about/configuration.md
@@ -50,7 +50,7 @@ specify it in the `docker run` command:
 
 ```bash
 $ docker run -d -p 5000:5000 --restart=always --name registry \
-             -v `pwd`/config.yml:/etc/distribution/config.yml \
+             -v `pwd`/config.yml:/etc/docker/registry/config.yml \
              registry:2
 ```
 
@@ -141,8 +141,6 @@ storage:
     usedualstack: false
     loglevel: debug
   inmemory:  # This driver takes no parameters
-  tag:
-    concurrencylimit: 8
   delete:
     enabled: false
   redirect:
@@ -168,9 +166,6 @@ auth:
     service: token-service
     issuer: registry-token-issuer
     rootcertbundle: /root/certs/bundle
-    signingalgorithms:
-        - EdDSA
-        - HS256
   htpasswd:
     realm: basic-realm
     path: /path/to/htpasswd
@@ -246,16 +241,20 @@ notifications:
         actions:
            - pull
 redis:
-  addr: localhost:6379
+  tls:
+    certificate: /path/to/cert.crt
+    key: /path/to/key.pem
+    clientcas:
+      - /path/to/ca.pem
+  addrs: [localhost:6379]
   password: asecret
   db: 0
   dialtimeout: 10ms
   readtimeout: 10ms
   writetimeout: 10ms
-  pool:
-    maxidle: 16
-    maxactive: 64
-    idletimeout: 300s
+  maxidleconns: 16
+  poolsize: 64
+  connmaxidletime: 300s
   tls:
     enabled: false
 health:
@@ -291,11 +290,6 @@ validation:
         - ^https?://([^/]+\.)*example\.com/
       deny:
         - ^https?://www\.example\.com/
-    indexes:
-      platforms: List
-      platformlist:
-      - architecture: amd64
-        os: linux
 ```
 
 In some instances a configuration option is **optional** but it contains child
@@ -531,26 +525,6 @@ parameter sets a limit on the number of descriptors to store in the cache.
 The default value is 10000. If this parameter is set to 0, the cache is allowed
 to grow with no size limit.
 
-### `tag`
-
-The `tag` subsection provides configuration to set concurrency limit for tag lookup.
-When user calls into the registry to delete the manifest, which in turn then does a
-lookup for all tags that reference the deleted manifest. To find the tag references,
-the registry will iterate every tag in the repository and read it's link file to check
-if it matches the deleted manifest (i.e. to see if uses the same sha256 digest).
-So, the more tags in repository, the worse the performance will be (as there will
-be more S3 API calls occurring for the tag directory lookups and tag file reads if
-using S3 storage driver).
-
-Therefore, add a single flag `concurrencylimit` to set concurrency limit to optimize tag
-lookup performance under the `tag` section. When a value is not provided or equal to 0,
-`GOMAXPROCS` will be used.
-
-```yaml
-tag:
-  concurrencylimit: 8
-```
-
 ### `redirect`
 
 The `redirect` subsection provides configuration for managing redirects from
@@ -580,10 +554,6 @@ auth:
     service: token-service
     issuer: registry-token-issuer
     rootcertbundle: /root/certs/bundle
-    signingalgorithms:
-        - EdDSA
-        - HS256
-        - ES512
   htpasswd:
     realm: basic-realm
     path: /path/to/htpasswd
@@ -625,39 +595,8 @@ security.
 | `service` | yes      | The service being authenticated.                      |
 | `issuer`  | yes      | The name of the token issuer. The issuer inserts this into the token so it must match the value configured for the issuer. |
 | `rootcertbundle` | yes | The absolute path to the root certificate bundle. This bundle contains the public part of the certificates used to sign authentication tokens. |
-| `autoredirect`   | no      | When set to `true`, `realm` will automatically be set using the Host header of the request as the domain and a path of `/auth/token/`(or specified by `autoredirectpath`), the `realm` URL Scheme will use `X-Forwarded-Proto` header if set, otherwise it will be set to `https`. |
-| `autoredirectpath`   | no      | The path to redirect to if `autoredirect` is set to `true`, default: `/auth/token/`. |
-| `signingalgorithms`  | no      | A list of token signing algorithms to use for verifying token signatures. If left empty the default list of signing algorithms is used. Please see below for allowed values and default. |
-
-Available `signingalgorithms`:
-- EdDSA
-- HS256
-- HS384
-- HS512
-- RS256
-- RS384
-- RS512
-- ES256
-- ES384
-- ES512
-- PS256
-- PS384
-- PS512
-
-Default `signingalgorithms`:
-- EdDSA
-- HS256
-- HS384
-- HS512
-- RS256
-- RS384
-- RS512
-- ES256
-- ES384
-- ES512
-- PS256
-- PS384
-- PS512
+| `autoredirect`   | no      | When set to `true`, `realm` will automatically be set using the Host header of the request as the domain and a path of `/auth/token/`|
+
 
 For more information about Token based authentication configuration, see the
 [specification](../spec/auth/token.md).
@@ -1017,72 +956,46 @@ The `events` structure configures the information provided in event notification
 
 ## `redis`
 
-```yaml
-redis:
-  addr: localhost:6379
-  password: asecret
-  db: 0
-  dialtimeout: 10ms
-  readtimeout: 10ms
-  writetimeout: 10ms
-  pool:
-    maxidle: 16
-    maxactive: 64
-    idletimeout: 300s
-  tls:
-    enabled: false
-```
-
 Declare parameters for constructing the `redis` connections. Registry instances
 may use the Redis instance for several applications. Currently, it caches
 information about immutable blobs. Most of the `redis` options control
-how the registry connects to the `redis` instance. You can control the pool's
-behavior with the [pool](#pool) subsection. Additionally, you can control
-TLS connection settings with the [tls](#tls) subsection (in-transit encryption).
+how the registry connects to the `redis` instance.
 
 You should configure Redis with the **allkeys-lru** eviction policy, because the
 registry does not set an expiration value on keys.
 
-| Parameter | Required | Description                                           |
-|-----------|----------|-------------------------------------------------------|
-| `addr`    | yes      | The address (host and port) of the Redis instance.    |
-| `password`| no       | A password used to authenticate to the Redis instance.|
-| `db`      | no       | The name of the database to use for each connection.  |
-| `dialtimeout` | no   | The timeout for connecting to the Redis instance.     |
-| `readtimeout` | no   | The timeout for reading from the Redis instance.      |
-| `writetimeout` | no  | The timeout for writing to the Redis instance.        |
-
-### `pool`
+Under the hood distribution uses [`go-redis`](https://github.com/redis/go-redis) Go module for
+Redis connectivity and its [`UniversalOptions`](https://pkg.go.dev/github.com/redis/go-redis/v9#UniversalOptions)
+struct.
 
-```yaml
-pool:
-  maxidle: 16
-  maxactive: 64
-  idletimeout: 300s
-```
+You can optionally specify TLS configuration on top of the `UniversalOptions` settings.
 
-Use these settings to configure the behavior of the Redis connection pool.
+Use these settings to configure Redis TLS:
 
 | Parameter | Required | Description                                           |
 |-----------|----------|-------------------------------------------------------|
-| `maxidle` | no       | The maximum number of idle connections in the pool.   |
-| `maxactive`| no      | The maximum number of connections which can be open before blocking a connection request. |
-| `idletimeout`| no    | How long to wait before closing inactive connections. |
-
-### `tls`
+| `certificate`  | yes  | Absolute path to the x509 certificate file.           |
+| `key`          | yes  | Absolute path to the x509 private key file.           |
+| `clientcas`    | no   | An array of absolute paths to x509 CA files.          |
 
 ```yaml
-tls:
-  enabled: false
+redis:
+  tls:
+    certificate: /path/to/cert.crt
+    key: /path/to/key.pem
+    clientcas:
+      - /path/to/ca.pem
+  addrs: [localhost:6379]
+  password: asecret
+  db: 0
+  dialtimeout: 10ms
+  readtimeout: 10ms
+  writetimeout: 10ms
+  maxidleconns: 16
+  poolsize: 64
+  connmaxidletime: 300s
 ```
 
-Use these settings to configure Redis TLS.
-
-| Parameter | Required | Description                           |
-|-----------|----------|-------------------------------------- |
-| `enabled` | no       | Whether or not to use TLS in-transit. |
-
-
 ## `health`
 
 ```yaml
@@ -1202,14 +1115,14 @@ username (such as `batman`) and the password for that username.
 
 ```yaml
 validation:
-  disabled: false
+  manifests:
+    urls:
+      allow:
+        - ^https?://([^/]+\.)*example\.com/
+      deny:
+        - ^https?://www\.example\.com/
 ```
 
-Use these settings to configure what validation the registry performs on content.
-
-Validation is performed when content is uploaded to the registry. Changing these
-settings will not validate content that has already been accepting into the registry.
-
 ### `disabled`
 
 The `disabled` flag disables the other options in the `validation`
@@ -1222,16 +1135,6 @@ Use the `manifests` subsection to configure validation of manifests. If
 
 #### `urls`
 
-```yaml
-validation:
-  manifests:
-    urls:
-      allow:
-        - ^https?://([^/]+\.)*example\.com/
-      deny:
-        - ^https?://www\.example\.com/
-```
-
 The `allow` and `deny` options are each a list of
 [regular expressions](https://pkg.go.dev/regexp/syntax) that restrict the URLs in
 pushed manifests.
@@ -1245,54 +1148,6 @@ one of the `allow` regular expressions **and** one of the following holds:
 2. `deny` is set but no URLs within the manifest match any of the `deny` regular
    expressions.
 
-#### `indexes`
-
-By default the registry will validate that all platform images exist when an image
-index is uploaded to the registry. Disabling this validatation is experimental
-because other tooling that uses the registry may expect the image index to be complete.
-
-validation:
-  manifests:
-    indexes:
-      platforms: [all|none|list]
-      platformlist:
-      - os: linux
-        architecture: amd64
-
-Use these settings to configure what validation the registry performs on image
-index manifests uploaded to the registry.
-
-##### `platforms`
-
-Set `platformexist` to `all` (the default) to validate all platform images exist.
-The registry will validate that the images referenced by the index exist in the
-registry before accepting the image index.
-
-Set `platforms` to `none` to disable all validation that images exist when an
-image index manifest is uploaded. This allows image lists to be uploaded to the
-registry without their associated images. This setting is experimental because
-other tooling that uses the registry may expect the image index to be complete.
-
-Set `platforms` to `list` to selectively validate the existence of platforms
-within image index manifests. This setting is experimental because other tooling
-that uses the registry may expect the image index to be complete.
-
-##### `platformlist`
-
-When `platforms` is set to `list`, set `platformlist` to an array of
-platforms to validate. If a platform is included in this the array and in the images
-contained within an index, the registry will validate that the platform specific image
-exists in the registry before accepting the index. The registry will not validate the
-existence of platform specific images in the index that do not appear in the
-`platformlist` array.
-
-This parameter does not validate that the configured platforms are included in every
-index. If an image index does not include one of the platform specific images configured
-in the `platformlist` array, it may still be accepted by the registry.
-
-Each platform is a map with two keys, `os` and `architecture`, as defined in the
-[OCI Image Index specification](https://github.com/opencontainers/image-spec/blob/main/image-index.md#image-index-property-descriptions).
-
 ## Example: Development configuration
 
 You can use this simple example for local development:
diff --git a/docs/content/recipes/mirror.md b/docs/content/recipes/mirror.md
index c5c5a5dc..d5ec06a5 100644
--- a/docs/content/recipes/mirror.md
+++ b/docs/content/recipes/mirror.md
@@ -72,7 +72,7 @@ be configured to use the `filesystem` driver for storage.
 
 The easiest way to run a registry as a pull through cache is to run the official
 Registry image.
-At least, you need to specify `proxy.remoteurl` within `/etc/distribution/config.yml`
+At least, you need to specify `proxy.remoteurl` within `/etc/docker/registry/config.yml`
 as described in the following subsection.
 
 Multiple registry caches can be deployed over the same back-end. A single
diff --git a/docs/content/storage-drivers/s3.md b/docs/content/storage-drivers/s3.md
index 86d765a3..f50cc3d2 100644
--- a/docs/content/storage-drivers/s3.md
+++ b/docs/content/storage-drivers/s3.md
@@ -15,7 +15,7 @@ Amazon S3 or S3 compatible services for object storage.
 | `secretkey`  | no   | Your AWS Secret Key. If you use [IAM roles](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html), omit to fetch temporary credentials from IAM. |
 | `region` |  yes  | The AWS region in which your bucket exists. |
 | `regionendpoint` | no | Endpoint for S3 compatible storage services (Minio, etc). |
-| `forcepathstyle` | no | To enable path-style addressing when the value is set to `true`. The default is `false`. |
+| `forcepathstyle` | no | To enable path-style addressing when the value is set to `true`. The default is `true`. |
 | `bucket`  | yes | The bucket name in which you want to store the registry's data. |
 | `encrypt`  | no | Specifies whether the registry stores the image in encrypted format or not. A boolean value. The default is `false`. |
 | `keyid`  | no | Optional KMS key ID to use for encryption (encrypt must be true, or this parameter is ignored). The default is `none`. |
@@ -43,7 +43,7 @@ Amazon S3 or S3 compatible services for object storage.
 
 `regionendpoint`: (optional) Endpoint URL for S3 compatible APIs. This should not be provided when using Amazon S3.
 
-`forcepathstyle`: (optional) Force path style for S3 compatible APIs. Some manufacturers only support force path style, while others only support DNS based bucket routing. Amazon S3 supports both. The value of this parameter applies, regardless of the region settings.
+`forcepathstyle`: (optional) The force path style for S3 compatible APIs. Some manufacturers only support force path style, while others only support DNS based bucket routing. Amazon S3 supports both.
 
 `bucket`: The name of your S3 bucket where you wish to store objects. The bucket must exist prior to the driver initialization.
 
diff --git a/docs/dockerhub.md b/docs/dockerhub.md
index 0b4d5bb7..7db49e9b 100644
--- a/docs/dockerhub.md
+++ b/docs/dockerhub.md
@@ -31,7 +31,7 @@ docker tag alpine localhost:5000/alpine
 docker push localhost:5000/alpine
 ```
 
-⚠️  Beware the default configuration uses [`filesystem` storage driver](https://github.com/distribution/distribution/blob/main/docs/content/storage-drivers/filesystem.md)
+⚠️  Beware the default configuration uses [`filesystem` storage driver](https://github.com/distribution/distribution/blob/main/docs/storage-drivers/filesystem.md)
 and the above example command does not mount a local filesystem volume into the running container.
 If you wish to mount the local filesystem to the `rootdirectory` of the
 `filesystem` storage driver run the following command:
@@ -44,7 +44,7 @@ docker run -d -p 5000:5000 $PWD/FS/PATH:/var/lib/registry --restart always --nam
 If you don't wan to use the default configuration file, you can supply
 your own custom configuration file as follows:
 ```
-docker run -d -p 5000:5000 $PWD/PATH/TO/config.yml:/etc/distribution/config.yml --restart always --name registry distribution/distribution:edge
+docker run -d -p 5000:5000 $PWD/PATH/TO/config.yml:/etc/docker/registry/config.yml --restart always --name registry distribution/distribution:edge
 ```
 
 ## Communication
diff --git a/go.mod b/go.mod
index 8784b870..cd164561 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/distribution/distribution/v3
 
-go 1.21.8
+go 1.21
 
 require (
 	cloud.google.com/go/storage v1.30.1
@@ -14,7 +14,7 @@ require (
 	github.com/distribution/reference v0.6.0
 	github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
 	github.com/docker/go-metrics v0.0.1
-	github.com/go-jose/go-jose/v4 v4.0.2
+	github.com/go-jose/go-jose/v3 v3.0.3
 	github.com/google/uuid v1.3.1
 	github.com/gorilla/handlers v1.5.2
 	github.com/gorilla/mux v1.8.1
@@ -34,8 +34,8 @@ require (
 	go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0
 	go.opentelemetry.io/otel/sdk v1.21.0
 	go.opentelemetry.io/otel/trace v1.21.0
-	golang.org/x/crypto v0.21.0
-	golang.org/x/net v0.23.0
+	golang.org/x/crypto v0.19.0
+	golang.org/x/net v0.20.0
 	golang.org/x/oauth2 v0.11.0
 	google.golang.org/api v0.126.0
 	gopkg.in/yaml.v2 v2.4.0
@@ -88,8 +88,8 @@ require (
 	go.opentelemetry.io/otel/metric v1.21.0 // indirect
 	go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect
 	go.opentelemetry.io/proto/otlp v1.0.0 // indirect
-	golang.org/x/sync v0.3.0
-	golang.org/x/sys v0.18.0 // indirect
+	golang.org/x/sync v0.3.0 // indirect
+	golang.org/x/sys v0.17.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
diff --git a/go.sum b/go.sum
index bc7f4823..2e1976a3 100644
--- a/go.sum
+++ b/go.sum
@@ -81,8 +81,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
-github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
+github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
+github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -126,6 +126,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/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=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -286,13 +287,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 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/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -308,8 +310,10 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
-golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+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.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
@@ -319,6 +323,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -338,16 +343,23 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -357,6 +369,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/manifests.go b/manifests.go
index f38d3ce8..8f84a220 100644
--- a/manifests.go
+++ b/manifests.go
@@ -47,7 +47,7 @@ type ManifestBuilder interface {
 	AppendReference(dependency Describable) error
 }
 
-// ManifestService describes operations on manifests.
+// ManifestService describes operations on image manifests.
 type ManifestService interface {
 	// Exists returns true if the manifest exists.
 	Exists(ctx context.Context, dgst digest.Digest) (bool, error)
diff --git a/registry/api/v2/urls.go b/registry/api/v2/urls.go
index 80617fd3..1a76565a 100644
--- a/registry/api/v2/urls.go
+++ b/registry/api/v2/urls.go
@@ -202,7 +202,7 @@ func (ub *URLBuilder) BuildBlobUploadChunkURL(name reference.Named, uuid string,
 	return appendValuesURL(uploadURL, values...).String(), nil
 }
 
-// cloneRoute returns a clone of the named route from the router. Routes
+// clondedRoute returns a clone of the named route from the router. Routes
 // must be cloned to avoid modifying them during url generation.
 func (ub *URLBuilder) cloneRoute(name string) clonedRoute {
 	route := new(mux.Route)
diff --git a/registry/auth/auth.go b/registry/auth/auth.go
index 687002c3..b9c3b01d 100644
--- a/registry/auth/auth.go
+++ b/registry/auth/auth.go
@@ -46,7 +46,7 @@ var (
 )
 
 // InitFunc is the type of an AccessController factory function and is used
-// to register the constructor for different AccessController backends.
+// to register the constructor for different AccesController backends.
 type InitFunc func(options map[string]interface{}) (AccessController, error)
 
 var accessControllers map[string]InitFunc
@@ -56,7 +56,7 @@ func init() {
 }
 
 // UserInfo carries information about
-// an authenticated/authorized client.
+// an autenticated/authorized client.
 type UserInfo struct {
 	Name string
 }
diff --git a/registry/auth/token/accesscontroller.go b/registry/auth/token/accesscontroller.go
index e6dbaf48..8f610c46 100644
--- a/registry/auth/token/accesscontroller.go
+++ b/registry/auth/token/accesscontroller.go
@@ -9,12 +9,11 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"net/url"
 	"os"
 	"strings"
 
 	"github.com/distribution/distribution/v3/registry/auth"
-	"github.com/go-jose/go-jose/v4"
+	"github.com/go-jose/go-jose/v3"
 	"github.com/sirupsen/logrus"
 )
 
@@ -84,12 +83,11 @@ var (
 
 // authChallenge implements the auth.Challenge interface.
 type authChallenge struct {
-	err              error
-	realm            string
-	autoRedirect     bool
-	autoRedirectPath string
-	service          string
-	accessSet        accessSet
+	err          error
+	realm        string
+	autoRedirect bool
+	service      string
+	accessSet    accessSet
 }
 
 var _ auth.Challenge = authChallenge{}
@@ -104,28 +102,13 @@ func (ac authChallenge) Status() int {
 	return http.StatusUnauthorized
 }
 
-func buildAutoRedirectURL(r *http.Request, autoRedirectPath string) string {
-	scheme := "https"
-
-	if forwardedProto := r.Header.Get("X-Forwarded-Proto"); len(forwardedProto) > 0 {
-		scheme = forwardedProto
-	}
-
-	u := &url.URL{
-		Scheme: scheme,
-		Host:   r.Host,
-		Path:   autoRedirectPath,
-	}
-	return u.String()
-}
-
 // challengeParams constructs the value to be used in
 // the WWW-Authenticate response challenge header.
 // See https://tools.ietf.org/html/rfc6750#section-3
 func (ac authChallenge) challengeParams(r *http.Request) string {
 	var realm string
 	if ac.autoRedirect {
-		realm = buildAutoRedirectURL(r, ac.autoRedirectPath)
+		realm = fmt.Sprintf("https://%s/auth/token", r.Host)
 	} else {
 		realm = ac.realm
 	}
@@ -144,38 +127,30 @@ func (ac authChallenge) challengeParams(r *http.Request) string {
 	return str
 }
 
-// SetHeaders sets the WWW-Authenticate value for the response.
+// SetChallenge sets the WWW-Authenticate value for the response.
 func (ac authChallenge) SetHeaders(r *http.Request, w http.ResponseWriter) {
 	w.Header().Add("WWW-Authenticate", ac.challengeParams(r))
 }
 
 // accessController implements the auth.AccessController interface.
 type accessController struct {
-	realm             string
-	autoRedirect      bool
-	autoRedirectPath  string
-	issuer            string
-	service           string
-	rootCerts         *x509.CertPool
-	trustedKeys       map[string]crypto.PublicKey
-	signingAlgorithms []jose.SignatureAlgorithm
+	realm        string
+	autoRedirect bool
+	issuer       string
+	service      string
+	rootCerts    *x509.CertPool
+	trustedKeys  map[string]crypto.PublicKey
 }
 
-const (
-	defaultAutoRedirectPath = "/auth/token"
-)
-
 // tokenAccessOptions is a convenience type for handling
-// options to the constructor of an accessController.
+// options to the contstructor of an accessController.
 type tokenAccessOptions struct {
-	realm             string
-	autoRedirect      bool
-	autoRedirectPath  string
-	issuer            string
-	service           string
-	rootCertBundle    string
-	jwks              string
-	signingAlgorithms []string
+	realm          string
+	autoRedirect   bool
+	issuer         string
+	service        string
+	rootCertBundle string
+	jwks           string
 }
 
 // checkOptions gathers the necessary options
@@ -208,32 +183,10 @@ func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
 	if ok {
 		autoRedirect, ok := autoRedirectVal.(bool)
 		if !ok {
-			return opts, errors.New("token auth requires a valid option bool: autoredirect")
+			return opts, fmt.Errorf("token auth requires a valid option bool: autoredirect")
 		}
 		opts.autoRedirect = autoRedirect
 	}
-	if opts.autoRedirect {
-		autoRedirectPathVal, ok := options["autoredirectpath"]
-		if ok {
-			autoRedirectPath, ok := autoRedirectPathVal.(string)
-			if !ok {
-				return opts, errors.New("token auth requires a valid option string: autoredirectpath")
-			}
-			opts.autoRedirectPath = autoRedirectPath
-		}
-		if opts.autoRedirectPath == "" {
-			opts.autoRedirectPath = defaultAutoRedirectPath
-		}
-	}
-
-	signingAlgos, ok := options["signingalgorithms"]
-	if ok {
-		signingAlgorithmsVals, ok := signingAlgos.([]string)
-		if !ok {
-			return opts, errors.New("signingalgorithms must be a list of signing algorithms")
-		}
-		opts.signingAlgorithms = signingAlgorithmsVals
-	}
 
 	return opts, nil
 }
@@ -290,18 +243,6 @@ func getJwks(path string) (*jose.JSONWebKeySet, error) {
 	return &jwks, nil
 }
 
-func getSigningAlgorithms(algos []string) ([]jose.SignatureAlgorithm, error) {
-	signAlgVals := make([]jose.SignatureAlgorithm, 0, len(algos))
-	for _, alg := range algos {
-		alg, ok := signingAlgorithms[alg]
-		if !ok {
-			return nil, fmt.Errorf("unsupported signing algorithm: %s", alg)
-		}
-		signAlgVals = append(signAlgVals, alg)
-	}
-	return signAlgVals, nil
-}
-
 // newAccessController creates an accessController using the given options.
 func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
 	config, err := checkOptions(options)
@@ -312,7 +253,6 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
 	var (
 		rootCerts []*x509.Certificate
 		jwks      *jose.JSONWebKeySet
-		signAlgos []jose.SignatureAlgorithm
 	)
 
 	if config.rootCertBundle != "" {
@@ -346,25 +286,13 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
 		}
 	}
 
-	signAlgos, err = getSigningAlgorithms(config.signingAlgorithms)
-	if err != nil {
-		return nil, err
-	}
-	if len(signAlgos) == 0 {
-		// NOTE: this is to maintain backwards compat
-		// with existing registry deployments
-		signAlgos = defaultSigningAlgorithms
-	}
-
 	return &accessController{
-		realm:             config.realm,
-		autoRedirect:      config.autoRedirect,
-		autoRedirectPath:  config.autoRedirectPath,
-		issuer:            config.issuer,
-		service:           config.service,
-		rootCerts:         rootPool,
-		trustedKeys:       trustedKeys,
-		signingAlgorithms: signAlgos,
+		realm:        config.realm,
+		autoRedirect: config.autoRedirect,
+		issuer:       config.issuer,
+		service:      config.service,
+		rootCerts:    rootPool,
+		trustedKeys:  trustedKeys,
 	}, nil
 }
 
@@ -372,11 +300,10 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
 // for actions on resources described by the given access items.
 func (ac *accessController) Authorized(req *http.Request, accessItems ...auth.Access) (*auth.Grant, error) {
 	challenge := &authChallenge{
-		realm:            ac.realm,
-		autoRedirect:     ac.autoRedirect,
-		autoRedirectPath: ac.autoRedirectPath,
-		service:          ac.service,
-		accessSet:        newAccessSet(accessItems...),
+		realm:        ac.realm,
+		autoRedirect: ac.autoRedirect,
+		service:      ac.service,
+		accessSet:    newAccessSet(accessItems...),
 	}
 
 	prefix, rawToken, ok := strings.Cut(req.Header.Get("Authorization"), " ")
@@ -385,7 +312,7 @@ func (ac *accessController) Authorized(req *http.Request, accessItems ...auth.Ac
 		return nil, challenge
 	}
 
-	token, err := NewToken(rawToken, ac.signingAlgorithms)
+	token, err := NewToken(rawToken)
 	if err != nil {
 		challenge.err = err
 		return nil, challenge
diff --git a/registry/auth/token/token.go b/registry/auth/token/token.go
index 5a081069..83fe58c7 100644
--- a/registry/auth/token/token.go
+++ b/registry/auth/token/token.go
@@ -7,8 +7,8 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/go-jose/go-jose/v4"
-	"github.com/go-jose/go-jose/v4/jwt"
+	"github.com/go-jose/go-jose/v3"
+	"github.com/go-jose/go-jose/v3/jwt"
 	log "github.com/sirupsen/logrus"
 
 	"github.com/distribution/distribution/v3/registry/auth"
@@ -23,38 +23,6 @@ const (
 	Leeway = 60 * time.Second
 )
 
-var signingAlgorithms = map[string]jose.SignatureAlgorithm{
-	"EdDSA": jose.EdDSA,
-	"HS256": jose.HS256,
-	"HS384": jose.HS384,
-	"HS512": jose.HS512,
-	"RS256": jose.RS256,
-	"RS384": jose.RS384,
-	"RS512": jose.RS512,
-	"ES256": jose.ES256,
-	"ES384": jose.ES384,
-	"ES512": jose.ES512,
-	"PS256": jose.PS256,
-	"PS384": jose.PS384,
-	"PS512": jose.PS512,
-}
-
-var defaultSigningAlgorithms = []jose.SignatureAlgorithm{
-	jose.EdDSA,
-	jose.HS256,
-	jose.HS384,
-	jose.HS512,
-	jose.RS256,
-	jose.RS384,
-	jose.RS512,
-	jose.ES256,
-	jose.ES384,
-	jose.ES512,
-	jose.PS256,
-	jose.PS384,
-	jose.PS512,
-}
-
 // Errors used by token parsing and verification.
 var (
 	ErrMalformedToken = errors.New("malformed token")
@@ -101,8 +69,8 @@ type VerifyOptions struct {
 
 // NewToken parses the given raw token string
 // and constructs an unverified JSON Web Token.
-func NewToken(rawToken string, signingAlgs []jose.SignatureAlgorithm) (*Token, error) {
-	token, err := jwt.ParseSigned(rawToken, signingAlgs)
+func NewToken(rawToken string) (*Token, error) {
+	token, err := jwt.ParseSigned(rawToken)
 	if err != nil {
 		return nil, ErrMalformedToken
 	}
@@ -172,13 +140,6 @@ func (t *Token) VerifySigningKey(verifyOpts VerifyOptions) (signingKey crypto.Pu
 	// verifying the first one in the list only at the moment.
 	header := t.JWT.Headers[0]
 
-	signingKey, err = verifyCertChain(header, verifyOpts.Roots)
-	// NOTE(milosgajdos): if the x5c header is missing
-	// the token may have been signed by a JWKS.
-	if err != nil && err != jose.ErrMissingX5cHeader {
-		return
-	}
-
 	switch {
 	case header.JSONWebKey != nil:
 		signingKey, err = verifyJWK(header, verifyOpts)
@@ -188,7 +149,7 @@ func (t *Token) VerifySigningKey(verifyOpts VerifyOptions) (signingKey crypto.Pu
 			err = fmt.Errorf("token signed by untrusted key with ID: %q", header.KeyID)
 		}
 	default:
-		err = ErrInvalidToken
+		signingKey, err = verifyCertChain(header, verifyOpts.Roots)
 	}
 
 	return
@@ -265,7 +226,7 @@ func getCertPubKey(chains [][]*x509.Certificate) crypto.PublicKey {
 
 	// NOTE: we dont have to verify that the public key in the leaf cert
 	// *is* the signing key: if it's not the signing then token claims
-	// verification with this key fails
+	// verifcation with this key fails
 	return cert.PublicKey.(crypto.PublicKey)
 }
 
diff --git a/registry/handlers/app.go b/registry/handlers/app.go
index 946c415b..02a8847e 100644
--- a/registry/handlers/app.go
+++ b/registry/handlers/app.go
@@ -3,6 +3,8 @@ package handlers
 import (
 	"context"
 	"crypto/rand"
+	"crypto/tls"
+	"crypto/x509"
 	"expvar"
 	"fmt"
 	"math"
@@ -77,7 +79,7 @@ type App struct {
 		source notifications.SourceRecord
 	}
 
-	redis *redis.Client
+	redis redis.UniversalClient
 
 	// isCache is true if this registry is configured as a pull through cache
 	isCache bool
@@ -155,11 +157,7 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
 		panic(err)
 	}
 
-	// Do not configure HTTP secret for a proxy registry as HTTP secret
-	// is only used for blob uploads and a proxy registry does not support blob uploads.
-	if !app.isCache {
-		app.configureSecret(config)
-	}
+	app.configureSecret(config)
 	app.configureEvents(config)
 	app.configureRedis(config)
 	app.configureLogHook(config)
@@ -188,21 +186,6 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
 		}
 	}
 
-	// configure tag lookup concurrency limit
-	if p := config.Storage.TagParameters(); p != nil {
-		l, ok := p["concurrencylimit"]
-		if ok {
-			limit, ok := l.(int)
-			if !ok {
-				panic("tag lookup concurrency limit config key must have a integer value")
-			}
-			if limit < 0 {
-				panic("tag lookup concurrency limit should be a non-negative integer value")
-			}
-			options = append(options, storage.TagLookupConcurrencyLimit(limit))
-		}
-	}
-
 	// configure redirects
 	var redirectDisabled bool
 	if redirectConfig, ok := config.Storage["redirect"]; ok {
@@ -255,21 +238,6 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App {
 				options = append(options, storage.ManifestURLsDenyRegexp(re))
 			}
 		}
-
-		switch config.Validation.Manifests.Indexes.Platforms {
-		case "list":
-			options = append(options, storage.EnableValidateImageIndexImagesExist)
-			for _, platform := range config.Validation.Manifests.Indexes.PlatformList {
-				options = append(options, storage.AddValidateImageIndexImagesExistPlatform(platform.Architecture, platform.OS))
-			}
-			fallthrough
-		case "none":
-			dcontext.GetLogger(app).Warn("Image index completeness validation has been disabled, which is an experimental option because other container tooling might expect all image indexes to be complete")
-		case "all":
-			fallthrough
-		default:
-			options = append(options, storage.EnableValidateImageIndexImagesExist)
-		}
 	}
 
 	// configure storage caches
@@ -445,14 +413,6 @@ func (app *App) RegisterHealthChecks(healthRegistries ...*health.Registry) {
 	}
 }
 
-// Shutdown close the underlying registry
-func (app *App) Shutdown() error {
-	if r, ok := app.registry.(proxy.Closer); ok {
-		return r.Close()
-	}
-	return nil
-}
-
 // register a handler with the application, by route name. The handler will be
 // passed through the application filters and context will be constructed at
 // request time.
@@ -529,12 +489,41 @@ func (app *App) configureEvents(configuration *configuration.Configuration) {
 }
 
 func (app *App) configureRedis(cfg *configuration.Configuration) {
-	if cfg.Redis.Addr == "" {
+	if len(cfg.Redis.Options.Addrs) == 0 {
 		dcontext.GetLogger(app).Infof("redis not configured")
 		return
 	}
 
-	app.redis = app.createPool(cfg.Redis)
+	// redis TLS config
+	if cfg.Redis.TLS.Certificate != "" || cfg.Redis.TLS.Key != "" {
+		var err error
+		tlsConf := &tls.Config{}
+		tlsConf.Certificates = make([]tls.Certificate, 1)
+		tlsConf.Certificates[0], err = tls.LoadX509KeyPair(cfg.Redis.TLS.Certificate, cfg.Redis.TLS.Key)
+		if err != nil {
+			panic(err)
+		}
+		if len(cfg.Redis.TLS.ClientCAs) != 0 {
+			pool := x509.NewCertPool()
+			for _, ca := range cfg.Redis.TLS.ClientCAs {
+				caPem, err := os.ReadFile(ca)
+				if err != nil {
+					dcontext.GetLogger(app).Errorf("failed reading redis client CA: %v", err)
+					return
+				}
+
+				if ok := pool.AppendCertsFromPEM(caPem); !ok {
+					dcontext.GetLogger(app).Error("could not add CA to pool")
+					return
+				}
+			}
+			tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
+			tlsConf.ClientCAs = pool
+		}
+		cfg.Redis.Options.TLSConfig = tlsConf
+	}
+
+	app.redis = app.createPool(cfg.Redis.Options)
 
 	// Enable metrics instrumentation.
 	if err := redisotel.InstrumentMetrics(app.redis); err != nil {
@@ -556,25 +545,12 @@ func (app *App) configureRedis(cfg *configuration.Configuration) {
 	}))
 }
 
-func (app *App) createPool(cfg configuration.Redis) *redis.Client {
-	return redis.NewClient(&redis.Options{
-		Addr: cfg.Addr,
-		OnConnect: func(ctx context.Context, cn *redis.Conn) error {
-			res := cn.Ping(ctx)
-			return res.Err()
-		},
-		Username:        cfg.Username,
-		Password:        cfg.Password,
-		DB:              cfg.DB,
-		MaxRetries:      3,
-		DialTimeout:     cfg.DialTimeout,
-		ReadTimeout:     cfg.ReadTimeout,
-		WriteTimeout:    cfg.WriteTimeout,
-		PoolFIFO:        false,
-		MaxIdleConns:    cfg.Pool.MaxIdle,
-		PoolSize:        cfg.Pool.MaxActive,
-		ConnMaxIdleTime: cfg.Pool.IdleTimeout,
-	})
+func (app *App) createPool(cfg redis.UniversalOptions) redis.UniversalClient {
+	cfg.OnConnect = func(ctx context.Context, cn *redis.Conn) error {
+		res := cn.Ping(ctx)
+		return res.Err()
+	}
+	return redis.NewUniversalClient(&cfg)
 }
 
 // configureLogHook prepares logging hook parameters.
diff --git a/registry/handlers/manifests.go b/registry/handlers/manifests.go
index 144c0448..704a8ab7 100644
--- a/registry/handlers/manifests.go
+++ b/registry/handlers/manifests.go
@@ -6,7 +6,6 @@ import (
 	"mime"
 	"net/http"
 	"strings"
-	"sync"
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/internal/dcontext"
@@ -14,13 +13,11 @@ import (
 	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/manifest/schema2"
 	"github.com/distribution/distribution/v3/registry/api/errcode"
-	"github.com/distribution/distribution/v3/registry/storage"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
 	"github.com/distribution/reference"
 	"github.com/gorilla/handlers"
 	"github.com/opencontainers/go-digest"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
-	"golang.org/x/sync/errgroup"
 )
 
 const (
@@ -484,26 +481,12 @@ func (imh *manifestHandler) DeleteManifest(w http.ResponseWriter, r *http.Reques
 		return
 	}
 
-	var (
-		errs []error
-		mu   sync.Mutex
-	)
-	g := errgroup.Group{}
-	g.SetLimit(storage.DefaultConcurrencyLimit)
 	for _, tag := range referencedTags {
-		tag := tag
-
-		g.Go(func() error {
-			if err := tagService.Untag(imh, tag); err != nil {
-				mu.Lock()
-				errs = append(errs, err)
-				mu.Unlock()
-			}
-			return nil
-		})
+		if err := tagService.Untag(imh, tag); err != nil {
+			imh.Errors = append(imh.Errors, err)
+			return
+		}
 	}
-	_ = g.Wait() // imh will record all errors, so ignore the error of Wait()
-	imh.Errors = errs
 
 	w.WriteHeader(http.StatusAccepted)
 }
diff --git a/registry/proxy/proxyauth.go b/registry/proxy/proxyauth.go
index adf60cb9..8cdc3ebf 100644
--- a/registry/proxy/proxyauth.go
+++ b/registry/proxy/proxyauth.go
@@ -17,23 +17,14 @@ type userpass struct {
 	password string
 }
 
-func (u userpass) Basic(_ *url.URL) (string, string) {
-	return u.username, u.password
-}
-
-func (u userpass) RefreshToken(_ *url.URL, service string) string {
-	return ""
-}
-
-func (u userpass) SetRefreshToken(_ *url.URL, service, token string) {
-}
-
 type credentials struct {
 	creds map[string]userpass
 }
 
 func (c credentials) Basic(u *url.URL) (string, string) {
-	return c.creds[u.String()].Basic(u)
+	up := c.creds[u.String()]
+
+	return up.username, up.password
 }
 
 func (c credentials) RefreshToken(u *url.URL, service string) string {
@@ -44,12 +35,12 @@ func (c credentials) SetRefreshToken(u *url.URL, service, token string) {
 }
 
 // configureAuth stores credentials for challenge responses
-func configureAuth(username, password, remoteURL string) (auth.CredentialStore, auth.CredentialStore, error) {
+func configureAuth(username, password, remoteURL string) (auth.CredentialStore, error) {
 	creds := map[string]userpass{}
 
 	authURLs, err := getAuthURLs(remoteURL)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 
 	for _, url := range authURLs {
@@ -60,7 +51,7 @@ func configureAuth(username, password, remoteURL string) (auth.CredentialStore,
 		}
 	}
 
-	return credentials{creds: creds}, userpass{username: username, password: password}, nil
+	return credentials{creds: creds}, nil
 }
 
 func getAuthURLs(remoteURL string) ([]string, error) {
diff --git a/registry/proxy/proxyblobstore.go b/registry/proxy/proxyblobstore.go
index 7dc3a1c4..0a082888 100644
--- a/registry/proxy/proxyblobstore.go
+++ b/registry/proxy/proxyblobstore.go
@@ -33,20 +33,22 @@ var inflight = make(map[digest.Digest]struct{})
 // mu protects inflight
 var mu sync.Mutex
 
-func setResponseHeaders(h http.Header, length int64, mediaType string, digest digest.Digest) {
-	h.Set("Content-Length", strconv.FormatInt(length, 10))
-	h.Set("Content-Type", mediaType)
-	h.Set("Docker-Content-Digest", digest.String())
-	h.Set("Etag", digest.String())
+func setResponseHeaders(w http.ResponseWriter, length int64, mediaType string, digest digest.Digest) {
+	w.Header().Set("Content-Length", strconv.FormatInt(length, 10))
+	w.Header().Set("Content-Type", mediaType)
+	w.Header().Set("Docker-Content-Digest", digest.String())
+	w.Header().Set("Etag", digest.String())
 }
 
-func (pbs *proxyBlobStore) copyContent(ctx context.Context, dgst digest.Digest, writer io.Writer, h http.Header) (distribution.Descriptor, error) {
+func (pbs *proxyBlobStore) copyContent(ctx context.Context, dgst digest.Digest, writer io.Writer) (distribution.Descriptor, error) {
 	desc, err := pbs.remoteStore.Stat(ctx, dgst)
 	if err != nil {
 		return distribution.Descriptor{}, err
 	}
 
-	setResponseHeaders(h, desc.Size, desc.MediaType, dgst)
+	if w, ok := writer.(http.ResponseWriter); ok {
+		setResponseHeaders(w, desc.Size, desc.MediaType, dgst)
+	}
 
 	remoteReader, err := pbs.remoteStore.Open(ctx, dgst)
 	if err != nil {
@@ -100,7 +102,7 @@ func (pbs *proxyBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter,
 		// Will return the blob from the remote store directly.
 		// TODO Maybe we could reuse the these blobs are serving remotely and caching locally.
 		mu.Unlock()
-		_, err := pbs.copyContent(ctx, dgst, w, w.Header())
+		_, err := pbs.copyContent(ctx, dgst, w)
 		return err
 	}
 	inflight[dgst] = struct{}{}
@@ -120,7 +122,7 @@ func (pbs *proxyBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter,
 	// Serving client and storing locally over same fetching request.
 	// This can prevent a redundant blob fetching.
 	multiWriter := io.MultiWriter(w, bw)
-	desc, err := pbs.copyContent(ctx, dgst, multiWriter, w.Header())
+	desc, err := pbs.copyContent(ctx, dgst, multiWriter)
 	if err != nil {
 		return err
 	}
diff --git a/registry/proxy/proxyregistry.go b/registry/proxy/proxyregistry.go
index e8bbe6bd..33dcc4af 100644
--- a/registry/proxy/proxyregistry.go
+++ b/registry/proxy/proxyregistry.go
@@ -8,8 +8,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/distribution/reference"
-
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/configuration"
 	"github.com/distribution/distribution/v3/internal/client"
@@ -20,6 +18,7 @@ import (
 	"github.com/distribution/distribution/v3/registry/proxy/scheduler"
 	"github.com/distribution/distribution/v3/registry/storage"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
+	"github.com/distribution/reference"
 )
 
 var repositoryTTL = 24 * 7 * time.Hour
@@ -31,7 +30,6 @@ type proxyingRegistry struct {
 	ttl            *time.Duration
 	remoteURL      url.URL
 	authChallenger authChallenger
-	basicAuth      auth.CredentialStore
 }
 
 // NewRegistryPullThroughCache creates a registry acting as a pull through cache
@@ -114,7 +112,7 @@ func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Name
 		}
 	}
 
-	cs, b, err := configureAuth(config.Username, config.Password, config.RemoteURL)
+	cs, err := configureAuth(config.Username, config.Password, config.RemoteURL)
 	if err != nil {
 		return nil, err
 	}
@@ -129,7 +127,6 @@ func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Name
 			cm:        challenge.NewSimpleManager(),
 			cs:        cs,
 		},
-		basicAuth: b,
 	}, nil
 }
 
@@ -158,8 +155,7 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named
 
 	tr := transport.NewTransport(http.DefaultTransport,
 		auth.NewAuthorizer(c.challengeManager(),
-			auth.NewTokenHandlerWithOptions(tkopts),
-			auth.NewBasicHandler(pr.basicAuth)))
+			auth.NewTokenHandlerWithOptions(tkopts)))
 
 	localRepo, err := pr.embedded.Repository(ctx, name)
 	if err != nil {
@@ -215,15 +211,6 @@ func (pr *proxyingRegistry) BlobStatter() distribution.BlobStatter {
 	return pr.embedded.BlobStatter()
 }
 
-type Closer interface {
-	// Close release all resources used by this object
-	Close() error
-}
-
-func (pr *proxyingRegistry) Close() error {
-	return pr.scheduler.Stop()
-}
-
 // authChallenger encapsulates a request to the upstream to establish credential challenges
 type authChallenger interface {
 	tryEstablishChallenges(context.Context) error
diff --git a/registry/proxy/scheduler/scheduler.go b/registry/proxy/scheduler/scheduler.go
index 78366c2f..ed1d9d41 100644
--- a/registry/proxy/scheduler/scheduler.go
+++ b/registry/proxy/scheduler/scheduler.go
@@ -206,13 +206,12 @@ func (ttles *TTLExpirationScheduler) startTimer(entry *schedulerEntry, ttl time.
 }
 
 // Stop stops the scheduler.
-func (ttles *TTLExpirationScheduler) Stop() error {
+func (ttles *TTLExpirationScheduler) Stop() {
 	ttles.Lock()
 	defer ttles.Unlock()
 
-	err := ttles.writeState()
-	if err != nil {
-		err = fmt.Errorf("error writing scheduler state: %w", err)
+	if err := ttles.writeState(); err != nil {
+		dcontext.GetLogger(ttles.ctx).Errorf("Error writing scheduler state: %s", err)
 	}
 
 	for _, entry := range ttles.entries {
@@ -222,7 +221,6 @@ func (ttles *TTLExpirationScheduler) Stop() error {
 	close(ttles.doneChan)
 	ttles.saveTimer.Stop()
 	ttles.stopped = true
-	return err
 }
 
 func (ttles *TTLExpirationScheduler) writeState() error {
diff --git a/registry/registry.go b/registry/registry.go
index 3d3bf1eb..96f68b12 100644
--- a/registry/registry.go
+++ b/registry/registry.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"crypto/tls"
 	"crypto/x509"
-	"errors"
 	"fmt"
 	"net/http"
 	"os"
@@ -82,6 +81,9 @@ var tlsVersions = map[string]uint16{
 // defaultLogFormatter is the default formatter to use for logs.
 const defaultLogFormatter = "text"
 
+// this channel gets notified when process receives signal. It is global to ease unit testing
+var quit = make(chan os.Signal, 1)
+
 // HandlerFunc defines an http middleware
 type HandlerFunc func(config *configuration.Configuration, handler http.Handler) http.Handler
 
@@ -128,7 +130,6 @@ type Registry struct {
 	config *configuration.Configuration
 	app    *handlers.App
 	server *http.Server
-	quit   chan os.Signal
 }
 
 // NewRegistry creates a new registry from a context and configuration struct.
@@ -172,7 +173,6 @@ func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Reg
 		app:    app,
 		config: config,
 		server: server,
-		quit:   make(chan os.Signal, 1),
 	}, nil
 }
 
@@ -313,7 +313,7 @@ func (registry *Registry) ListenAndServe() error {
 	}
 
 	// setup channel to get notified on SIGTERM signal
-	signal.Notify(registry.quit, os.Interrupt, syscall.SIGTERM)
+	signal.Notify(quit, syscall.SIGTERM)
 	serveErr := make(chan error)
 
 	// Start serving in goroutine and listen for stop signal in main thread
@@ -324,22 +324,13 @@ func (registry *Registry) ListenAndServe() error {
 	select {
 	case err := <-serveErr:
 		return err
-	case <-registry.quit:
+	case <-quit:
 		dcontext.GetLogger(registry.app).Info("stopping server gracefully. Draining connections for ", config.HTTP.DrainTimeout)
 		// shutdown the server with a grace period of configured timeout
 		c, cancel := context.WithTimeout(context.Background(), config.HTTP.DrainTimeout)
 		defer cancel()
-		return registry.Shutdown(c)
-	}
-}
-
-// Shutdown gracefully shuts down the registry's HTTP server and application object.
-func (registry *Registry) Shutdown(ctx context.Context) error {
-	err := registry.server.Shutdown(ctx)
-	if appErr := registry.app.Shutdown(); appErr != nil {
-		err = errors.Join(err, appErr)
+		return registry.server.Shutdown(c)
 	}
-	return err
 }
 
 func configureDebugServer(config *configuration.Configuration) {
diff --git a/registry/storage/cache/redis/redis.go b/registry/storage/cache/redis/redis.go
index d2596b7b..baae8e1b 100644
--- a/registry/storage/cache/redis/redis.go
+++ b/registry/storage/cache/redis/redis.go
@@ -25,7 +25,7 @@ import (
 // Note that there is no implied relationship between these two caches. The
 // layer may exist in one, both or none and the code must be written this way.
 type redisBlobDescriptorService struct {
-	pool *redis.Client
+	pool redis.UniversalClient
 
 	// TODO(stevvooe): We use a pool because we don't have great control over
 	// the cache lifecycle to manage connections. A new connection if fetched
@@ -37,7 +37,7 @@ var _ distribution.BlobDescriptorService = &redisBlobDescriptorService{}
 
 // NewRedisBlobDescriptorCacheProvider returns a new redis-based
 // BlobDescriptorCacheProvider using the provided redis connection pool.
-func NewRedisBlobDescriptorCacheProvider(pool *redis.Client) cache.BlobDescriptorCacheProvider {
+func NewRedisBlobDescriptorCacheProvider(pool redis.UniversalClient) cache.BlobDescriptorCacheProvider {
 	return metrics.NewPrometheusCacheProvider(
 		&redisBlobDescriptorService{
 			pool: pool,
diff --git a/registry/storage/driver/s3-aws/s3.go b/registry/storage/driver/s3-aws/s3.go
index 71b7b793..40d30266 100644
--- a/registry/storage/driver/s3-aws/s3.go
+++ b/registry/storage/driver/s3-aws/s3.go
@@ -205,7 +205,7 @@ func FromParameters(ctx context.Context, parameters map[string]interface{}) (*Dr
 		regionEndpoint = ""
 	}
 
-	forcePathStyleBool := false
+	forcePathStyleBool := true
 	forcePathStyle := parameters["forcepathstyle"]
 	switch forcePathStyle := forcePathStyle.(type) {
 	case string:
@@ -529,9 +529,9 @@ func New(ctx context.Context, params DriverParameters) (*Driver, error) {
 
 	if params.RegionEndpoint != "" {
 		awsConfig.WithEndpoint(params.RegionEndpoint)
+		awsConfig.WithS3ForcePathStyle(params.ForcePathStyle)
 	}
 
-	awsConfig.WithS3ForcePathStyle(params.ForcePathStyle)
 	awsConfig.WithS3UseAccelerate(params.Accelerate)
 	awsConfig.WithRegion(params.Region)
 	awsConfig.WithDisableSSL(!params.Secure)
diff --git a/registry/storage/garbagecollect.go b/registry/storage/garbagecollect.go
index 23229e3d..7895c9b5 100644
--- a/registry/storage/garbagecollect.go
+++ b/registry/storage/garbagecollect.go
@@ -68,15 +68,12 @@ func MarkAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
 					return fmt.Errorf("failed to retrieve tags for digest %v: %v", dgst, err)
 				}
 				if len(tags) == 0 {
+					emit("manifest eligible for deletion: %s", dgst)
 					// fetch all tags from repository
 					// all of these tags could contain manifest in history
 					// which means that we need check (and delete) those references when deleting manifest
 					allTags, err := repository.Tags(ctx).All(ctx)
 					if err != nil {
-						if _, ok := err.(distribution.ErrRepositoryUnknown); ok {
-							emit("manifest tags path of repository %s does not exist", repoName)
-							return nil
-						}
 						return fmt.Errorf("failed to retrieve tags %v", err)
 					}
 					manifestArr = append(manifestArr, ManifestDel{Name: repoName, Digest: dgst, Tags: allTags})
@@ -87,14 +84,18 @@ func MarkAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
 			emit("%s: marking manifest %s ", repoName, dgst)
 			markSet[dgst] = struct{}{}
 
-			return markManifestReferences(dgst, manifestService, ctx, func(d digest.Digest) bool {
-				_, marked := markSet[d]
-				if !marked {
-					markSet[d] = struct{}{}
-					emit("%s: marking blob %s", repoName, d)
-				}
-				return marked
-			})
+			manifest, err := manifestService.Get(ctx, dgst)
+			if err != nil {
+				return fmt.Errorf("failed to retrieve manifest for digest %v: %v", dgst, err)
+			}
+
+			descriptors := manifest.References()
+			for _, descriptor := range descriptors {
+				markSet[descriptor.Digest] = struct{}{}
+				emit("%s: marking blob %s", repoName, descriptor.Digest)
+			}
+
+			return nil
 		})
 
 		// In certain situations such as unfinished uploads, deleting all
@@ -112,8 +113,6 @@ func MarkAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
 		return fmt.Errorf("failed to mark: %v", err)
 	}
 
-	manifestArr = unmarkReferencedManifest(manifestArr, markSet)
-
 	// sweep
 	vacuum := NewVacuum(ctx, storageDriver)
 	if !opts.DryRun {
@@ -150,40 +149,3 @@ func MarkAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
 
 	return err
 }
-
-// unmarkReferencedManifest filters out manifest present in markSet
-func unmarkReferencedManifest(manifestArr []ManifestDel, markSet map[digest.Digest]struct{}) []ManifestDel {
-	filtered := make([]ManifestDel, 0)
-	for _, obj := range manifestArr {
-		if _, ok := markSet[obj.Digest]; !ok {
-			emit("manifest eligible for deletion: %s", obj)
-			filtered = append(filtered, obj)
-		}
-	}
-	return filtered
-}
-
-// markManifestReferences marks the manifest references
-func markManifestReferences(dgst digest.Digest, manifestService distribution.ManifestService, ctx context.Context, ingester func(digest.Digest) bool) error {
-	manifest, err := manifestService.Get(ctx, dgst)
-	if err != nil {
-		return fmt.Errorf("failed to retrieve manifest for digest %v: %v", dgst, err)
-	}
-
-	descriptors := manifest.References()
-	for _, descriptor := range descriptors {
-
-		// do not visit references if already marked
-		if ingester(descriptor.Digest) {
-			continue
-		}
-
-		if ok, _ := manifestService.Exists(ctx, descriptor.Digest); ok {
-			err := markManifestReferences(descriptor.Digest, manifestService, ctx, ingester)
-			if err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
diff --git a/registry/storage/manifestlisthandler.go b/registry/storage/manifestlisthandler.go
index a1da4632..caaf76bb 100644
--- a/registry/storage/manifestlisthandler.go
+++ b/registry/storage/manifestlisthandler.go
@@ -13,10 +13,9 @@ import (
 
 // manifestListHandler is a ManifestHandler that covers schema2 manifest lists.
 type manifestListHandler struct {
-	repository           distribution.Repository
-	blobStore            distribution.BlobStore
-	ctx                  context.Context
-	validateImageIndexes validateImageIndexes
+	repository distribution.Repository
+	blobStore  distribution.BlobStore
+	ctx        context.Context
 }
 
 var _ ManifestHandler = &manifestListHandler{}
@@ -75,24 +74,24 @@ func (ms *manifestListHandler) Put(ctx context.Context, manifestList distributio
 func (ms *manifestListHandler) verifyManifest(ctx context.Context, mnfst distribution.Manifest, skipDependencyVerification bool) error {
 	var errs distribution.ErrManifestVerification
 
-	// Check if we should be validating the existence of any child images in images indexes
-	if ms.validateImageIndexes.imagesExist && !skipDependencyVerification {
-		// Get the manifest service we can use to check for the existence of child images
+	if !skipDependencyVerification {
+		// This manifest service is different from the blob service
+		// returned by Blob. It uses a linked blob store to ensure that
+		// only manifests are accessible.
+
 		manifestService, err := ms.repository.Manifests(ctx)
 		if err != nil {
 			return err
 		}
 
 		for _, manifestDescriptor := range mnfst.References() {
-			if ms.platformMustExist(manifestDescriptor) {
-				exists, err := manifestService.Exists(ctx, manifestDescriptor.Digest)
-				if err != nil && err != distribution.ErrBlobUnknown {
-					errs = append(errs, err)
-				}
-				if err != nil || !exists {
-					// On error here, we always append unknown blob errors.
-					errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: manifestDescriptor.Digest})
-				}
+			exists, err := manifestService.Exists(ctx, manifestDescriptor.Digest)
+			if err != nil && err != distribution.ErrBlobUnknown {
+				errs = append(errs, err)
+			}
+			if err != nil || !exists {
+				// On error here, we always append unknown blob errors.
+				errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: manifestDescriptor.Digest})
 			}
 		}
 	}
@@ -102,24 +101,3 @@ func (ms *manifestListHandler) verifyManifest(ctx context.Context, mnfst distrib
 
 	return nil
 }
-
-// platformMustExist checks if a descriptor within an index should be validated as existing before accepting the manifest into the registry.
-func (ms *manifestListHandler) platformMustExist(descriptor distribution.Descriptor) bool {
-	// If there are no image platforms configured to validate, we must check the existence of all child images.
-	if len(ms.validateImageIndexes.imagePlatforms) == 0 {
-		return true
-	}
-
-	imagePlatform := descriptor.Platform
-
-	// If the platform matches a platform that is configured to validate, we must check the existence.
-	for _, platform := range ms.validateImageIndexes.imagePlatforms {
-		if imagePlatform.Architecture == platform.architecture &&
-			imagePlatform.OS == platform.os {
-			return true
-		}
-	}
-
-	// If the platform doesn't match a platform configured to validate, we don't need to check the existence.
-	return false
-}
diff --git a/registry/storage/purgeuploads.go b/registry/storage/purgeuploads.go
index 9e9407a1..e1a876e7 100644
--- a/registry/storage/purgeuploads.go
+++ b/registry/storage/purgeuploads.go
@@ -93,7 +93,7 @@ func getOutstandingUploads(ctx context.Context, driver storageDriver.StorageDriv
 			ud.containingDir = filePath
 		}
 		if file == "startedat" {
-			if t, err := readStartedAtFile(ctx, driver, filePath); err == nil {
+			if t, err := readStartedAtFile(driver, filePath); err == nil {
 				ud.startedAt = t
 			} else {
 				errors = pushError(errors, filePath, err)
@@ -124,8 +124,9 @@ func uuidFromPath(path string) (string, bool) {
 }
 
 // readStartedAtFile reads the date from an upload's startedAtFile
-func readStartedAtFile(ctx context.Context, driver storageDriver.StorageDriver, path string) (time.Time, error) {
-	startedAtBytes, err := driver.GetContent(ctx, path)
+func readStartedAtFile(driver storageDriver.StorageDriver, path string) (time.Time, error) {
+	// todo:(richardscothern) - pass in a context
+	startedAtBytes, err := driver.GetContent(context.Background(), path)
 	if err != nil {
 		return time.Now(), err
 	}
diff --git a/registry/storage/registry.go b/registry/storage/registry.go
index ab8fb1f9..ecf483bf 100644
--- a/registry/storage/registry.go
+++ b/registry/storage/registry.go
@@ -3,7 +3,6 @@ package storage
 import (
 	"context"
 	"regexp"
-	"runtime"
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/registry/storage/cache"
@@ -11,10 +10,6 @@ import (
 	"github.com/distribution/reference"
 )
 
-var (
-	DefaultConcurrencyLimit = runtime.GOMAXPROCS(0)
-)
-
 // registry is the top-level implementation of Registry for use in the storage
 // package. All instances should descend from this object.
 type registry struct {
@@ -23,14 +18,10 @@ type registry struct {
 	statter                      *blobStatter // global statter service.
 	blobDescriptorCacheProvider  cache.BlobDescriptorCacheProvider
 	deleteEnabled                bool
-	tagLookupConcurrencyLimit    int
 	resumableDigestEnabled       bool
 	blobDescriptorServiceFactory distribution.BlobDescriptorServiceFactory
+	manifestURLs                 manifestURLs
 	driver                       storagedriver.StorageDriver
-
-	// Validation
-	manifestURLs         manifestURLs
-	validateImageIndexes validateImageIndexes
 }
 
 // manifestURLs holds regular expressions for controlling manifest URL whitelisting
@@ -39,20 +30,6 @@ type manifestURLs struct {
 	deny  *regexp.Regexp
 }
 
-// validateImageIndexImages holds configuration for validation of image indexes
-type validateImageIndexes struct {
-	// exist can be used to disable checking that platform images exist entirely. Default true.
-	imagesExist bool
-	// platforms can be used to only validate the existence of images for a set of platforms. The empty array means validate all platforms.
-	imagePlatforms []platform
-}
-
-// platform represents a platform to validate exists in the
-type platform struct {
-	architecture string
-	os           string
-}
-
 // RegistryOption is the type used for functional options for NewRegistry.
 type RegistryOption func(*registry) error
 
@@ -63,13 +40,6 @@ func EnableRedirect(registry *registry) error {
 	return nil
 }
 
-func TagLookupConcurrencyLimit(concurrencyLimit int) RegistryOption {
-	return func(registry *registry) error {
-		registry.tagLookupConcurrencyLimit = concurrencyLimit
-		return nil
-	}
-}
-
 // EnableDelete is a functional option for NewRegistry. It enables deletion on
 // the registry.
 func EnableDelete(registry *registry) error {
@@ -100,28 +70,6 @@ func ManifestURLsDenyRegexp(r *regexp.Regexp) RegistryOption {
 	}
 }
 
-// EnableValidateImageIndexImagesExist is a functional option for NewRegistry. It enables
-// validation that references exist before an image index is accepted.
-func EnableValidateImageIndexImagesExist(registry *registry) error {
-	registry.validateImageIndexes.imagesExist = true
-	return nil
-}
-
-// AddValidateImageIndexImagesExistPlatform returns a functional option for NewRegistry.
-// It adds a platform to check for existence before an image index is accepted.
-func AddValidateImageIndexImagesExistPlatform(architecture string, os string) RegistryOption {
-	return func(registry *registry) error {
-		registry.validateImageIndexes.imagePlatforms = append(
-			registry.validateImageIndexes.imagePlatforms,
-			platform{
-				architecture: architecture,
-				os:           os,
-			},
-		)
-		return nil
-	}
-}
-
 // BlobDescriptorServiceFactory returns a functional option for NewRegistry. It sets the
 // factory to create BlobDescriptorServiceFactory middleware.
 func BlobDescriptorServiceFactory(factory distribution.BlobDescriptorServiceFactory) RegistryOption {
@@ -236,14 +184,9 @@ func (repo *repository) Named() reference.Named {
 }
 
 func (repo *repository) Tags(ctx context.Context) distribution.TagService {
-	limit := DefaultConcurrencyLimit
-	if repo.tagLookupConcurrencyLimit > 0 {
-		limit = repo.tagLookupConcurrencyLimit
-	}
 	tags := &tagStore{
-		repository:       repo,
-		blobStore:        repo.registry.blobStore,
-		concurrencyLimit: limit,
+		repository: repo,
+		blobStore:  repo.registry.blobStore,
 	}
 
 	return tags
@@ -279,10 +222,9 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M
 	}
 
 	manifestListHandler := &manifestListHandler{
-		ctx:                  ctx,
-		repository:           repo,
-		blobStore:            blobStore,
-		validateImageIndexes: repo.validateImageIndexes,
+		ctx:        ctx,
+		repository: repo,
+		blobStore:  blobStore,
 	}
 
 	ms := &manifestStore{
diff --git a/registry/storage/tagstore.go b/registry/storage/tagstore.go
index a481df94..29dcf4e3 100644
--- a/registry/storage/tagstore.go
+++ b/registry/storage/tagstore.go
@@ -4,13 +4,10 @@ import (
 	"context"
 	"path"
 	"sort"
-	"sync"
-
-	"github.com/opencontainers/go-digest"
-	"golang.org/x/sync/errgroup"
 
 	"github.com/distribution/distribution/v3"
 	storagedriver "github.com/distribution/distribution/v3/registry/storage/driver"
+	"github.com/opencontainers/go-digest"
 )
 
 var _ distribution.TagService = &tagStore{}
@@ -21,9 +18,8 @@ var _ distribution.TagService = &tagStore{}
 // which only makes use of the Digest field of the returned distribution.Descriptor
 // but does not enable full roundtripping of Descriptor objects
 type tagStore struct {
-	repository       *repository
-	blobStore        *blobStore
-	concurrencyLimit int
+	repository *repository
+	blobStore  *blobStore
 }
 
 // All returns all tags
@@ -149,48 +145,26 @@ func (ts *tagStore) Lookup(ctx context.Context, desc distribution.Descriptor) ([
 		return nil, err
 	}
 
-	g, ctx := errgroup.WithContext(ctx)
-	g.SetLimit(ts.concurrencyLimit)
-
-	var (
-		tags []string
-		mu   sync.Mutex
-	)
+	var tags []string
 	for _, tag := range allTags {
-		if ctx.Err() != nil {
-			break
+		tagLinkPathSpec := manifestTagCurrentPathSpec{
+			name: ts.repository.Named().Name(),
+			tag:  tag,
 		}
-		tag := tag
-
-		g.Go(func() error {
-			tagLinkPathSpec := manifestTagCurrentPathSpec{
-				name: ts.repository.Named().Name(),
-				tag:  tag,
-			}
-
-			tagLinkPath, _ := pathFor(tagLinkPathSpec)
-			tagDigest, err := ts.blobStore.readlink(ctx, tagLinkPath)
-			if err != nil {
-				switch err.(type) {
-				case storagedriver.PathNotFoundError:
-					return nil
-				}
-				return err
-			}
 
-			if tagDigest == desc.Digest {
-				mu.Lock()
-				tags = append(tags, tag)
-				mu.Unlock()
+		tagLinkPath, _ := pathFor(tagLinkPathSpec)
+		tagDigest, err := ts.blobStore.readlink(ctx, tagLinkPath)
+		if err != nil {
+			switch err.(type) {
+			case storagedriver.PathNotFoundError:
+				continue
 			}
+			return nil, err
+		}
 
-			return nil
-		})
-	}
-
-	err = g.Wait()
-	if err != nil {
-		return nil, err
+		if tagDigest == desc.Digest {
+			tags = append(tags, tag)
+		}
 	}
 
 	return tags, nil
diff --git a/vendor/github.com/go-jose/go-jose/v4/.gitignore b/vendor/github.com/go-jose/go-jose/v3/.gitignore
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/.gitignore
rename to vendor/github.com/go-jose/go-jose/v3/.gitignore
diff --git a/vendor/github.com/go-jose/go-jose/v4/.golangci.yml b/vendor/github.com/go-jose/go-jose/v3/.golangci.yml
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/.golangci.yml
rename to vendor/github.com/go-jose/go-jose/v3/.golangci.yml
diff --git a/vendor/github.com/go-jose/go-jose/v4/.travis.yml b/vendor/github.com/go-jose/go-jose/v3/.travis.yml
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/.travis.yml
rename to vendor/github.com/go-jose/go-jose/v3/.travis.yml
diff --git a/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md b/vendor/github.com/go-jose/go-jose/v3/CHANGELOG.md
similarity index 96%
rename from vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md
rename to vendor/github.com/go-jose/go-jose/v3/CHANGELOG.md
index 28bdd2fc..ce2a54eb 100644
--- a/vendor/github.com/go-jose/go-jose/v4/CHANGELOG.md
+++ b/vendor/github.com/go-jose/go-jose/v3/CHANGELOG.md
@@ -45,6 +45,12 @@ token".
 
 [1]: https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf
 
+# v3.0.3
+
+## Fixed
+
+ - Limit decompression output size to prevent a DoS. Backport from v4.0.1.
+
 # v3.0.2
 
 ## Fixed
diff --git a/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md b/vendor/github.com/go-jose/go-jose/v3/CONTRIBUTING.md
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md
rename to vendor/github.com/go-jose/go-jose/v3/CONTRIBUTING.md
diff --git a/vendor/github.com/go-jose/go-jose/v4/LICENSE b/vendor/github.com/go-jose/go-jose/v3/LICENSE
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/LICENSE
rename to vendor/github.com/go-jose/go-jose/v3/LICENSE
diff --git a/vendor/github.com/go-jose/go-jose/v4/README.md b/vendor/github.com/go-jose/go-jose/v3/README.md
similarity index 82%
rename from vendor/github.com/go-jose/go-jose/v4/README.md
rename to vendor/github.com/go-jose/go-jose/v3/README.md
index 79a7c5ec..282cd9e1 100644
--- a/vendor/github.com/go-jose/go-jose/v4/README.md
+++ b/vendor/github.com/go-jose/go-jose/v3/README.md
@@ -1,9 +1,17 @@
 # Go JOSE
 
-[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
-[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
-[![license](https://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
-[![test](https://img.shields.io/github/checks-status/go-jose/go-jose/v4)](https://github.com/go-jose/go-jose/actions)
+### Versions
+
+[Version 4](https://github.com/go-jose/go-jose)
+([branch](https://github.com/go-jose/go-jose/),
+[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v4), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version:
+
+    import "github.com/go-jose/go-jose/v4"
+
+The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which
+are deprecated.
+
+### Summary
 
 Package jose aims to provide an implementation of the Javascript Object Signing
 and Encryption set of standards. This includes support for JSON Web Encryption,
@@ -35,20 +43,6 @@ of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/curren
 This is to avoid differences in interpretation of messages between go-jose and
 libraries in other languages.
 
-### Versions
-
-[Version 4](https://github.com/go-jose/go-jose)
-([branch](https://github.com/go-jose/go-jose/tree/main),
-[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v4), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version:
-
-    import "github.com/go-jose/go-jose/v4"
-
-The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which
-are still useable but not actively developed anymore.
-
-Version 3, in this repo, is still receiving security fixes but not functionality
-updates.
-
 ### Supported algorithms
 
 See below for a table of supported algorithms. Algorithm identifiers match
@@ -104,11 +98,11 @@ allows attaching a key id.
 
 ## Examples
 
-[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
-[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
+[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v3.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v3)
+[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v3/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v3/jwt)
 
 Examples can be found in the Godoc
 reference for this package. The
-[`jose-util`](https://github.com/go-jose/go-jose/tree/v4/jose-util)
+[`jose-util`](https://github.com/go-jose/go-jose/tree/v3/jose-util)
 subdirectory also contains a small command-line utility which might be useful
 as an example as well.
diff --git a/vendor/github.com/go-jose/go-jose/v4/SECURITY.md b/vendor/github.com/go-jose/go-jose/v3/SECURITY.md
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/SECURITY.md
rename to vendor/github.com/go-jose/go-jose/v3/SECURITY.md
diff --git a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go b/vendor/github.com/go-jose/go-jose/v3/asymmetric.go
similarity index 99%
rename from vendor/github.com/go-jose/go-jose/v4/asymmetric.go
rename to vendor/github.com/go-jose/go-jose/v3/asymmetric.go
index f8d5774e..d4d4961b 100644
--- a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go
+++ b/vendor/github.com/go-jose/go-jose/v3/asymmetric.go
@@ -29,8 +29,8 @@ import (
 	"fmt"
 	"math/big"
 
-	josecipher "github.com/go-jose/go-jose/v4/cipher"
-	"github.com/go-jose/go-jose/v4/json"
+	josecipher "github.com/go-jose/go-jose/v3/cipher"
+	"github.com/go-jose/go-jose/v3/json"
 )
 
 // A generic RSA-based encrypter/verifier
diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go b/vendor/github.com/go-jose/go-jose/v3/cipher/cbc_hmac.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go
rename to vendor/github.com/go-jose/go-jose/v3/cipher/cbc_hmac.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go b/vendor/github.com/go-jose/go-jose/v3/cipher/concat_kdf.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go
rename to vendor/github.com/go-jose/go-jose/v3/cipher/concat_kdf.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go b/vendor/github.com/go-jose/go-jose/v3/cipher/ecdh_es.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go
rename to vendor/github.com/go-jose/go-jose/v3/cipher/ecdh_es.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go b/vendor/github.com/go-jose/go-jose/v3/cipher/key_wrap.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go
rename to vendor/github.com/go-jose/go-jose/v3/cipher/key_wrap.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/crypter.go b/vendor/github.com/go-jose/go-jose/v3/crypter.go
similarity index 99%
rename from vendor/github.com/go-jose/go-jose/v4/crypter.go
rename to vendor/github.com/go-jose/go-jose/v3/crypter.go
index aba08424..8870e890 100644
--- a/vendor/github.com/go-jose/go-jose/v4/crypter.go
+++ b/vendor/github.com/go-jose/go-jose/v3/crypter.go
@@ -22,7 +22,7 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/go-jose/go-jose/v4/json"
+	"github.com/go-jose/go-jose/v3/json"
 )
 
 // Encrypter represents an encrypter which produces an encrypted JWE object.
diff --git a/vendor/github.com/go-jose/go-jose/v4/doc.go b/vendor/github.com/go-jose/go-jose/v3/doc.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/doc.go
rename to vendor/github.com/go-jose/go-jose/v3/doc.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/encoding.go b/vendor/github.com/go-jose/go-jose/v3/encoding.go
similarity index 92%
rename from vendor/github.com/go-jose/go-jose/v4/encoding.go
rename to vendor/github.com/go-jose/go-jose/v3/encoding.go
index 4f6e0d4a..9f07cfdc 100644
--- a/vendor/github.com/go-jose/go-jose/v4/encoding.go
+++ b/vendor/github.com/go-jose/go-jose/v3/encoding.go
@@ -27,7 +27,7 @@ import (
 	"strings"
 	"unicode"
 
-	"github.com/go-jose/go-jose/v4/json"
+	"github.com/go-jose/go-jose/v3/json"
 )
 
 // Helper function to serialize known-good objects.
@@ -106,7 +106,10 @@ func inflate(input []byte) ([]byte, error) {
 	output := new(bytes.Buffer)
 	reader := flate.NewReader(bytes.NewBuffer(input))
 
-	maxCompressedSize := max(250_000, 10*int64(len(input)))
+	maxCompressedSize := 10 * int64(len(input))
+	if maxCompressedSize < 250000 {
+		maxCompressedSize = 250000
+	}
 
 	limit := maxCompressedSize + 1
 	n, err := io.CopyN(output, reader, limit)
@@ -164,7 +167,7 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
 		return nil
 	}
 
-	decoded, err := base64.RawURLEncoding.DecodeString(encoded)
+	decoded, err := base64URLDecode(encoded)
 	if err != nil {
 		return err
 	}
@@ -194,6 +197,12 @@ func (b byteBuffer) toInt() int {
 	return int(b.bigInt().Int64())
 }
 
+// base64URLDecode is implemented as defined in https://www.rfc-editor.org/rfc/rfc7515.html#appendix-C
+func base64URLDecode(value string) ([]byte, error) {
+	value = strings.TrimRight(value, "=")
+	return base64.RawURLEncoding.DecodeString(value)
+}
+
 func base64EncodeLen(sl []byte) int {
 	return base64.RawURLEncoding.EncodedLen(len(sl))
 }
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/LICENSE b/vendor/github.com/go-jose/go-jose/v3/json/LICENSE
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/json/LICENSE
rename to vendor/github.com/go-jose/go-jose/v3/json/LICENSE
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/README.md b/vendor/github.com/go-jose/go-jose/v3/json/README.md
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/json/README.md
rename to vendor/github.com/go-jose/go-jose/v3/json/README.md
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/decode.go b/vendor/github.com/go-jose/go-jose/v3/json/decode.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/json/decode.go
rename to vendor/github.com/go-jose/go-jose/v3/json/decode.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/encode.go b/vendor/github.com/go-jose/go-jose/v3/json/encode.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/json/encode.go
rename to vendor/github.com/go-jose/go-jose/v3/json/encode.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/indent.go b/vendor/github.com/go-jose/go-jose/v3/json/indent.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/json/indent.go
rename to vendor/github.com/go-jose/go-jose/v3/json/indent.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/scanner.go b/vendor/github.com/go-jose/go-jose/v3/json/scanner.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/json/scanner.go
rename to vendor/github.com/go-jose/go-jose/v3/json/scanner.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/stream.go b/vendor/github.com/go-jose/go-jose/v3/json/stream.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/json/stream.go
rename to vendor/github.com/go-jose/go-jose/v3/json/stream.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/json/tags.go b/vendor/github.com/go-jose/go-jose/v3/json/tags.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/json/tags.go
rename to vendor/github.com/go-jose/go-jose/v3/json/tags.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/jwe.go b/vendor/github.com/go-jose/go-jose/v3/jwe.go
similarity index 63%
rename from vendor/github.com/go-jose/go-jose/v4/jwe.go
rename to vendor/github.com/go-jose/go-jose/v3/jwe.go
index 89f03ee3..4267ac75 100644
--- a/vendor/github.com/go-jose/go-jose/v4/jwe.go
+++ b/vendor/github.com/go-jose/go-jose/v3/jwe.go
@@ -18,11 +18,10 @@ package jose
 
 import (
 	"encoding/base64"
-	"errors"
 	"fmt"
 	"strings"
 
-	"github.com/go-jose/go-jose/v4/json"
+	"github.com/go-jose/go-jose/v3/json"
 )
 
 // rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
@@ -105,75 +104,29 @@ func (obj JSONWebEncryption) computeAuthData() []byte {
 	return output
 }
 
-func containsKeyAlgorithm(haystack []KeyAlgorithm, needle KeyAlgorithm) bool {
-	for _, algorithm := range haystack {
-		if algorithm == needle {
-			return true
-		}
-	}
-	return false
-}
-
-func containsContentEncryption(haystack []ContentEncryption, needle ContentEncryption) bool {
-	for _, algorithm := range haystack {
-		if algorithm == needle {
-			return true
-		}
-	}
-	return false
-}
-
-// ParseEncrypted parses an encrypted message in JWE Compact or JWE JSON Serialization.
-//
-// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
-// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
-//
-// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
-// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
-// parsed data must contain a value that is present in the corresponding parameter. That
-// includes the protected and unprotected headers as well as all recipients. To accept
-// multiple algorithms, pass a slice of all the algorithms you want to accept.
-func ParseEncrypted(input string,
-	keyEncryptionAlgorithms []KeyAlgorithm,
-	contentEncryption []ContentEncryption,
-) (*JSONWebEncryption, error) {
+// ParseEncrypted parses an encrypted message in compact or JWE JSON Serialization format.
+func ParseEncrypted(input string) (*JSONWebEncryption, error) {
 	input = stripWhitespace(input)
 	if strings.HasPrefix(input, "{") {
-		return ParseEncryptedJSON(input, keyEncryptionAlgorithms, contentEncryption)
+		return parseEncryptedFull(input)
 	}
 
-	return ParseEncryptedCompact(input, keyEncryptionAlgorithms, contentEncryption)
+	return parseEncryptedCompact(input)
 }
 
-// ParseEncryptedJSON parses a message in JWE JSON Serialization.
-//
-// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
-func ParseEncryptedJSON(
-	input string,
-	keyEncryptionAlgorithms []KeyAlgorithm,
-	contentEncryption []ContentEncryption,
-) (*JSONWebEncryption, error) {
+// parseEncryptedFull parses a message in compact format.
+func parseEncryptedFull(input string) (*JSONWebEncryption, error) {
 	var parsed rawJSONWebEncryption
 	err := json.Unmarshal([]byte(input), &parsed)
 	if err != nil {
 		return nil, err
 	}
 
-	return parsed.sanitized(keyEncryptionAlgorithms, contentEncryption)
+	return parsed.sanitized()
 }
 
 // sanitized produces a cleaned-up JWE object from the raw JSON.
-func (parsed *rawJSONWebEncryption) sanitized(
-	keyEncryptionAlgorithms []KeyAlgorithm,
-	contentEncryption []ContentEncryption,
-) (*JSONWebEncryption, error) {
-	if len(keyEncryptionAlgorithms) == 0 {
-		return nil, errors.New("go-jose/go-jose: no key algorithms provided")
-	}
-	if len(contentEncryption) == 0 {
-		return nil, errors.New("go-jose/go-jose: no content encryption algorithms provided")
-	}
-
+func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
 	obj := &JSONWebEncryption{
 		original:    parsed,
 		unprotected: parsed.Unprotected,
@@ -217,7 +170,7 @@ func (parsed *rawJSONWebEncryption) sanitized(
 	} else {
 		obj.recipients = make([]recipientInfo, len(parsed.Recipients))
 		for r := range parsed.Recipients {
-			encryptedKey, err := base64.RawURLEncoding.DecodeString(parsed.Recipients[r].EncryptedKey)
+			encryptedKey, err := base64URLDecode(parsed.Recipients[r].EncryptedKey)
 			if err != nil {
 				return nil, err
 			}
@@ -232,31 +185,10 @@ func (parsed *rawJSONWebEncryption) sanitized(
 		}
 	}
 
-	for i, recipient := range obj.recipients {
+	for _, recipient := range obj.recipients {
 		headers := obj.mergedHeaders(&recipient)
-		if headers.getAlgorithm() == "" {
-			return nil, fmt.Errorf(`go-jose/go-jose: recipient %d: missing header "alg"`, i)
-		}
-		if headers.getEncryption() == "" {
-			return nil, fmt.Errorf(`go-jose/go-jose: recipient %d: missing header "enc"`, i)
-		}
-		err := validateAlgEnc(headers, keyEncryptionAlgorithms, contentEncryption)
-		if err != nil {
-			return nil, fmt.Errorf("go-jose/go-jose: recipient %d: %s", i, err)
-		}
-
-	}
-
-	if obj.protected != nil {
-		err := validateAlgEnc(*obj.protected, keyEncryptionAlgorithms, contentEncryption)
-		if err != nil {
-			return nil, fmt.Errorf("go-jose/go-jose: protected header: %s", err)
-		}
-	}
-	if obj.unprotected != nil {
-		err := validateAlgEnc(*obj.unprotected, keyEncryptionAlgorithms, contentEncryption)
-		if err != nil {
-			return nil, fmt.Errorf("go-jose/go-jose: unprotected header: %s", err)
+		if headers.getAlgorithm() == "" || headers.getEncryption() == "" {
+			return nil, fmt.Errorf("go-jose/go-jose: message is missing alg/enc headers")
 		}
 	}
 
@@ -268,52 +200,34 @@ func (parsed *rawJSONWebEncryption) sanitized(
 	return obj, nil
 }
 
-func validateAlgEnc(headers rawHeader, keyAlgorithms []KeyAlgorithm, contentEncryption []ContentEncryption) error {
-	alg := headers.getAlgorithm()
-	enc := headers.getEncryption()
-	if alg != "" && !containsKeyAlgorithm(keyAlgorithms, alg) {
-		return fmt.Errorf("unexpected key algorithm %q; expected %q", alg, keyAlgorithms)
-	}
-	if alg != "" && !containsContentEncryption(contentEncryption, enc) {
-		return fmt.Errorf("unexpected content encryption algorithm %q; expected %q", enc, contentEncryption)
-	}
-	return nil
-}
-
-// ParseEncryptedCompact parses a message in JWE Compact Serialization.
-//
-// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
-func ParseEncryptedCompact(
-	input string,
-	keyAlgorithms []KeyAlgorithm,
-	contentEncryption []ContentEncryption,
-) (*JSONWebEncryption, error) {
+// parseEncryptedCompact parses a message in compact format.
+func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
 	parts := strings.Split(input, ".")
 	if len(parts) != 5 {
 		return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts")
 	}
 
-	rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
+	rawProtected, err := base64URLDecode(parts[0])
 	if err != nil {
 		return nil, err
 	}
 
-	encryptedKey, err := base64.RawURLEncoding.DecodeString(parts[1])
+	encryptedKey, err := base64URLDecode(parts[1])
 	if err != nil {
 		return nil, err
 	}
 
-	iv, err := base64.RawURLEncoding.DecodeString(parts[2])
+	iv, err := base64URLDecode(parts[2])
 	if err != nil {
 		return nil, err
 	}
 
-	ciphertext, err := base64.RawURLEncoding.DecodeString(parts[3])
+	ciphertext, err := base64URLDecode(parts[3])
 	if err != nil {
 		return nil, err
 	}
 
-	tag, err := base64.RawURLEncoding.DecodeString(parts[4])
+	tag, err := base64URLDecode(parts[4])
 	if err != nil {
 		return nil, err
 	}
@@ -326,7 +240,7 @@ func ParseEncryptedCompact(
 		Tag:          newBuffer(tag),
 	}
 
-	return raw.sanitized(keyAlgorithms, contentEncryption)
+	return raw.sanitized()
 }
 
 // CompactSerialize serializes an object using the compact serialization format.
diff --git a/vendor/github.com/go-jose/go-jose/v4/jwk.go b/vendor/github.com/go-jose/go-jose/v3/jwk.go
similarity index 99%
rename from vendor/github.com/go-jose/go-jose/v4/jwk.go
rename to vendor/github.com/go-jose/go-jose/v3/jwk.go
index a565aaab..e4021959 100644
--- a/vendor/github.com/go-jose/go-jose/v4/jwk.go
+++ b/vendor/github.com/go-jose/go-jose/v3/jwk.go
@@ -35,7 +35,7 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/go-jose/go-jose/v4/json"
+	"github.com/go-jose/go-jose/v3/json"
 )
 
 // rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
@@ -266,7 +266,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
 
 	// x5t parameters are base64url-encoded SHA thumbprints
 	// See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
-	x5tSHA1bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA1)
+	x5tSHA1bytes, err := base64URLDecode(raw.X5tSHA1)
 	if err != nil {
 		return errors.New("go-jose/go-jose: invalid JWK, x5t header has invalid encoding")
 	}
@@ -286,7 +286,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
 
 	k.CertificateThumbprintSHA1 = x5tSHA1bytes
 
-	x5tSHA256bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA256)
+	x5tSHA256bytes, err := base64URLDecode(raw.X5tSHA256)
 	if err != nil {
 		return errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
 	}
diff --git a/vendor/github.com/go-jose/go-jose/v4/jws.go b/vendor/github.com/go-jose/go-jose/v3/jws.go
similarity index 80%
rename from vendor/github.com/go-jose/go-jose/v4/jws.go
rename to vendor/github.com/go-jose/go-jose/v3/jws.go
index 3a912301..e37007db 100644
--- a/vendor/github.com/go-jose/go-jose/v4/jws.go
+++ b/vendor/github.com/go-jose/go-jose/v3/jws.go
@@ -23,7 +23,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/go-jose/go-jose/v4/json"
+	"github.com/go-jose/go-jose/v3/json"
 )
 
 // rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
@@ -75,41 +75,22 @@ type Signature struct {
 	original  *rawSignatureInfo
 }
 
-// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization.
-//
-// https://datatracker.ietf.org/doc/html/rfc7515#section-7
-func ParseSigned(
-	signature string,
-	signatureAlgorithms []SignatureAlgorithm,
-) (*JSONWebSignature, error) {
+// ParseSigned parses a signed message in compact or JWS JSON Serialization format.
+func ParseSigned(signature string) (*JSONWebSignature, error) {
 	signature = stripWhitespace(signature)
 	if strings.HasPrefix(signature, "{") {
-		return ParseSignedJSON(signature, signatureAlgorithms)
+		return parseSignedFull(signature)
 	}
 
-	return parseSignedCompact(signature, nil, signatureAlgorithms)
-}
-
-// ParseSignedCompact parses a message in JWS Compact Serialization.
-//
-// https://datatracker.ietf.org/doc/html/rfc7515#section-7.1
-func ParseSignedCompact(
-	signature string,
-	signatureAlgorithms []SignatureAlgorithm,
-) (*JSONWebSignature, error) {
-	return parseSignedCompact(signature, nil, signatureAlgorithms)
+	return parseSignedCompact(signature, nil)
 }
 
 // ParseDetached parses a signed message in compact serialization format with detached payload.
-func ParseDetached(
-	signature string,
-	payload []byte,
-	signatureAlgorithms []SignatureAlgorithm,
-) (*JSONWebSignature, error) {
+func ParseDetached(signature string, payload []byte) (*JSONWebSignature, error) {
 	if payload == nil {
 		return nil, errors.New("go-jose/go-jose: nil payload")
 	}
-	return parseSignedCompact(stripWhitespace(signature), payload, signatureAlgorithms)
+	return parseSignedCompact(stripWhitespace(signature), payload)
 }
 
 // Get a header value
@@ -156,36 +137,19 @@ func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature
 	return authData.Bytes(), nil
 }
 
-// ParseSignedJSON parses a message in JWS JSON Serialization.
-//
-// https://datatracker.ietf.org/doc/html/rfc7515#section-7.2
-func ParseSignedJSON(
-	input string,
-	signatureAlgorithms []SignatureAlgorithm,
-) (*JSONWebSignature, error) {
+// parseSignedFull parses a message in full format.
+func parseSignedFull(input string) (*JSONWebSignature, error) {
 	var parsed rawJSONWebSignature
 	err := json.Unmarshal([]byte(input), &parsed)
 	if err != nil {
 		return nil, err
 	}
 
-	return parsed.sanitized(signatureAlgorithms)
-}
-
-func containsSignatureAlgorithm(haystack []SignatureAlgorithm, needle SignatureAlgorithm) bool {
-	for _, algorithm := range haystack {
-		if algorithm == needle {
-			return true
-		}
-	}
-	return false
+	return parsed.sanitized()
 }
 
 // sanitized produces a cleaned-up JWS object from the raw JSON.
-func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgorithm) (*JSONWebSignature, error) {
-	if len(signatureAlgorithms) == 0 {
-		return nil, errors.New("go-jose/go-jose: no signature algorithms specified")
-	}
+func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
 	if parsed.Payload == nil {
 		return nil, fmt.Errorf("go-jose/go-jose: missing payload in JWS message")
 	}
@@ -234,12 +198,6 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
 			return nil, err
 		}
 
-		alg := SignatureAlgorithm(signature.Header.Algorithm)
-		if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
-			return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q",
-				alg, signatureAlgorithms)
-		}
-
 		if signature.header != nil {
 			signature.Unprotected, err = signature.header.sanitized()
 			if err != nil {
@@ -283,12 +241,6 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
 			return nil, err
 		}
 
-		alg := SignatureAlgorithm(obj.Signatures[i].Header.Algorithm)
-		if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
-			return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q",
-				alg, signatureAlgorithms)
-		}
-
 		if obj.Signatures[i].header != nil {
 			obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
 			if err != nil {
@@ -322,11 +274,7 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
 }
 
 // parseSignedCompact parses a message in compact format.
-func parseSignedCompact(
-	input string,
-	payload []byte,
-	signatureAlgorithms []SignatureAlgorithm,
-) (*JSONWebSignature, error) {
+func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error) {
 	parts := strings.Split(input, ".")
 	if len(parts) != 3 {
 		return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts")
@@ -336,19 +284,19 @@ func parseSignedCompact(
 		return nil, fmt.Errorf("go-jose/go-jose: payload is not detached")
 	}
 
-	rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
+	rawProtected, err := base64URLDecode(parts[0])
 	if err != nil {
 		return nil, err
 	}
 
 	if payload == nil {
-		payload, err = base64.RawURLEncoding.DecodeString(parts[1])
+		payload, err = base64URLDecode(parts[1])
 		if err != nil {
 			return nil, err
 		}
 	}
 
-	signature, err := base64.RawURLEncoding.DecodeString(parts[2])
+	signature, err := base64URLDecode(parts[2])
 	if err != nil {
 		return nil, err
 	}
@@ -358,7 +306,7 @@ func parseSignedCompact(
 		Protected: newBuffer(rawProtected),
 		Signature: newBuffer(signature),
 	}
-	return raw.sanitized(signatureAlgorithms)
+	return raw.sanitized()
 }
 
 func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
diff --git a/vendor/github.com/go-jose/go-jose/v4/jwt/builder.go b/vendor/github.com/go-jose/go-jose/v3/jwt/builder.go
similarity index 83%
rename from vendor/github.com/go-jose/go-jose/v4/jwt/builder.go
rename to vendor/github.com/go-jose/go-jose/v3/jwt/builder.go
index d68bb372..7df270cc 100644
--- a/vendor/github.com/go-jose/go-jose/v4/jwt/builder.go
+++ b/vendor/github.com/go-jose/go-jose/v3/jwt/builder.go
@@ -21,13 +21,13 @@ import (
 	"bytes"
 	"reflect"
 
-	"github.com/go-jose/go-jose/v4/json"
+	"github.com/go-jose/go-jose/v3/json"
 
-	"github.com/go-jose/go-jose/v4"
+	"github.com/go-jose/go-jose/v3"
 )
 
 // Builder is a utility for making JSON Web Tokens. Calls can be chained, and
-// errors are accumulated until the final call to Serialize.
+// errors are accumulated until the final call to CompactSerialize/FullSerialize.
 type Builder interface {
 	// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
 	// into single JSON object. If you are passing private claims, make sure to set
@@ -36,13 +36,15 @@ type Builder interface {
 	Claims(i interface{}) Builder
 	// Token builds a JSONWebToken from provided data.
 	Token() (*JSONWebToken, error)
-	// Serialize serializes a token.
-	Serialize() (string, error)
+	// FullSerialize serializes a token using the JWS/JWE JSON Serialization format.
+	FullSerialize() (string, error)
+	// CompactSerialize serializes a token using the compact serialization format.
+	CompactSerialize() (string, error)
 }
 
 // NestedBuilder is a utility for making Signed-Then-Encrypted JSON Web Tokens.
 // Calls can be chained, and errors are accumulated until final call to
-// Serialize.
+// CompactSerialize/FullSerialize.
 type NestedBuilder interface {
 	// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
 	// into single JSON object. If you are passing private claims, make sure to set
@@ -51,8 +53,10 @@ type NestedBuilder interface {
 	Claims(i interface{}) NestedBuilder
 	// Token builds a NestedJSONWebToken from provided data.
 	Token() (*NestedJSONWebToken, error)
-	// Serialize serializes a token.
-	Serialize() (string, error)
+	// FullSerialize serializes a token using the JSON Serialization format.
+	FullSerialize() (string, error)
+	// CompactSerialize serializes a token using the compact serialization format.
+	CompactSerialize() (string, error)
 }
 
 type builder struct {
@@ -190,7 +194,7 @@ func (b *signedBuilder) Token() (*JSONWebToken, error) {
 	return b.builder.token(sig.Verify, h)
 }
 
-func (b *signedBuilder) Serialize() (string, error) {
+func (b *signedBuilder) CompactSerialize() (string, error) {
 	sig, err := b.sign()
 	if err != nil {
 		return "", err
@@ -199,6 +203,15 @@ func (b *signedBuilder) Serialize() (string, error) {
 	return sig.CompactSerialize()
 }
 
+func (b *signedBuilder) FullSerialize() (string, error) {
+	sig, err := b.sign()
+	if err != nil {
+		return "", err
+	}
+
+	return sig.FullSerialize(), nil
+}
+
 func (b *signedBuilder) sign() (*jose.JSONWebSignature, error) {
 	if b.err != nil {
 		return nil, b.err
@@ -219,7 +232,7 @@ func (b *encryptedBuilder) Claims(i interface{}) Builder {
 	}
 }
 
-func (b *encryptedBuilder) Serialize() (string, error) {
+func (b *encryptedBuilder) CompactSerialize() (string, error) {
 	enc, err := b.encrypt()
 	if err != nil {
 		return "", err
@@ -228,6 +241,15 @@ func (b *encryptedBuilder) Serialize() (string, error) {
 	return enc.CompactSerialize()
 }
 
+func (b *encryptedBuilder) FullSerialize() (string, error) {
+	enc, err := b.encrypt()
+	if err != nil {
+		return "", err
+	}
+
+	return enc.FullSerialize(), nil
+}
+
 func (b *encryptedBuilder) Token() (*JSONWebToken, error) {
 	enc, err := b.encrypt()
 	if err != nil {
@@ -258,8 +280,6 @@ func (b *nestedBuilder) Claims(i interface{}) NestedBuilder {
 	}
 }
 
-// Token produced a token suitable for serialization. It cannot be decrypted
-// without serializing and then deserializing.
 func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) {
 	enc, err := b.signAndEncrypt()
 	if err != nil {
@@ -267,13 +287,12 @@ func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) {
 	}
 
 	return &NestedJSONWebToken{
-		allowedSignatureAlgorithms: nil,
-		enc:                        enc,
-		Headers:                    []jose.Header{enc.Header},
+		enc:     enc,
+		Headers: []jose.Header{enc.Header},
 	}, nil
 }
 
-func (b *nestedBuilder) Serialize() (string, error) {
+func (b *nestedBuilder) CompactSerialize() (string, error) {
 	enc, err := b.signAndEncrypt()
 	if err != nil {
 		return "", err
diff --git a/vendor/github.com/go-jose/go-jose/v4/jwt/claims.go b/vendor/github.com/go-jose/go-jose/v3/jwt/claims.go
similarity index 98%
rename from vendor/github.com/go-jose/go-jose/v4/jwt/claims.go
rename to vendor/github.com/go-jose/go-jose/v3/jwt/claims.go
index e73412a3..b2a8dc8d 100644
--- a/vendor/github.com/go-jose/go-jose/v4/jwt/claims.go
+++ b/vendor/github.com/go-jose/go-jose/v3/jwt/claims.go
@@ -21,7 +21,7 @@ import (
 	"strconv"
 	"time"
 
-	"github.com/go-jose/go-jose/v4/json"
+	"github.com/go-jose/go-jose/v3/json"
 )
 
 // Claims represents public claim values (as specified in RFC 7519).
diff --git a/vendor/github.com/go-jose/go-jose/v4/jwt/doc.go b/vendor/github.com/go-jose/go-jose/v3/jwt/doc.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/jwt/doc.go
rename to vendor/github.com/go-jose/go-jose/v3/jwt/doc.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/jwt/errors.go b/vendor/github.com/go-jose/go-jose/v3/jwt/errors.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/jwt/errors.go
rename to vendor/github.com/go-jose/go-jose/v3/jwt/errors.go
diff --git a/vendor/github.com/go-jose/go-jose/v3/jwt/jwt.go b/vendor/github.com/go-jose/go-jose/v3/jwt/jwt.go
new file mode 100644
index 00000000..8553fc50
--- /dev/null
+++ b/vendor/github.com/go-jose/go-jose/v3/jwt/jwt.go
@@ -0,0 +1,133 @@
+/*-
+ * Copyright 2016 Zbigniew Mandziejewicz
+ * Copyright 2016 Square, Inc.
+ *
+ * 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 jwt
+
+import (
+	"fmt"
+	"strings"
+
+	jose "github.com/go-jose/go-jose/v3"
+	"github.com/go-jose/go-jose/v3/json"
+)
+
+// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
+type JSONWebToken struct {
+	payload           func(k interface{}) ([]byte, error)
+	unverifiedPayload func() []byte
+	Headers           []jose.Header
+}
+
+type NestedJSONWebToken struct {
+	enc     *jose.JSONWebEncryption
+	Headers []jose.Header
+}
+
+// Claims deserializes a JSONWebToken into dest using the provided key.
+func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
+	b, err := t.payload(key)
+	if err != nil {
+		return err
+	}
+
+	for _, d := range dest {
+		if err := json.Unmarshal(b, d); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// UnsafeClaimsWithoutVerification deserializes the claims of a
+// JSONWebToken into the dests. For signed JWTs, the claims are not
+// verified. This function won't work for encrypted JWTs.
+func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{}) error {
+	if t.unverifiedPayload == nil {
+		return fmt.Errorf("go-jose/go-jose: Cannot get unverified claims")
+	}
+	claims := t.unverifiedPayload()
+	for _, d := range dest {
+		if err := json.Unmarshal(claims, d); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken, error) {
+	b, err := t.enc.Decrypt(decryptionKey)
+	if err != nil {
+		return nil, err
+	}
+
+	sig, err := ParseSigned(string(b))
+	if err != nil {
+		return nil, err
+	}
+
+	return sig, nil
+}
+
+// ParseSigned parses token from JWS form.
+func ParseSigned(s string) (*JSONWebToken, error) {
+	sig, err := jose.ParseSigned(s)
+	if err != nil {
+		return nil, err
+	}
+	headers := make([]jose.Header, len(sig.Signatures))
+	for i, signature := range sig.Signatures {
+		headers[i] = signature.Header
+	}
+
+	return &JSONWebToken{
+		payload:           sig.Verify,
+		unverifiedPayload: sig.UnsafePayloadWithoutVerification,
+		Headers:           headers,
+	}, nil
+}
+
+// ParseEncrypted parses token from JWE form.
+func ParseEncrypted(s string) (*JSONWebToken, error) {
+	enc, err := jose.ParseEncrypted(s)
+	if err != nil {
+		return nil, err
+	}
+
+	return &JSONWebToken{
+		payload: enc.Decrypt,
+		Headers: []jose.Header{enc.Header},
+	}, nil
+}
+
+// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
+func ParseSignedAndEncrypted(s string) (*NestedJSONWebToken, error) {
+	enc, err := jose.ParseEncrypted(s)
+	if err != nil {
+		return nil, err
+	}
+
+	contentType, _ := enc.Header.ExtraHeaders[jose.HeaderContentType].(string)
+	if strings.ToUpper(contentType) != "JWT" {
+		return nil, ErrInvalidContentType
+	}
+
+	return &NestedJSONWebToken{
+		enc:     enc,
+		Headers: []jose.Header{enc.Header},
+	}, nil
+}
diff --git a/vendor/github.com/go-jose/go-jose/v4/jwt/validation.go b/vendor/github.com/go-jose/go-jose/v3/jwt/validation.go
similarity index 91%
rename from vendor/github.com/go-jose/go-jose/v4/jwt/validation.go
rename to vendor/github.com/go-jose/go-jose/v3/jwt/validation.go
index 841a93ee..09d8541f 100644
--- a/vendor/github.com/go-jose/go-jose/v4/jwt/validation.go
+++ b/vendor/github.com/go-jose/go-jose/v3/jwt/validation.go
@@ -33,9 +33,8 @@ type Expected struct {
 	Issuer string
 	// Subject matches the "sub" claim exactly.
 	Subject string
-	// AnyAudience matches if there is a non-empty intersection between
-	// its values and the values in the "aud" claim.
-	AnyAudience Audience
+	// Audience matches the values in "aud" claim, regardless of their order.
+	Audience Audience
 	// ID matches the "jti" claim exactly.
 	ID string
 	// Time matches the "exp", "nbf" and "iat" claims with leeway.
@@ -89,18 +88,12 @@ func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
 		return ErrInvalidID
 	}
 
-	if len(e.AnyAudience) != 0 {
-		var intersection bool
-		for _, v := range e.AnyAudience {
-			if c.Audience.Contains(v) {
-				intersection = true
-				break
+	if len(e.Audience) != 0 {
+		for _, v := range e.Audience {
+			if !c.Audience.Contains(v) {
+				return ErrInvalidAudience
 			}
 		}
-
-		if !intersection {
-			return ErrInvalidAudience
-		}
 	}
 
 	// validate using the e.Time, or time.Now if not provided
diff --git a/vendor/github.com/go-jose/go-jose/v4/opaque.go b/vendor/github.com/go-jose/go-jose/v3/opaque.go
similarity index 100%
rename from vendor/github.com/go-jose/go-jose/v4/opaque.go
rename to vendor/github.com/go-jose/go-jose/v3/opaque.go
diff --git a/vendor/github.com/go-jose/go-jose/v4/shared.go b/vendor/github.com/go-jose/go-jose/v3/shared.go
similarity index 97%
rename from vendor/github.com/go-jose/go-jose/v4/shared.go
rename to vendor/github.com/go-jose/go-jose/v3/shared.go
index 1ec33961..489a04e3 100644
--- a/vendor/github.com/go-jose/go-jose/v4/shared.go
+++ b/vendor/github.com/go-jose/go-jose/v3/shared.go
@@ -23,7 +23,7 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/go-jose/go-jose/v4/json"
+	"github.com/go-jose/go-jose/v3/json"
 )
 
 // KeyAlgorithm represents a key management algorithm.
@@ -71,12 +71,6 @@ var (
 	// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
 	// nonce header parameter was included in an unprotected header object.
 	ErrUnprotectedNonce = errors.New("go-jose/go-jose: Nonce parameter included in unprotected header")
-
-	// ErrMissingX5cHeader indicates that the JWT header is missing x5c headers.
-	ErrMissingX5cHeader = errors.New("go-jose/go-jose: no x5c header present in message")
-
-	// ErrUnsupportedEllipticCurve indicates unsupported or unknown elliptic curve has been found.
-	ErrUnsupportedEllipticCurve = errors.New("go-jose/go-jose: unsupported/unknown elliptic curve")
 )
 
 // Key management algorithms
@@ -205,7 +199,7 @@ type Header struct {
 // not be validated with the given verify options.
 func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
 	if len(h.certificates) == 0 {
-		return nil, ErrMissingX5cHeader
+		return nil, errors.New("go-jose/go-jose: no x5c header present in message")
 	}
 
 	leaf := h.certificates[0]
@@ -507,7 +501,7 @@ func curveName(crv elliptic.Curve) (string, error) {
 	case elliptic.P521():
 		return "P-521", nil
 	default:
-		return "", ErrUnsupportedEllipticCurve
+		return "", fmt.Errorf("go-jose/go-jose: unsupported/unknown elliptic curve")
 	}
 }
 
diff --git a/vendor/github.com/go-jose/go-jose/v4/signing.go b/vendor/github.com/go-jose/go-jose/v3/signing.go
similarity index 97%
rename from vendor/github.com/go-jose/go-jose/v4/signing.go
rename to vendor/github.com/go-jose/go-jose/v3/signing.go
index 46c9a4d9..52f3d856 100644
--- a/vendor/github.com/go-jose/go-jose/v4/signing.go
+++ b/vendor/github.com/go-jose/go-jose/v3/signing.go
@@ -25,7 +25,7 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/go-jose/go-jose/v4/json"
+	"github.com/go-jose/go-jose/v3/json"
 )
 
 // NonceSource represents a source of random nonces to go into JWS objects
@@ -49,11 +49,6 @@ type Signer interface {
 //   - JSONWebKey
 //   - []byte (an HMAC key)
 //   - Any type that satisfies the OpaqueSigner interface
-//
-// If the key is an HMAC key, it must have at least as many bytes as the relevant hash output:
-//   - HS256: 32 bytes
-//   - HS384: 48 bytes
-//   - HS512: 64 bytes
 type SigningKey struct {
 	Algorithm SignatureAlgorithm
 	Key       interface{}
@@ -358,15 +353,8 @@ func (ctx *genericSigner) Options() SignerOptions {
 //   - *rsa.PublicKey
 //   - *JSONWebKey
 //   - JSONWebKey
-//   - *JSONWebKeySet
-//   - JSONWebKeySet
 //   - []byte (an HMAC key)
 //   - Any type that implements the OpaqueVerifier interface.
-//
-// If the key is an HMAC key, it must have at least as many bytes as the relevant hash output:
-//   - HS256: 32 bytes
-//   - HS384: 48 bytes
-//   - HS512: 64 bytes
 func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
 	err := obj.DetachedVerify(obj.payload, verificationKey)
 	if err != nil {
diff --git a/vendor/github.com/go-jose/go-jose/v4/symmetric.go b/vendor/github.com/go-jose/go-jose/v3/symmetric.go
similarity index 96%
rename from vendor/github.com/go-jose/go-jose/v4/symmetric.go
rename to vendor/github.com/go-jose/go-jose/v3/symmetric.go
index a69103b0..10d8e19f 100644
--- a/vendor/github.com/go-jose/go-jose/v4/symmetric.go
+++ b/vendor/github.com/go-jose/go-jose/v3/symmetric.go
@@ -32,7 +32,7 @@ import (
 
 	"golang.org/x/crypto/pbkdf2"
 
-	josecipher "github.com/go-jose/go-jose/v4/cipher"
+	josecipher "github.com/go-jose/go-jose/v3/cipher"
 )
 
 // RandReader is a cryptographically secure random number generator (stubbed out in tests).
@@ -454,7 +454,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
 func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
 	mac, err := ctx.hmac(payload, alg)
 	if err != nil {
-		return Signature{}, err
+		return Signature{}, errors.New("go-jose/go-jose: failed to compute hmac")
 	}
 
 	return Signature{
@@ -486,24 +486,12 @@ func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureA
 func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) {
 	var hash func() hash.Hash
 
-	// https://datatracker.ietf.org/doc/html/rfc7518#section-3.2
-	// A key of the same size as the hash output (for instance, 256 bits for
-	// "HS256") or larger MUST be used
 	switch alg {
 	case HS256:
-		if len(ctx.key)*8 < 256 {
-			return nil, ErrInvalidKeySize
-		}
 		hash = sha256.New
 	case HS384:
-		if len(ctx.key)*8 < 384 {
-			return nil, ErrInvalidKeySize
-		}
 		hash = sha512.New384
 	case HS512:
-		if len(ctx.key)*8 < 512 {
-			return nil, ErrInvalidKeySize
-		}
 		hash = sha512.New
 	default:
 		return nil, ErrUnsupportedAlgorithm
diff --git a/vendor/github.com/go-jose/go-jose/v4/jwt/jwt.go b/vendor/github.com/go-jose/go-jose/v4/jwt/jwt.go
deleted file mode 100644
index c4998d78..00000000
--- a/vendor/github.com/go-jose/go-jose/v4/jwt/jwt.go
+++ /dev/null
@@ -1,198 +0,0 @@
-/*-
- * Copyright 2016 Zbigniew Mandziejewicz
- * Copyright 2016 Square, Inc.
- *
- * 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 jwt
-
-import (
-	"fmt"
-	"strings"
-
-	jose "github.com/go-jose/go-jose/v4"
-	"github.com/go-jose/go-jose/v4/json"
-)
-
-// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
-type JSONWebToken struct {
-	payload           func(k interface{}) ([]byte, error)
-	unverifiedPayload func() []byte
-	Headers           []jose.Header
-}
-
-type NestedJSONWebToken struct {
-	enc     *jose.JSONWebEncryption
-	Headers []jose.Header
-	// Used when parsing and decrypting an input
-	allowedSignatureAlgorithms []jose.SignatureAlgorithm
-}
-
-// Claims deserializes a JSONWebToken into dest using the provided key.
-func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
-	b, err := t.payload(key)
-	if err != nil {
-		return err
-	}
-
-	for _, d := range dest {
-		if err := json.Unmarshal(b, d); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// UnsafeClaimsWithoutVerification deserializes the claims of a
-// JSONWebToken into the dests. For signed JWTs, the claims are not
-// verified. This function won't work for encrypted JWTs.
-func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{}) error {
-	if t.unverifiedPayload == nil {
-		return fmt.Errorf("go-jose/go-jose: Cannot get unverified claims")
-	}
-	claims := t.unverifiedPayload()
-	for _, d := range dest {
-		if err := json.Unmarshal(claims, d); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken, error) {
-	b, err := t.enc.Decrypt(decryptionKey)
-	if err != nil {
-		return nil, err
-	}
-
-	sig, err := ParseSigned(string(b), t.allowedSignatureAlgorithms)
-	if err != nil {
-		return nil, err
-	}
-
-	return sig, nil
-}
-
-// ParseSigned parses token from JWS form.
-func ParseSigned(s string, signatureAlgorithms []jose.SignatureAlgorithm) (*JSONWebToken, error) {
-	sig, err := jose.ParseSignedCompact(s, signatureAlgorithms)
-	if err != nil {
-		return nil, err
-	}
-	headers := make([]jose.Header, len(sig.Signatures))
-	for i, signature := range sig.Signatures {
-		headers[i] = signature.Header
-	}
-
-	return &JSONWebToken{
-		payload:           sig.Verify,
-		unverifiedPayload: sig.UnsafePayloadWithoutVerification,
-		Headers:           headers,
-	}, nil
-}
-
-func validateKeyEncryptionAlgorithm(algs []jose.KeyAlgorithm) error {
-	for _, alg := range algs {
-		switch alg {
-		case jose.ED25519,
-			jose.RSA1_5,
-			jose.RSA_OAEP,
-			jose.RSA_OAEP_256,
-			jose.ECDH_ES,
-			jose.ECDH_ES_A128KW,
-			jose.ECDH_ES_A192KW,
-			jose.ECDH_ES_A256KW:
-			return fmt.Errorf("asymmetric encryption algorithms not supported for JWT: "+
-				"invalid key encryption algorithm: %s", alg)
-		case jose.PBES2_HS256_A128KW,
-			jose.PBES2_HS384_A192KW,
-			jose.PBES2_HS512_A256KW:
-			return fmt.Errorf("password-based encryption not supported for JWT: "+
-				"invalid key encryption algorithm: %s", alg)
-		}
-	}
-	return nil
-}
-
-func parseEncryptedCompact(
-	s string,
-	keyAlgorithms []jose.KeyAlgorithm,
-	contentEncryption []jose.ContentEncryption,
-) (*jose.JSONWebEncryption, error) {
-	err := validateKeyEncryptionAlgorithm(keyAlgorithms)
-	if err != nil {
-		return nil, err
-	}
-	enc, err := jose.ParseEncryptedCompact(s, keyAlgorithms, contentEncryption)
-	if err != nil {
-		return nil, err
-	}
-	return enc, nil
-}
-
-// ParseEncrypted parses token from JWE form.
-//
-// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
-// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
-// parsed data must contain a value that is present in the corresponding parameter. That
-// includes the protected and unprotected headers as well as all recipients. To accept
-// multiple algorithms, pass a slice of all the algorithms you want to accept.
-func ParseEncrypted(s string,
-	keyAlgorithms []jose.KeyAlgorithm,
-	contentEncryption []jose.ContentEncryption,
-) (*JSONWebToken, error) {
-	enc, err := parseEncryptedCompact(s, keyAlgorithms, contentEncryption)
-	if err != nil {
-		return nil, err
-	}
-
-	return &JSONWebToken{
-		payload: enc.Decrypt,
-		Headers: []jose.Header{enc.Header},
-	}, nil
-}
-
-// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
-//
-// The encryptionKeyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
-// header parameters, respectively, of the outer JWE. They must be nonempty, and each "alg" or "enc"
-// header in parsed data must contain a value that is present in the corresponding parameter. That
-// includes the protected and unprotected headers as well as all recipients. To accept
-// multiple algorithms, pass a slice of all the algorithms you want to accept.
-//
-// The signatureAlgorithms parameter is used to validate the "alg" header parameter of the
-// inner JWS. It must be nonempty, and the "alg" header in the inner JWS must contain a value
-// that is present in the parameter.
-func ParseSignedAndEncrypted(s string,
-	encryptionKeyAlgorithms []jose.KeyAlgorithm,
-	contentEncryption []jose.ContentEncryption,
-	signatureAlgorithms []jose.SignatureAlgorithm,
-) (*NestedJSONWebToken, error) {
-	enc, err := parseEncryptedCompact(s, encryptionKeyAlgorithms, contentEncryption)
-	if err != nil {
-		return nil, err
-	}
-
-	contentType, _ := enc.Header.ExtraHeaders[jose.HeaderContentType].(string)
-	if strings.ToUpper(contentType) != "JWT" {
-		return nil, ErrInvalidContentType
-	}
-
-	return &NestedJSONWebToken{
-		allowedSignatureAlgorithms: signatureAlgorithms,
-		enc:                        enc,
-		Headers:                    []jose.Header{enc.Header},
-	}, nil
-}
diff --git a/vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64le.s b/vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64le.s
index b3c1699b..d2ca5dee 100644
--- a/vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64le.s
+++ b/vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64le.s
@@ -19,14 +19,15 @@
 
 #define POLY1305_MUL(h0, h1, h2, r0, r1, t0, t1, t2, t3, t4, t5) \
 	MULLD  r0, h0, t0;  \
-	MULHDU r0, h0, t1;  \
 	MULLD  r0, h1, t4;  \
+	MULHDU r0, h0, t1;  \
 	MULHDU r0, h1, t5;  \
 	ADDC   t4, t1, t1;  \
 	MULLD  r0, h2, t2;  \
+	ADDZE  t5;          \
 	MULHDU r1, h0, t4;  \
 	MULLD  r1, h0, h0;  \
-	ADDE   t5, t2, t2;  \
+	ADD    t5, t2, t2;  \
 	ADDC   h0, t1, t1;  \
 	MULLD  h2, r1, t3;  \
 	ADDZE  t4, h0;      \
@@ -36,11 +37,13 @@
 	ADDE   t5, t3, t3;  \
 	ADDC   h0, t2, t2;  \
 	MOVD   $-4, t4;     \
+	MOVD   t0, h0;      \
+	MOVD   t1, h1;      \
 	ADDZE  t3;          \
-	RLDICL $0, t2, $62, h2; \
-	AND    t2, t4, h0;  \
+	ANDCC  $3, t2, h2;  \
+	AND    t2, t4, t0;  \
 	ADDC   t0, h0, h0;  \
-	ADDE   t3, t1, h1;  \
+	ADDE   t3, h1, h1;  \
 	SLD    $62, t3, t4; \
 	SRD    $2, t2;      \
 	ADDZE  h2;          \
@@ -72,7 +75,6 @@ TEXT ·update(SB), $0-32
 loop:
 	POLY1305_ADD(R4, R8, R9, R10, R20, R21, R22)
 
-	PCALIGN $16
 multiply:
 	POLY1305_MUL(R8, R9, R10, R11, R12, R16, R17, R18, R14, R20, R21)
 	ADD $-16, R5
diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go
index 43557ab7..c1f6b90d 100644
--- a/vendor/golang.org/x/net/http2/frame.go
+++ b/vendor/golang.org/x/net/http2/frame.go
@@ -1510,12 +1510,13 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
 }
 
 func (fr *Framer) maxHeaderStringLen() int {
-	v := int(fr.maxHeaderListSize())
-	if v < 0 {
-		// If maxHeaderListSize overflows an int, use no limit (0).
-		return 0
+	v := fr.maxHeaderListSize()
+	if uint32(int(v)) == v {
+		return int(v)
 	}
-	return v
+	// They had a crazy big number for MaxHeaderBytes anyway,
+	// so give them unlimited header lengths:
+	return 0
 }
 
 // readMetaFrame returns 0 or more CONTINUATION frames from fr and
@@ -1564,7 +1565,6 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
 		if size > remainSize {
 			hdec.SetEmitEnabled(false)
 			mh.Truncated = true
-			remainSize = 0
 			return
 		}
 		remainSize -= size
@@ -1577,36 +1577,6 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
 	var hc headersOrContinuation = hf
 	for {
 		frag := hc.HeaderBlockFragment()
-
-		// Avoid parsing large amounts of headers that we will then discard.
-		// If the sender exceeds the max header list size by too much,
-		// skip parsing the fragment and close the connection.
-		//
-		// "Too much" is either any CONTINUATION frame after we've already
-		// exceeded the max header list size (in which case remainSize is 0),
-		// or a frame whose encoded size is more than twice the remaining
-		// header list bytes we're willing to accept.
-		if int64(len(frag)) > int64(2*remainSize) {
-			if VerboseLogs {
-				log.Printf("http2: header list too large")
-			}
-			// It would be nice to send a RST_STREAM before sending the GOAWAY,
-			// but the structure of the server's frame writer makes this difficult.
-			return nil, ConnectionError(ErrCodeProtocol)
-		}
-
-		// Also close the connection after any CONTINUATION frame following an
-		// invalid header, since we stop tracking the size of the headers after
-		// an invalid one.
-		if invalid != nil {
-			if VerboseLogs {
-				log.Printf("http2: invalid header: %v", invalid)
-			}
-			// It would be nice to send a RST_STREAM before sending the GOAWAY,
-			// but the structure of the server's frame writer makes this difficult.
-			return nil, ConnectionError(ErrCodeProtocol)
-		}
-
 		if _, err := hdec.Write(frag); err != nil {
 			return nil, ConnectionError(ErrCodeCompression)
 		}
diff --git a/vendor/golang.org/x/net/http2/pipe.go b/vendor/golang.org/x/net/http2/pipe.go
index 3b9f06b9..684d984f 100644
--- a/vendor/golang.org/x/net/http2/pipe.go
+++ b/vendor/golang.org/x/net/http2/pipe.go
@@ -77,10 +77,7 @@ func (p *pipe) Read(d []byte) (n int, err error) {
 	}
 }
 
-var (
-	errClosedPipeWrite        = errors.New("write on closed buffer")
-	errUninitializedPipeWrite = errors.New("write on uninitialized buffer")
-)
+var errClosedPipeWrite = errors.New("write on closed buffer")
 
 // Write copies bytes from p into the buffer and wakes a reader.
 // It is an error to write more data than the buffer can hold.
@@ -94,12 +91,6 @@ func (p *pipe) Write(d []byte) (n int, err error) {
 	if p.err != nil || p.breakErr != nil {
 		return 0, errClosedPipeWrite
 	}
-	// pipe.setBuffer is never invoked, leaving the buffer uninitialized.
-	// We shouldn't try to write to an uninitialized pipe,
-	// but returning an error is better than panicking.
-	if p.b == nil {
-		return 0, errUninitializedPipeWrite
-	}
 	return p.b.Write(d)
 }
 
diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go
index ce2e8b40..ae94c640 100644
--- a/vendor/golang.org/x/net/http2/server.go
+++ b/vendor/golang.org/x/net/http2/server.go
@@ -124,7 +124,6 @@ type Server struct {
 	// IdleTimeout specifies how long until idle clients should be
 	// closed with a GOAWAY frame. PING frames are not considered
 	// activity for the purposes of IdleTimeout.
-	// If zero or negative, there is no timeout.
 	IdleTimeout time.Duration
 
 	// MaxUploadBufferPerConnection is the size of the initial flow
@@ -435,7 +434,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
 	// passes the connection off to us with the deadline already set.
 	// Write deadlines are set per stream in serverConn.newStream.
 	// Disarm the net.Conn write deadline here.
-	if sc.hs.WriteTimeout > 0 {
+	if sc.hs.WriteTimeout != 0 {
 		sc.conn.SetWriteDeadline(time.Time{})
 	}
 
@@ -925,7 +924,7 @@ func (sc *serverConn) serve() {
 	sc.setConnState(http.StateActive)
 	sc.setConnState(http.StateIdle)
 
-	if sc.srv.IdleTimeout > 0 {
+	if sc.srv.IdleTimeout != 0 {
 		sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
 		defer sc.idleTimer.Stop()
 	}
@@ -1638,7 +1637,7 @@ func (sc *serverConn) closeStream(st *stream, err error) {
 	delete(sc.streams, st.id)
 	if len(sc.streams) == 0 {
 		sc.setConnState(http.StateIdle)
-		if sc.srv.IdleTimeout > 0 {
+		if sc.srv.IdleTimeout != 0 {
 			sc.idleTimer.Reset(sc.srv.IdleTimeout)
 		}
 		if h1ServerKeepAlivesDisabled(sc.hs) {
@@ -2018,7 +2017,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
 	// similar to how the http1 server works. Here it's
 	// technically more like the http1 Server's ReadHeaderTimeout
 	// (in Go 1.8), though. That's a more sane option anyway.
-	if sc.hs.ReadTimeout > 0 {
+	if sc.hs.ReadTimeout != 0 {
 		sc.conn.SetReadDeadline(time.Time{})
 		st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
 	}
@@ -2039,7 +2038,7 @@ func (sc *serverConn) upgradeRequest(req *http.Request) {
 
 	// Disable any read deadline set by the net/http package
 	// prior to the upgrade.
-	if sc.hs.ReadTimeout > 0 {
+	if sc.hs.ReadTimeout != 0 {
 		sc.conn.SetReadDeadline(time.Time{})
 	}
 
@@ -2117,7 +2116,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
 	st.flow.conn = &sc.flow // link to conn-level counter
 	st.flow.add(sc.initialStreamSendWindowSize)
 	st.inflow.init(sc.srv.initialStreamRecvWindowSize())
-	if sc.hs.WriteTimeout > 0 {
+	if sc.hs.WriteTimeout != 0 {
 		st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
 	}
 
diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go
index ce375c8c..df578b86 100644
--- a/vendor/golang.org/x/net/http2/transport.go
+++ b/vendor/golang.org/x/net/http2/transport.go
@@ -147,12 +147,6 @@ type Transport struct {
 	// waiting for their turn.
 	StrictMaxConcurrentStreams bool
 
-	// IdleConnTimeout is the maximum amount of time an idle
-	// (keep-alive) connection will remain idle before closing
-	// itself.
-	// Zero means no limit.
-	IdleConnTimeout time.Duration
-
 	// ReadIdleTimeout is the timeout after which a health check using ping
 	// frame will be carried out if no frame is received on the connection.
 	// Note that a ping response will is considered a received frame, so if
@@ -184,8 +178,6 @@ type Transport struct {
 
 	connPoolOnce  sync.Once
 	connPoolOrDef ClientConnPool // non-nil version of ConnPool
-
-	syncHooks *testSyncHooks
 }
 
 func (t *Transport) maxHeaderListSize() uint32 {
@@ -310,7 +302,7 @@ type ClientConn struct {
 	readerErr  error         // set before readerDone is closed
 
 	idleTimeout time.Duration // or 0 for never
-	idleTimer   timer
+	idleTimer   *time.Timer
 
 	mu              sync.Mutex // guards following
 	cond            *sync.Cond // hold mu; broadcast on flow/closed changes
@@ -352,60 +344,6 @@ type ClientConn struct {
 	werr error        // first write error that has occurred
 	hbuf bytes.Buffer // HPACK encoder writes into this
 	henc *hpack.Encoder
-
-	syncHooks *testSyncHooks // can be nil
-}
-
-// Hook points used for testing.
-// Outside of tests, cc.syncHooks is nil and these all have minimal implementations.
-// Inside tests, see the testSyncHooks function docs.
-
-// goRun starts a new goroutine.
-func (cc *ClientConn) goRun(f func()) {
-	if cc.syncHooks != nil {
-		cc.syncHooks.goRun(f)
-		return
-	}
-	go f()
-}
-
-// condBroadcast is cc.cond.Broadcast.
-func (cc *ClientConn) condBroadcast() {
-	if cc.syncHooks != nil {
-		cc.syncHooks.condBroadcast(cc.cond)
-	}
-	cc.cond.Broadcast()
-}
-
-// condWait is cc.cond.Wait.
-func (cc *ClientConn) condWait() {
-	if cc.syncHooks != nil {
-		cc.syncHooks.condWait(cc.cond)
-	}
-	cc.cond.Wait()
-}
-
-// newTimer creates a new time.Timer, or a synthetic timer in tests.
-func (cc *ClientConn) newTimer(d time.Duration) timer {
-	if cc.syncHooks != nil {
-		return cc.syncHooks.newTimer(d)
-	}
-	return newTimeTimer(d)
-}
-
-// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
-func (cc *ClientConn) afterFunc(d time.Duration, f func()) timer {
-	if cc.syncHooks != nil {
-		return cc.syncHooks.afterFunc(d, f)
-	}
-	return newTimeAfterFunc(d, f)
-}
-
-func (cc *ClientConn) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
-	if cc.syncHooks != nil {
-		return cc.syncHooks.contextWithTimeout(ctx, d)
-	}
-	return context.WithTimeout(ctx, d)
 }
 
 // clientStream is the state for a single HTTP/2 stream. One of these
@@ -487,7 +425,7 @@ func (cs *clientStream) abortStreamLocked(err error) {
 	// TODO(dneil): Clean up tests where cs.cc.cond is nil.
 	if cs.cc.cond != nil {
 		// Wake up writeRequestBody if it is waiting on flow control.
-		cs.cc.condBroadcast()
+		cs.cc.cond.Broadcast()
 	}
 }
 
@@ -497,7 +435,7 @@ func (cs *clientStream) abortRequestBodyWrite() {
 	defer cc.mu.Unlock()
 	if cs.reqBody != nil && cs.reqBodyClosed == nil {
 		cs.closeReqBodyLocked()
-		cc.condBroadcast()
+		cc.cond.Broadcast()
 	}
 }
 
@@ -507,10 +445,10 @@ func (cs *clientStream) closeReqBodyLocked() {
 	}
 	cs.reqBodyClosed = make(chan struct{})
 	reqBodyClosed := cs.reqBodyClosed
-	cs.cc.goRun(func() {
+	go func() {
 		cs.reqBody.Close()
 		close(reqBodyClosed)
-	})
+	}()
 }
 
 type stickyErrWriter struct {
@@ -599,6 +537,15 @@ func authorityAddr(scheme string, authority string) (addr string) {
 	return net.JoinHostPort(host, port)
 }
 
+var retryBackoffHook func(time.Duration) *time.Timer
+
+func backoffNewTimer(d time.Duration) *time.Timer {
+	if retryBackoffHook != nil {
+		return retryBackoffHook(d)
+	}
+	return time.NewTimer(d)
+}
+
 // RoundTripOpt is like RoundTrip, but takes options.
 func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
 	if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
@@ -626,27 +573,13 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
 				backoff := float64(uint(1) << (uint(retry) - 1))
 				backoff += backoff * (0.1 * mathrand.Float64())
 				d := time.Second * time.Duration(backoff)
-				var tm timer
-				if t.syncHooks != nil {
-					tm = t.syncHooks.newTimer(d)
-					t.syncHooks.blockUntil(func() bool {
-						select {
-						case <-tm.C():
-						case <-req.Context().Done():
-						default:
-							return false
-						}
-						return true
-					})
-				} else {
-					tm = newTimeTimer(d)
-				}
+				timer := backoffNewTimer(d)
 				select {
-				case <-tm.C():
+				case <-timer.C:
 					t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
 					continue
 				case <-req.Context().Done():
-					tm.Stop()
+					timer.Stop()
 					err = req.Context().Err()
 				}
 			}
@@ -725,9 +658,6 @@ func canRetryError(err error) bool {
 }
 
 func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
-	if t.syncHooks != nil {
-		return t.newClientConn(nil, singleUse, t.syncHooks)
-	}
 	host, _, err := net.SplitHostPort(addr)
 	if err != nil {
 		return nil, err
@@ -736,7 +666,7 @@ func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse b
 	if err != nil {
 		return nil, err
 	}
-	return t.newClientConn(tconn, singleUse, nil)
+	return t.newClientConn(tconn, singleUse)
 }
 
 func (t *Transport) newTLSConfig(host string) *tls.Config {
@@ -802,10 +732,10 @@ func (t *Transport) maxEncoderHeaderTableSize() uint32 {
 }
 
 func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
-	return t.newClientConn(c, t.disableKeepAlives(), nil)
+	return t.newClientConn(c, t.disableKeepAlives())
 }
 
-func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHooks) (*ClientConn, error) {
+func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
 	cc := &ClientConn{
 		t:                     t,
 		tconn:                 c,
@@ -820,15 +750,10 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHoo
 		wantSettingsAck:       true,
 		pings:                 make(map[[8]byte]chan struct{}),
 		reqHeaderMu:           make(chan struct{}, 1),
-		syncHooks:             hooks,
-	}
-	if hooks != nil {
-		hooks.newclientconn(cc)
-		c = cc.tconn
 	}
 	if d := t.idleConnTimeout(); d != 0 {
 		cc.idleTimeout = d
-		cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
+		cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout)
 	}
 	if VerboseLogs {
 		t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
@@ -893,7 +818,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHoo
 		return nil, cc.werr
 	}
 
-	cc.goRun(cc.readLoop)
+	go cc.readLoop()
 	return cc, nil
 }
 
@@ -901,7 +826,7 @@ func (cc *ClientConn) healthCheck() {
 	pingTimeout := cc.t.pingTimeout()
 	// We don't need to periodically ping in the health check, because the readLoop of ClientConn will
 	// trigger the healthCheck again if there is no frame received.
-	ctx, cancel := cc.contextWithTimeout(context.Background(), pingTimeout)
+	ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
 	defer cancel()
 	cc.vlogf("http2: Transport sending health check")
 	err := cc.Ping(ctx)
@@ -1131,7 +1056,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
 	// Wait for all in-flight streams to complete or connection to close
 	done := make(chan struct{})
 	cancelled := false // guarded by cc.mu
-	cc.goRun(func() {
+	go func() {
 		cc.mu.Lock()
 		defer cc.mu.Unlock()
 		for {
@@ -1143,9 +1068,9 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
 			if cancelled {
 				break
 			}
-			cc.condWait()
+			cc.cond.Wait()
 		}
-	})
+	}()
 	shutdownEnterWaitStateHook()
 	select {
 	case <-done:
@@ -1155,7 +1080,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
 		cc.mu.Lock()
 		// Free the goroutine above
 		cancelled = true
-		cc.condBroadcast()
+		cc.cond.Broadcast()
 		cc.mu.Unlock()
 		return ctx.Err()
 	}
@@ -1193,7 +1118,7 @@ func (cc *ClientConn) closeForError(err error) {
 	for _, cs := range cc.streams {
 		cs.abortStreamLocked(err)
 	}
-	cc.condBroadcast()
+	cc.cond.Broadcast()
 	cc.mu.Unlock()
 	cc.closeConn()
 }
@@ -1290,10 +1215,6 @@ func (cc *ClientConn) decrStreamReservationsLocked() {
 }
 
 func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
-	return cc.roundTrip(req, nil)
-}
-
-func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
 	ctx := req.Context()
 	cs := &clientStream{
 		cc:                   cc,
@@ -1308,23 +1229,9 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
 		respHeaderRecv:       make(chan struct{}),
 		donec:                make(chan struct{}),
 	}
-	cc.goRun(func() {
-		cs.doRequest(req)
-	})
+	go cs.doRequest(req)
 
 	waitDone := func() error {
-		if cc.syncHooks != nil {
-			cc.syncHooks.blockUntil(func() bool {
-				select {
-				case <-cs.donec:
-				case <-ctx.Done():
-				case <-cs.reqCancel:
-				default:
-					return false
-				}
-				return true
-			})
-		}
 		select {
 		case <-cs.donec:
 			return nil
@@ -1385,24 +1292,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
 		return err
 	}
 
-	if streamf != nil {
-		streamf(cs)
-	}
-
 	for {
-		if cc.syncHooks != nil {
-			cc.syncHooks.blockUntil(func() bool {
-				select {
-				case <-cs.respHeaderRecv:
-				case <-cs.abort:
-				case <-ctx.Done():
-				case <-cs.reqCancel:
-				default:
-					return false
-				}
-				return true
-			})
-		}
 		select {
 		case <-cs.respHeaderRecv:
 			return handleResponseHeaders()
@@ -1458,21 +1348,6 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
 	if cc.reqHeaderMu == nil {
 		panic("RoundTrip on uninitialized ClientConn") // for tests
 	}
-	var newStreamHook func(*clientStream)
-	if cc.syncHooks != nil {
-		newStreamHook = cc.syncHooks.newstream
-		cc.syncHooks.blockUntil(func() bool {
-			select {
-			case cc.reqHeaderMu <- struct{}{}:
-				<-cc.reqHeaderMu
-			case <-cs.reqCancel:
-			case <-ctx.Done():
-			default:
-				return false
-			}
-			return true
-		})
-	}
 	select {
 	case cc.reqHeaderMu <- struct{}{}:
 	case <-cs.reqCancel:
@@ -1497,10 +1372,6 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
 	}
 	cc.mu.Unlock()
 
-	if newStreamHook != nil {
-		newStreamHook(cs)
-	}
-
 	// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
 	if !cc.t.disableCompression() &&
 		req.Header.Get("Accept-Encoding") == "" &&
@@ -1581,30 +1452,15 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
 	var respHeaderTimer <-chan time.Time
 	var respHeaderRecv chan struct{}
 	if d := cc.responseHeaderTimeout(); d != 0 {
-		timer := cc.newTimer(d)
+		timer := time.NewTimer(d)
 		defer timer.Stop()
-		respHeaderTimer = timer.C()
+		respHeaderTimer = timer.C
 		respHeaderRecv = cs.respHeaderRecv
 	}
 	// Wait until the peer half-closes its end of the stream,
 	// or until the request is aborted (via context, error, or otherwise),
 	// whichever comes first.
 	for {
-		if cc.syncHooks != nil {
-			cc.syncHooks.blockUntil(func() bool {
-				select {
-				case <-cs.peerClosed:
-				case <-respHeaderTimer:
-				case <-respHeaderRecv:
-				case <-cs.abort:
-				case <-ctx.Done():
-				case <-cs.reqCancel:
-				default:
-					return false
-				}
-				return true
-			})
-		}
 		select {
 		case <-cs.peerClosed:
 			return nil
@@ -1753,7 +1609,7 @@ func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
 			return nil
 		}
 		cc.pendingRequests++
-		cc.condWait()
+		cc.cond.Wait()
 		cc.pendingRequests--
 		select {
 		case <-cs.abort:
@@ -2015,24 +1871,8 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
 			cs.flow.take(take)
 			return take, nil
 		}
-		cc.condWait()
-	}
-}
-
-func validateHeaders(hdrs http.Header) string {
-	for k, vv := range hdrs {
-		if !httpguts.ValidHeaderFieldName(k) {
-			return fmt.Sprintf("name %q", k)
-		}
-		for _, v := range vv {
-			if !httpguts.ValidHeaderFieldValue(v) {
-				// Don't include the value in the error,
-				// because it may be sensitive.
-				return fmt.Sprintf("value for header %q", k)
-			}
-		}
+		cc.cond.Wait()
 	}
-	return ""
 }
 
 var errNilRequestURL = errors.New("http2: Request.URI is nil")
@@ -2072,14 +1912,19 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
 		}
 	}
 
-	// Check for any invalid headers+trailers and return an error before we
+	// Check for any invalid headers and return an error before we
 	// potentially pollute our hpack state. (We want to be able to
 	// continue to reuse the hpack encoder for future requests)
-	if err := validateHeaders(req.Header); err != "" {
-		return nil, fmt.Errorf("invalid HTTP header %s", err)
-	}
-	if err := validateHeaders(req.Trailer); err != "" {
-		return nil, fmt.Errorf("invalid HTTP trailer %s", err)
+	for k, vv := range req.Header {
+		if !httpguts.ValidHeaderFieldName(k) {
+			return nil, fmt.Errorf("invalid HTTP header name %q", k)
+		}
+		for _, v := range vv {
+			if !httpguts.ValidHeaderFieldValue(v) {
+				// Don't include the value in the error, because it may be sensitive.
+				return nil, fmt.Errorf("invalid HTTP header value for header %q", k)
+			}
+		}
 	}
 
 	enumerateHeaders := func(f func(name, value string)) {
@@ -2298,7 +2143,7 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
 	}
 	// Wake up writeRequestBody via clientStream.awaitFlowControl and
 	// wake up RoundTrip if there is a pending request.
-	cc.condBroadcast()
+	cc.cond.Broadcast()
 
 	closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
 	if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 {
@@ -2386,7 +2231,7 @@ func (rl *clientConnReadLoop) cleanup() {
 			cs.abortStreamLocked(err)
 		}
 	}
-	cc.condBroadcast()
+	cc.cond.Broadcast()
 	cc.mu.Unlock()
 }
 
@@ -2421,9 +2266,10 @@ func (rl *clientConnReadLoop) run() error {
 	cc := rl.cc
 	gotSettings := false
 	readIdleTimeout := cc.t.ReadIdleTimeout
-	var t timer
+	var t *time.Timer
 	if readIdleTimeout != 0 {
-		t = cc.afterFunc(readIdleTimeout, cc.healthCheck)
+		t = time.AfterFunc(readIdleTimeout, cc.healthCheck)
+		defer t.Stop()
 	}
 	for {
 		f, err := cc.fr.ReadFrame()
@@ -2838,7 +2684,7 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
 		})
 		return nil
 	}
-	if !cs.pastHeaders {
+	if !cs.firstByte {
 		cc.logf("protocol error: received DATA before a HEADERS frame")
 		rl.endStreamError(cs, StreamError{
 			StreamID: f.StreamID,
@@ -3021,7 +2867,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
 			for _, cs := range cc.streams {
 				cs.flow.add(delta)
 			}
-			cc.condBroadcast()
+			cc.cond.Broadcast()
 
 			cc.initialWindowSize = s.Val
 		case SettingHeaderTableSize:
@@ -3065,18 +2911,9 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
 		fl = &cs.flow
 	}
 	if !fl.add(int32(f.Increment)) {
-		// For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR
-		if cs != nil {
-			rl.endStreamError(cs, StreamError{
-				StreamID: f.StreamID,
-				Code:     ErrCodeFlowControl,
-			})
-			return nil
-		}
-
 		return ConnectionError(ErrCodeFlowControl)
 	}
-	cc.condBroadcast()
+	cc.cond.Broadcast()
 	return nil
 }
 
@@ -3118,38 +2955,24 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
 		}
 		cc.mu.Unlock()
 	}
-	var pingError error
-	errc := make(chan struct{})
-	cc.goRun(func() {
+	errc := make(chan error, 1)
+	go func() {
 		cc.wmu.Lock()
 		defer cc.wmu.Unlock()
-		if pingError = cc.fr.WritePing(false, p); pingError != nil {
-			close(errc)
+		if err := cc.fr.WritePing(false, p); err != nil {
+			errc <- err
 			return
 		}
-		if pingError = cc.bw.Flush(); pingError != nil {
-			close(errc)
+		if err := cc.bw.Flush(); err != nil {
+			errc <- err
 			return
 		}
-	})
-	if cc.syncHooks != nil {
-		cc.syncHooks.blockUntil(func() bool {
-			select {
-			case <-c:
-			case <-errc:
-			case <-ctx.Done():
-			case <-cc.readerDone:
-			default:
-				return false
-			}
-			return true
-		})
-	}
+	}()
 	select {
 	case <-c:
 		return nil
-	case <-errc:
-		return pingError
+	case err := <-errc:
+		return err
 	case <-ctx.Done():
 		return ctx.Err()
 	case <-cc.readerDone:
@@ -3318,17 +3141,9 @@ func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, err
 }
 
 func (t *Transport) idleConnTimeout() time.Duration {
-	// to keep things backwards compatible, we use non-zero values of
-	// IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
-	// http1 transport, followed by 0
-	if t.IdleConnTimeout != 0 {
-		return t.IdleConnTimeout
-	}
-
 	if t.t1 != nil {
 		return t.t1.IdleConnTimeout
 	}
-
 	return 0
 }
 
diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go
deleted file mode 100644
index b18efb74..00000000
--- a/vendor/golang.org/x/sync/errgroup/errgroup.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2016 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package errgroup provides synchronization, error propagation, and Context
-// cancelation for groups of goroutines working on subtasks of a common task.
-package errgroup
-
-import (
-	"context"
-	"fmt"
-	"sync"
-)
-
-type token struct{}
-
-// A Group is a collection of goroutines working on subtasks that are part of
-// the same overall task.
-//
-// A zero Group is valid, has no limit on the number of active goroutines,
-// and does not cancel on error.
-type Group struct {
-	cancel func(error)
-
-	wg sync.WaitGroup
-
-	sem chan token
-
-	errOnce sync.Once
-	err     error
-}
-
-func (g *Group) done() {
-	if g.sem != nil {
-		<-g.sem
-	}
-	g.wg.Done()
-}
-
-// WithContext returns a new Group and an associated Context derived from ctx.
-//
-// The derived Context is canceled the first time a function passed to Go
-// returns a non-nil error or the first time Wait returns, whichever occurs
-// first.
-func WithContext(ctx context.Context) (*Group, context.Context) {
-	ctx, cancel := withCancelCause(ctx)
-	return &Group{cancel: cancel}, ctx
-}
-
-// Wait blocks until all function calls from the Go method have returned, then
-// returns the first non-nil error (if any) from them.
-func (g *Group) Wait() error {
-	g.wg.Wait()
-	if g.cancel != nil {
-		g.cancel(g.err)
-	}
-	return g.err
-}
-
-// Go calls the given function in a new goroutine.
-// It blocks until the new goroutine can be added without the number of
-// active goroutines in the group exceeding the configured limit.
-//
-// The first call to return a non-nil error cancels the group's context, if the
-// group was created by calling WithContext. The error will be returned by Wait.
-func (g *Group) Go(f func() error) {
-	if g.sem != nil {
-		g.sem <- token{}
-	}
-
-	g.wg.Add(1)
-	go func() {
-		defer g.done()
-
-		if err := f(); err != nil {
-			g.errOnce.Do(func() {
-				g.err = err
-				if g.cancel != nil {
-					g.cancel(g.err)
-				}
-			})
-		}
-	}()
-}
-
-// TryGo calls the given function in a new goroutine only if the number of
-// active goroutines in the group is currently below the configured limit.
-//
-// The return value reports whether the goroutine was started.
-func (g *Group) TryGo(f func() error) bool {
-	if g.sem != nil {
-		select {
-		case g.sem <- token{}:
-			// Note: this allows barging iff channels in general allow barging.
-		default:
-			return false
-		}
-	}
-
-	g.wg.Add(1)
-	go func() {
-		defer g.done()
-
-		if err := f(); err != nil {
-			g.errOnce.Do(func() {
-				g.err = err
-				if g.cancel != nil {
-					g.cancel(g.err)
-				}
-			})
-		}
-	}()
-	return true
-}
-
-// SetLimit limits the number of active goroutines in this group to at most n.
-// A negative value indicates no limit.
-//
-// Any subsequent call to the Go method will block until it can add an active
-// goroutine without exceeding the configured limit.
-//
-// The limit must not be modified while any goroutines in the group are active.
-func (g *Group) SetLimit(n int) {
-	if n < 0 {
-		g.sem = nil
-		return
-	}
-	if len(g.sem) != 0 {
-		panic(fmt.Errorf("errgroup: modify limit while %v goroutines in the group are still active", len(g.sem)))
-	}
-	g.sem = make(chan token, n)
-}
diff --git a/vendor/golang.org/x/sync/errgroup/go120.go b/vendor/golang.org/x/sync/errgroup/go120.go
deleted file mode 100644
index 7d419d37..00000000
--- a/vendor/golang.org/x/sync/errgroup/go120.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.20
-// +build go1.20
-
-package errgroup
-
-import "context"
-
-func withCancelCause(parent context.Context) (context.Context, func(error)) {
-	return context.WithCancelCause(parent)
-}
diff --git a/vendor/golang.org/x/sync/errgroup/pre_go120.go b/vendor/golang.org/x/sync/errgroup/pre_go120.go
deleted file mode 100644
index 1795c18a..00000000
--- a/vendor/golang.org/x/sync/errgroup/pre_go120.go
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.20
-// +build !go1.20
-
-package errgroup
-
-import "context"
-
-func withCancelCause(parent context.Context) (context.Context, func(error)) {
-	ctx, cancel := context.WithCancel(parent)
-	return ctx, func(error) { cancel() }
-}
diff --git a/vendor/golang.org/x/sys/unix/aliases.go b/vendor/golang.org/x/sys/unix/aliases.go
index b0e41985..e7d3df4b 100644
--- a/vendor/golang.org/x/sys/unix/aliases.go
+++ b/vendor/golang.org/x/sys/unix/aliases.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
+//go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) && go1.9
 
 package unix
 
diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go b/vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go
index 2f0fa76e..16dc6993 100644
--- a/vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go
+++ b/vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build darwin
+//go:build darwin && go1.12
 
 package unix
 
diff --git a/vendor/golang.org/x/sys/unix/syscall_freebsd.go b/vendor/golang.org/x/sys/unix/syscall_freebsd.go
index 2b57e0f7..64d1bb4d 100644
--- a/vendor/golang.org/x/sys/unix/syscall_freebsd.go
+++ b/vendor/golang.org/x/sys/unix/syscall_freebsd.go
@@ -13,7 +13,6 @@
 package unix
 
 import (
-	"errors"
 	"sync"
 	"unsafe"
 )
@@ -170,26 +169,25 @@ func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
 func Uname(uname *Utsname) error {
 	mib := []_C_int{CTL_KERN, KERN_OSTYPE}
 	n := unsafe.Sizeof(uname.Sysname)
-	// Suppress ENOMEM errors to be compatible with the C library __xuname() implementation.
-	if err := sysctl(mib, &uname.Sysname[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
+	if err := sysctl(mib, &uname.Sysname[0], &n, nil, 0); err != nil {
 		return err
 	}
 
 	mib = []_C_int{CTL_KERN, KERN_HOSTNAME}
 	n = unsafe.Sizeof(uname.Nodename)
-	if err := sysctl(mib, &uname.Nodename[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
+	if err := sysctl(mib, &uname.Nodename[0], &n, nil, 0); err != nil {
 		return err
 	}
 
 	mib = []_C_int{CTL_KERN, KERN_OSRELEASE}
 	n = unsafe.Sizeof(uname.Release)
-	if err := sysctl(mib, &uname.Release[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
+	if err := sysctl(mib, &uname.Release[0], &n, nil, 0); err != nil {
 		return err
 	}
 
 	mib = []_C_int{CTL_KERN, KERN_VERSION}
 	n = unsafe.Sizeof(uname.Version)
-	if err := sysctl(mib, &uname.Version[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
+	if err := sysctl(mib, &uname.Version[0], &n, nil, 0); err != nil {
 		return err
 	}
 
@@ -207,7 +205,7 @@ func Uname(uname *Utsname) error {
 
 	mib = []_C_int{CTL_HW, HW_MACHINE}
 	n = unsafe.Sizeof(uname.Machine)
-	if err := sysctl(mib, &uname.Machine[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) {
+	if err := sysctl(mib, &uname.Machine[0], &n, nil, 0); err != nil {
 		return err
 	}
 
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go
index 5682e262..0f85e29e 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux.go
@@ -1849,105 +1849,6 @@ func Dup2(oldfd, newfd int) error {
 //sys	Fsmount(fd int, flags int, mountAttrs int) (fsfd int, err error)
 //sys	Fsopen(fsName string, flags int) (fd int, err error)
 //sys	Fspick(dirfd int, pathName string, flags int) (fd int, err error)
-
-//sys	fsconfig(fd int, cmd uint, key *byte, value *byte, aux int) (err error)
-
-func fsconfigCommon(fd int, cmd uint, key string, value *byte, aux int) (err error) {
-	var keyp *byte
-	if keyp, err = BytePtrFromString(key); err != nil {
-		return
-	}
-	return fsconfig(fd, cmd, keyp, value, aux)
-}
-
-// FsconfigSetFlag is equivalent to fsconfig(2) called
-// with cmd == FSCONFIG_SET_FLAG.
-//
-// fd is the filesystem context to act upon.
-// key the parameter key to set.
-func FsconfigSetFlag(fd int, key string) (err error) {
-	return fsconfigCommon(fd, FSCONFIG_SET_FLAG, key, nil, 0)
-}
-
-// FsconfigSetString is equivalent to fsconfig(2) called
-// with cmd == FSCONFIG_SET_STRING.
-//
-// fd is the filesystem context to act upon.
-// key the parameter key to set.
-// value is the parameter value to set.
-func FsconfigSetString(fd int, key string, value string) (err error) {
-	var valuep *byte
-	if valuep, err = BytePtrFromString(value); err != nil {
-		return
-	}
-	return fsconfigCommon(fd, FSCONFIG_SET_STRING, key, valuep, 0)
-}
-
-// FsconfigSetBinary is equivalent to fsconfig(2) called
-// with cmd == FSCONFIG_SET_BINARY.
-//
-// fd is the filesystem context to act upon.
-// key the parameter key to set.
-// value is the parameter value to set.
-func FsconfigSetBinary(fd int, key string, value []byte) (err error) {
-	if len(value) == 0 {
-		return EINVAL
-	}
-	return fsconfigCommon(fd, FSCONFIG_SET_BINARY, key, &value[0], len(value))
-}
-
-// FsconfigSetPath is equivalent to fsconfig(2) called
-// with cmd == FSCONFIG_SET_PATH.
-//
-// fd is the filesystem context to act upon.
-// key the parameter key to set.
-// path is a non-empty path for specified key.
-// atfd is a file descriptor at which to start lookup from or AT_FDCWD.
-func FsconfigSetPath(fd int, key string, path string, atfd int) (err error) {
-	var valuep *byte
-	if valuep, err = BytePtrFromString(path); err != nil {
-		return
-	}
-	return fsconfigCommon(fd, FSCONFIG_SET_PATH, key, valuep, atfd)
-}
-
-// FsconfigSetPathEmpty is equivalent to fsconfig(2) called
-// with cmd == FSCONFIG_SET_PATH_EMPTY. The same as
-// FconfigSetPath but with AT_PATH_EMPTY implied.
-func FsconfigSetPathEmpty(fd int, key string, path string, atfd int) (err error) {
-	var valuep *byte
-	if valuep, err = BytePtrFromString(path); err != nil {
-		return
-	}
-	return fsconfigCommon(fd, FSCONFIG_SET_PATH_EMPTY, key, valuep, atfd)
-}
-
-// FsconfigSetFd is equivalent to fsconfig(2) called
-// with cmd == FSCONFIG_SET_FD.
-//
-// fd is the filesystem context to act upon.
-// key the parameter key to set.
-// value is a file descriptor to be assigned to specified key.
-func FsconfigSetFd(fd int, key string, value int) (err error) {
-	return fsconfigCommon(fd, FSCONFIG_SET_FD, key, nil, value)
-}
-
-// FsconfigCreate is equivalent to fsconfig(2) called
-// with cmd == FSCONFIG_CMD_CREATE.
-//
-// fd is the filesystem context to act upon.
-func FsconfigCreate(fd int) (err error) {
-	return fsconfig(fd, FSCONFIG_CMD_CREATE, nil, nil, 0)
-}
-
-// FsconfigReconfigure is equivalent to fsconfig(2) called
-// with cmd == FSCONFIG_CMD_RECONFIGURE.
-//
-// fd is the filesystem context to act upon.
-func FsconfigReconfigure(fd int) (err error) {
-	return fsconfig(fd, FSCONFIG_CMD_RECONFIGURE, nil, nil, 0)
-}
-
 //sys	Getdents(fd int, buf []byte) (n int, err error) = SYS_GETDENTS64
 //sysnb	Getpgid(pid int) (pgid int, err error)
 
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go
index 87d8612a..1488d271 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go
@@ -906,16 +906,6 @@ func Fspick(dirfd int, pathName string, flags int) (fd int, err error) {
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
-func fsconfig(fd int, cmd uint, key *byte, value *byte, aux int) (err error) {
-	_, _, e1 := Syscall6(SYS_FSCONFIG, uintptr(fd), uintptr(cmd), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(value)), uintptr(aux), 0)
-	if e1 != 0 {
-		err = errnoErr(e1)
-	}
-	return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
 func Getdents(fd int, buf []byte) (n int, err error) {
 	var _p0 unsafe.Pointer
 	if len(buf) > 0 {
diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go
index eff6bcde..dc0c955e 100644
--- a/vendor/golang.org/x/sys/unix/ztypes_linux.go
+++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go
@@ -836,15 +836,6 @@ const (
 	FSPICK_EMPTY_PATH       = 0x8
 
 	FSMOUNT_CLOEXEC = 0x1
-
-	FSCONFIG_SET_FLAG        = 0x0
-	FSCONFIG_SET_STRING      = 0x1
-	FSCONFIG_SET_BINARY      = 0x2
-	FSCONFIG_SET_PATH        = 0x3
-	FSCONFIG_SET_PATH_EMPTY  = 0x4
-	FSCONFIG_SET_FD          = 0x5
-	FSCONFIG_CMD_CREATE      = 0x6
-	FSCONFIG_CMD_RECONFIGURE = 0x7
 )
 
 type OpenHow struct {
@@ -1559,7 +1550,6 @@ const (
 	IFLA_DEVLINK_PORT                          = 0x3e
 	IFLA_GSO_IPV4_MAX_SIZE                     = 0x3f
 	IFLA_GRO_IPV4_MAX_SIZE                     = 0x40
-	IFLA_DPLL_PIN                              = 0x41
 	IFLA_PROTO_DOWN_REASON_UNSPEC              = 0x0
 	IFLA_PROTO_DOWN_REASON_MASK                = 0x1
 	IFLA_PROTO_DOWN_REASON_VALUE               = 0x2
@@ -1575,7 +1565,6 @@ const (
 	IFLA_INET6_ICMP6STATS                      = 0x6
 	IFLA_INET6_TOKEN                           = 0x7
 	IFLA_INET6_ADDR_GEN_MODE                   = 0x8
-	IFLA_INET6_RA_MTU                          = 0x9
 	IFLA_BR_UNSPEC                             = 0x0
 	IFLA_BR_FORWARD_DELAY                      = 0x1
 	IFLA_BR_HELLO_TIME                         = 0x2
@@ -1623,9 +1612,6 @@ const (
 	IFLA_BR_MCAST_MLD_VERSION                  = 0x2c
 	IFLA_BR_VLAN_STATS_PER_PORT                = 0x2d
 	IFLA_BR_MULTI_BOOLOPT                      = 0x2e
-	IFLA_BR_MCAST_QUERIER_STATE                = 0x2f
-	IFLA_BR_FDB_N_LEARNED                      = 0x30
-	IFLA_BR_FDB_MAX_LEARNED                    = 0x31
 	IFLA_BRPORT_UNSPEC                         = 0x0
 	IFLA_BRPORT_STATE                          = 0x1
 	IFLA_BRPORT_PRIORITY                       = 0x2
@@ -1663,14 +1649,6 @@ const (
 	IFLA_BRPORT_BACKUP_PORT                    = 0x22
 	IFLA_BRPORT_MRP_RING_OPEN                  = 0x23
 	IFLA_BRPORT_MRP_IN_OPEN                    = 0x24
-	IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT          = 0x25
-	IFLA_BRPORT_MCAST_EHT_HOSTS_CNT            = 0x26
-	IFLA_BRPORT_LOCKED                         = 0x27
-	IFLA_BRPORT_MAB                            = 0x28
-	IFLA_BRPORT_MCAST_N_GROUPS                 = 0x29
-	IFLA_BRPORT_MCAST_MAX_GROUPS               = 0x2a
-	IFLA_BRPORT_NEIGH_VLAN_SUPPRESS            = 0x2b
-	IFLA_BRPORT_BACKUP_NHID                    = 0x2c
 	IFLA_INFO_UNSPEC                           = 0x0
 	IFLA_INFO_KIND                             = 0x1
 	IFLA_INFO_DATA                             = 0x2
@@ -1692,9 +1670,6 @@ const (
 	IFLA_MACVLAN_MACADDR                       = 0x4
 	IFLA_MACVLAN_MACADDR_DATA                  = 0x5
 	IFLA_MACVLAN_MACADDR_COUNT                 = 0x6
-	IFLA_MACVLAN_BC_QUEUE_LEN                  = 0x7
-	IFLA_MACVLAN_BC_QUEUE_LEN_USED             = 0x8
-	IFLA_MACVLAN_BC_CUTOFF                     = 0x9
 	IFLA_VRF_UNSPEC                            = 0x0
 	IFLA_VRF_TABLE                             = 0x1
 	IFLA_VRF_PORT_UNSPEC                       = 0x0
@@ -1718,22 +1693,9 @@ const (
 	IFLA_XFRM_UNSPEC                           = 0x0
 	IFLA_XFRM_LINK                             = 0x1
 	IFLA_XFRM_IF_ID                            = 0x2
-	IFLA_XFRM_COLLECT_METADATA                 = 0x3
 	IFLA_IPVLAN_UNSPEC                         = 0x0
 	IFLA_IPVLAN_MODE                           = 0x1
 	IFLA_IPVLAN_FLAGS                          = 0x2
-	NETKIT_NEXT                                = -0x1
-	NETKIT_PASS                                = 0x0
-	NETKIT_DROP                                = 0x2
-	NETKIT_REDIRECT                            = 0x7
-	NETKIT_L2                                  = 0x0
-	NETKIT_L3                                  = 0x1
-	IFLA_NETKIT_UNSPEC                         = 0x0
-	IFLA_NETKIT_PEER_INFO                      = 0x1
-	IFLA_NETKIT_PRIMARY                        = 0x2
-	IFLA_NETKIT_POLICY                         = 0x3
-	IFLA_NETKIT_PEER_POLICY                    = 0x4
-	IFLA_NETKIT_MODE                           = 0x5
 	IFLA_VXLAN_UNSPEC                          = 0x0
 	IFLA_VXLAN_ID                              = 0x1
 	IFLA_VXLAN_GROUP                           = 0x2
@@ -1764,8 +1726,6 @@ const (
 	IFLA_VXLAN_GPE                             = 0x1b
 	IFLA_VXLAN_TTL_INHERIT                     = 0x1c
 	IFLA_VXLAN_DF                              = 0x1d
-	IFLA_VXLAN_VNIFILTER                       = 0x1e
-	IFLA_VXLAN_LOCALBYPASS                     = 0x1f
 	IFLA_GENEVE_UNSPEC                         = 0x0
 	IFLA_GENEVE_ID                             = 0x1
 	IFLA_GENEVE_REMOTE                         = 0x2
@@ -1780,7 +1740,6 @@ const (
 	IFLA_GENEVE_LABEL                          = 0xb
 	IFLA_GENEVE_TTL_INHERIT                    = 0xc
 	IFLA_GENEVE_DF                             = 0xd
-	IFLA_GENEVE_INNER_PROTO_INHERIT            = 0xe
 	IFLA_BAREUDP_UNSPEC                        = 0x0
 	IFLA_BAREUDP_PORT                          = 0x1
 	IFLA_BAREUDP_ETHERTYPE                     = 0x2
@@ -1793,8 +1752,6 @@ const (
 	IFLA_GTP_FD1                               = 0x2
 	IFLA_GTP_PDP_HASHSIZE                      = 0x3
 	IFLA_GTP_ROLE                              = 0x4
-	IFLA_GTP_CREATE_SOCKETS                    = 0x5
-	IFLA_GTP_RESTART_COUNT                     = 0x6
 	IFLA_BOND_UNSPEC                           = 0x0
 	IFLA_BOND_MODE                             = 0x1
 	IFLA_BOND_ACTIVE_SLAVE                     = 0x2
@@ -1824,9 +1781,6 @@ const (
 	IFLA_BOND_AD_ACTOR_SYSTEM                  = 0x1a
 	IFLA_BOND_TLB_DYNAMIC_LB                   = 0x1b
 	IFLA_BOND_PEER_NOTIF_DELAY                 = 0x1c
-	IFLA_BOND_AD_LACP_ACTIVE                   = 0x1d
-	IFLA_BOND_MISSED_MAX                       = 0x1e
-	IFLA_BOND_NS_IP6_TARGET                    = 0x1f
 	IFLA_BOND_AD_INFO_UNSPEC                   = 0x0
 	IFLA_BOND_AD_INFO_AGGREGATOR               = 0x1
 	IFLA_BOND_AD_INFO_NUM_PORTS                = 0x2
@@ -1842,7 +1796,6 @@ const (
 	IFLA_BOND_SLAVE_AD_AGGREGATOR_ID           = 0x6
 	IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE   = 0x7
 	IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE = 0x8
-	IFLA_BOND_SLAVE_PRIO                       = 0x9
 	IFLA_VF_INFO_UNSPEC                        = 0x0
 	IFLA_VF_INFO                               = 0x1
 	IFLA_VF_UNSPEC                             = 0x0
@@ -1901,16 +1854,8 @@ const (
 	IFLA_STATS_LINK_XSTATS_SLAVE               = 0x3
 	IFLA_STATS_LINK_OFFLOAD_XSTATS             = 0x4
 	IFLA_STATS_AF_SPEC                         = 0x5
-	IFLA_STATS_GETSET_UNSPEC                   = 0x0
-	IFLA_STATS_GET_FILTERS                     = 0x1
-	IFLA_STATS_SET_OFFLOAD_XSTATS_L3_STATS     = 0x2
 	IFLA_OFFLOAD_XSTATS_UNSPEC                 = 0x0
 	IFLA_OFFLOAD_XSTATS_CPU_HIT                = 0x1
-	IFLA_OFFLOAD_XSTATS_HW_S_INFO              = 0x2
-	IFLA_OFFLOAD_XSTATS_L3_STATS               = 0x3
-	IFLA_OFFLOAD_XSTATS_HW_S_INFO_UNSPEC       = 0x0
-	IFLA_OFFLOAD_XSTATS_HW_S_INFO_REQUEST      = 0x1
-	IFLA_OFFLOAD_XSTATS_HW_S_INFO_USED         = 0x2
 	IFLA_XDP_UNSPEC                            = 0x0
 	IFLA_XDP_FD                                = 0x1
 	IFLA_XDP_ATTACHED                          = 0x2
@@ -1940,11 +1885,6 @@ const (
 	IFLA_RMNET_UNSPEC                          = 0x0
 	IFLA_RMNET_MUX_ID                          = 0x1
 	IFLA_RMNET_FLAGS                           = 0x2
-	IFLA_MCTP_UNSPEC                           = 0x0
-	IFLA_MCTP_NET                              = 0x1
-	IFLA_DSA_UNSPEC                            = 0x0
-	IFLA_DSA_CONDUIT                           = 0x1
-	IFLA_DSA_MASTER                            = 0x1
 )
 
 const (
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 11b05840..66ce90f6 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -181,12 +181,12 @@ github.com/docker/go-metrics
 # github.com/felixge/httpsnoop v1.0.4
 ## explicit; go 1.13
 github.com/felixge/httpsnoop
-# github.com/go-jose/go-jose/v4 v4.0.2
-## explicit; go 1.21
-github.com/go-jose/go-jose/v4
-github.com/go-jose/go-jose/v4/cipher
-github.com/go-jose/go-jose/v4/json
-github.com/go-jose/go-jose/v4/jwt
+# github.com/go-jose/go-jose/v3 v3.0.3
+## explicit; go 1.12
+github.com/go-jose/go-jose/v3
+github.com/go-jose/go-jose/v3/cipher
+github.com/go-jose/go-jose/v3/json
+github.com/go-jose/go-jose/v3/jwt
 # github.com/go-logr/logr v1.3.0
 ## explicit; go 1.18
 github.com/go-logr/logr
@@ -467,7 +467,7 @@ go.opentelemetry.io/proto/otlp/common/v1
 go.opentelemetry.io/proto/otlp/metrics/v1
 go.opentelemetry.io/proto/otlp/resource/v1
 go.opentelemetry.io/proto/otlp/trace/v1
-# golang.org/x/crypto v0.21.0
+# golang.org/x/crypto v0.19.0
 ## explicit; go 1.18
 golang.org/x/crypto/acme
 golang.org/x/crypto/acme/autocert
@@ -483,7 +483,7 @@ golang.org/x/crypto/internal/poly1305
 golang.org/x/crypto/pbkdf2
 golang.org/x/crypto/pkcs12
 golang.org/x/crypto/pkcs12/internal/rc2
-# golang.org/x/net v0.23.0
+# golang.org/x/net v0.20.0
 ## explicit; go 1.18
 golang.org/x/net/context
 golang.org/x/net/http/httpguts
@@ -504,9 +504,8 @@ golang.org/x/oauth2/jws
 golang.org/x/oauth2/jwt
 # golang.org/x/sync v0.3.0
 ## explicit; go 1.17
-golang.org/x/sync/errgroup
 golang.org/x/sync/semaphore
-# golang.org/x/sys v0.18.0
+# golang.org/x/sys v0.17.0
 ## explicit; go 1.18
 golang.org/x/sys/cpu
 golang.org/x/sys/unix


Test patch:
diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go
index 7e0a1e22..ba538802 100644
--- a/configuration/configuration_test.go
+++ b/configuration/configuration_test.go
@@ -8,6 +8,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/redis/go-redis/v9"
 	"github.com/stretchr/testify/suite"
 	"gopkg.in/yaml.v2"
 )
@@ -39,9 +40,6 @@ var configStruct = Configuration{
 			"url1":    "https://foo.example.com",
 			"path1":   "/some-path",
 		},
-		"tag": Parameters{
-			"concurrencylimit": 10,
-		},
 	},
 	Auth: Auth{
 		"silly": Parameters{
@@ -134,28 +132,22 @@ var configStruct = Configuration{
 		},
 	},
 	Redis: Redis{
-		Addr:     "localhost:6379",
-		Username: "alice",
-		Password: "123456",
-		DB:       1,
-		Pool: struct {
-			MaxIdle     int           `yaml:"maxidle,omitempty"`
-			MaxActive   int           `yaml:"maxactive,omitempty"`
-			IdleTimeout time.Duration `yaml:"idletimeout,omitempty"`
-		}{
-			MaxIdle:     16,
-			MaxActive:   64,
-			IdleTimeout: time.Second * 300,
+		Options: redis.UniversalOptions{
+			Addrs:           []string{"localhost:6379"},
+			Username:        "alice",
+			Password:        "123456",
+			DB:              1,
+			MaxIdleConns:    16,
+			PoolSize:        64,
+			ConnMaxIdleTime: time.Second * 300,
+			DialTimeout:     time.Millisecond * 10,
+			ReadTimeout:     time.Millisecond * 10,
+			WriteTimeout:    time.Millisecond * 10,
 		},
-		DialTimeout:  time.Millisecond * 10,
-		ReadTimeout:  time.Millisecond * 10,
-		WriteTimeout: time.Millisecond * 10,
-	},
-	Validation: Validation{
-		Manifests: ValidationManifests{
-			Indexes: ValidationIndexes{
-				Platforms: "none",
-			},
+		TLS: RedisTLSOptions{
+			Certificate: "/foo/cert.crt",
+			Key:         "/foo/key.pem",
+			ClientCAs:   []string{"/path/to/ca.pem"},
 		},
 	},
 }
@@ -177,8 +169,6 @@ storage:
     int1: 42
     url1: "https://foo.example.com"
     path1: "/some-path"
-  tag:
-    concurrencylimit: 10
 auth:
   silly:
     realm: silly
@@ -197,26 +187,27 @@ notifications:
         actions:
            - pull
 http:
-  clientcas:
-    - /path/to/ca.pem
+  tls:
+    clientcas:
+      - /path/to/ca.pem
   headers:
     X-Content-Type-Options: [nosniff]
 redis:
-  addr: localhost:6379
+  tls:
+    certificate: /foo/cert.crt
+    key: /foo/key.pem
+    clientcas:
+      - /path/to/ca.pem
+  addrs: [localhost:6379]
   username: alice
-  password: 123456
+  password: "123456"
   db: 1
-  pool:
-    maxidle: 16
-    maxactive: 64
-    idletimeout: 300s
+  maxidleconns: 16
+  poolsize: 64
+  connmaxidletime: 300s
   dialtimeout: 10ms
   readtimeout: 10ms
   writetimeout: 10ms
-validation:
-  manifests:
-    indexes:
-      platforms: none
 `
 
 // inmemoryConfigYamlV0_1 is a Version 0.1 yaml document specifying an inmemory
@@ -246,10 +237,6 @@ notifications:
 http:
   headers:
     X-Content-Type-Options: [nosniff]
-validation:
-  manifests:
-    indexes:
-      platforms: none
 `
 
 type ConfigSuite struct {
@@ -289,6 +276,7 @@ func (suite *ConfigSuite) TestParseSimple() {
 func (suite *ConfigSuite) TestParseInmemory() {
 	suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
 	suite.expectedConfig.Log.Fields = nil
+	suite.expectedConfig.HTTP.TLS.ClientCAs = nil
 	suite.expectedConfig.Redis = Redis{}
 
 	config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1)))
@@ -309,8 +297,8 @@ func (suite *ConfigSuite) TestParseIncomplete() {
 	suite.expectedConfig.Auth = Auth{"silly": Parameters{"realm": "silly"}}
 	suite.expectedConfig.Notifications = Notifications{}
 	suite.expectedConfig.HTTP.Headers = nil
+	suite.expectedConfig.HTTP.TLS.ClientCAs = nil
 	suite.expectedConfig.Redis = Redis{}
-	suite.expectedConfig.Validation.Manifests.Indexes.Platforms = ""
 
 	// Note: this also tests that REGISTRY_STORAGE and
 	// REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY can be used together
@@ -563,9 +551,6 @@ func copyConfig(config Configuration) *Configuration {
 	for k, v := range config.Storage.Parameters() {
 		configCopy.Storage.setParameter(k, v)
 	}
-	for k, v := range config.Storage.TagParameters() {
-		configCopy.Storage.setTagParameter(k, v)
-	}
 
 	configCopy.Auth = Auth{config.Auth.Type(): Parameters{}}
 	for k, v := range config.Auth.Parameters() {
@@ -579,14 +564,14 @@ func copyConfig(config Configuration) *Configuration {
 	for k, v := range config.HTTP.Headers {
 		configCopy.HTTP.Headers[k] = v
 	}
+	configCopy.HTTP.TLS.ClientCAs = make([]string, 0, len(config.HTTP.TLS.ClientCAs))
+	configCopy.HTTP.TLS.ClientCAs = append(configCopy.HTTP.TLS.ClientCAs, config.HTTP.TLS.ClientCAs...)
 
 	configCopy.Redis = config.Redis
-
-	configCopy.Validation = Validation{
-		Enabled:   config.Validation.Enabled,
-		Disabled:  config.Validation.Disabled,
-		Manifests: config.Validation.Manifests,
-	}
+	configCopy.Redis.TLS.Certificate = config.Redis.TLS.Certificate
+	configCopy.Redis.TLS.Key = config.Redis.TLS.Key
+	configCopy.Redis.TLS.ClientCAs = make([]string, 0, len(config.Redis.TLS.ClientCAs))
+	configCopy.Redis.TLS.ClientCAs = append(configCopy.Redis.TLS.ClientCAs, config.Redis.TLS.ClientCAs...)
 
 	return configCopy
 }
diff --git a/registry/auth/token/accesscontroller_test.go b/registry/auth/token/accesscontroller_test.go
deleted file mode 100644
index fde20311..00000000
--- a/registry/auth/token/accesscontroller_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package token
-
-import (
-	"net/http"
-	"net/http/httptest"
-	"testing"
-)
-
-func TestBuildAutoRedirectURL(t *testing.T) {
-	cases := []struct {
-		name             string
-		reqGetter        func() *http.Request
-		autoRedirectPath string
-		expectedURL      string
-	}{{
-		name: "http",
-		reqGetter: func() *http.Request {
-			req := httptest.NewRequest("GET", "http://example.com/", nil)
-			return req
-		},
-		autoRedirectPath: "/auth",
-		expectedURL:      "https://example.com/auth",
-	}, {
-		name: "x-forwarded",
-		reqGetter: func() *http.Request {
-			req := httptest.NewRequest("GET", "http://example.com/", nil)
-			req.Header.Set("X-Forwarded-Proto", "http")
-			return req
-		},
-		autoRedirectPath: "/auth/token",
-		expectedURL:      "http://example.com/auth/token",
-	}}
-
-	for _, tc := range cases {
-		t.Run(tc.name, func(t *testing.T) {
-			req := tc.reqGetter()
-			result := buildAutoRedirectURL(req, tc.autoRedirectPath)
-			if result != tc.expectedURL {
-				t.Errorf("expected %s, got %s", tc.expectedURL, result)
-			}
-		})
-	}
-}
-
-func TestCheckOptions(t *testing.T) {
-	realm := "https://auth.example.com/token/"
-	issuer := "test-issuer.example.com"
-	service := "test-service.example.com"
-
-	options := map[string]interface{}{
-		"realm":            realm,
-		"issuer":           issuer,
-		"service":          service,
-		"rootcertbundle":   "",
-		"autoredirect":     true,
-		"autoredirectpath": "/auth",
-	}
-
-	ta, err := checkOptions(options)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if ta.autoRedirect != true {
-		t.Fatal("autoredirect should be true")
-	}
-	if ta.autoRedirectPath != "/auth" {
-		t.Fatal("autoredirectpath should be /auth")
-	}
-
-	options = map[string]interface{}{
-		"realm":                        realm,
-		"issuer":                       issuer,
-		"service":                      service,
-		"rootcertbundle":               "",
-		"autoredirect":                 true,
-		"autoredirectforcetlsdisabled": true,
-	}
-
-	ta, err = checkOptions(options)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if ta.autoRedirect != true {
-		t.Fatal("autoredirect should be true")
-	}
-	if ta.autoRedirectPath != "/auth/token" {
-		t.Fatal("autoredirectpath should be /auth/token")
-	}
-}
diff --git a/registry/auth/token/fuzz_test.go b/registry/auth/token/fuzz_test.go
index 7e37e541..7cbfbb7a 100644
--- a/registry/auth/token/fuzz_test.go
+++ b/registry/auth/token/fuzz_test.go
@@ -4,7 +4,6 @@ import (
 	"testing"
 
 	fuzz "github.com/AdaLogics/go-fuzz-headers"
-	"github.com/go-jose/go-jose/v4"
 )
 
 func FuzzToken1(f *testing.F) {
@@ -19,7 +18,7 @@ func FuzzToken1(f *testing.F) {
 		if err != nil {
 			return
 		}
-		token, err := NewToken(rawToken, []jose.SignatureAlgorithm{jose.EdDSA, jose.RS384})
+		token, err := NewToken(rawToken)
 		if err != nil {
 			return
 		}
diff --git a/registry/auth/token/token_test.go b/registry/auth/token/token_test.go
index b982ab14..a96546af 100644
--- a/registry/auth/token/token_test.go
+++ b/registry/auth/token/token_test.go
@@ -19,8 +19,8 @@ import (
 	"time"
 
 	"github.com/distribution/distribution/v3/registry/auth"
-	"github.com/go-jose/go-jose/v4"
-	"github.com/go-jose/go-jose/v4/jwt"
+	"github.com/go-jose/go-jose/v3"
+	"github.com/go-jose/go-jose/v3/jwt"
 )
 
 func makeRootKeys(numKeys int) ([]*ecdsa.PrivateKey, error) {
@@ -123,12 +123,12 @@ func makeTestToken(jwk *jose.JSONWebKey, issuer, audience string, access []*Reso
 		Access:     access,
 	}
 
-	tokenString, err := jwt.Signed(signer).Claims(claimSet).Serialize()
+	tokenString, err := jwt.Signed(signer).Claims(claimSet).CompactSerialize()
 	if err != nil {
 		return nil, fmt.Errorf("unable to build token string: %v", err)
 	}
 
-	return NewToken(tokenString, []jose.SignatureAlgorithm{signingKey.Algorithm})
+	return NewToken(tokenString)
 }
 
 // NOTE(milosgajdos): certTemplateInfo type as well
diff --git a/registry/handlers/api_test.go b/registry/handlers/api_test.go
index 43d3ae14..dc5a57ce 100644
--- a/registry/handlers/api_test.go
+++ b/registry/handlers/api_test.go
@@ -2514,7 +2514,7 @@ func pushChunk(t *testing.T, ub *v2.URLBuilder, name reference.Named, uploadURLB
 
 func checkResponse(t *testing.T, msg string, resp *http.Response, expectedStatus int) {
 	if resp.StatusCode != expectedStatus {
-		t.Logf("unexpected status %s: expected %v, got %v", msg, resp.StatusCode, expectedStatus)
+		t.Logf("unexpected status %s: %v != %v", msg, resp.StatusCode, expectedStatus)
 		maybeDumpResponse(t, resp)
 		t.FailNow()
 	}
diff --git a/registry/proxy/proxyblobstore_test.go b/registry/proxy/proxyblobstore_test.go
index 91218a40..e7dccc09 100644
--- a/registry/proxy/proxyblobstore_test.go
+++ b/registry/proxy/proxyblobstore_test.go
@@ -448,22 +448,12 @@ func testProxyStoreServe(t *testing.T, te *testEnv, numClients int) {
 					return
 				}
 
-				resp := w.Result()
-				bodyBytes, err := io.ReadAll(resp.Body)
-				resp.Body.Close()
-				if err != nil {
-					t.Errorf(err.Error())
-					return
-				}
+				bodyBytes := w.Body.Bytes()
 				localDigest := digest.FromBytes(bodyBytes)
 				if localDigest != remoteBlob.Digest {
 					t.Errorf("Mismatching blob fetch from proxy")
 					return
 				}
-				if resp.Header.Get("Docker-Content-Digest") != localDigest.String() {
-					t.Errorf("Mismatching digest in response header")
-					return
-				}
 
 				desc, err := te.store.localStore.Stat(te.ctx, remoteBlob.Digest)
 				if err != nil {
diff --git a/registry/proxy/scheduler/scheduler_test.go b/registry/proxy/scheduler/scheduler_test.go
index fb0869c2..38fa0f58 100644
--- a/registry/proxy/scheduler/scheduler_test.go
+++ b/registry/proxy/scheduler/scheduler_test.go
@@ -136,12 +136,7 @@ func TestRestoreOld(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Error starting ttlExpirationScheduler: %s", err)
 	}
-	defer func(s *TTLExpirationScheduler) {
-		err := s.Stop()
-		if err != nil {
-			t.Fatalf("Error stopping ttlExpirationScheduler: %s", err)
-		}
-	}(s)
+	defer s.Stop()
 
 	wg.Wait()
 	mu.Lock()
@@ -182,10 +177,7 @@ func TestStopRestore(t *testing.T) {
 
 	// Start and stop before all operations complete
 	// state will be written to fs
-	err = s.Stop()
-	if err != nil {
-		t.Fatalf(err.Error())
-	}
+	s.Stop()
 	time.Sleep(10 * time.Millisecond)
 
 	// v2 will restore state from fs
diff --git a/registry/registry_test.go b/registry/registry_test.go
index ea23b9db..98dd6c94 100644
--- a/registry/registry_test.go
+++ b/registry/registry_test.go
@@ -103,7 +103,7 @@ func TestGracefulShutdown(t *testing.T) {
 	fmt.Fprintf(conn, "GET /v2/ ")
 
 	// send stop signal
-	registry.quit <- os.Interrupt
+	quit <- os.Interrupt
 	time.Sleep(100 * time.Millisecond)
 
 	// try connecting again. it shouldn't
@@ -325,7 +325,7 @@ func TestRegistrySupportedCipherSuite(t *testing.T) {
 	}
 
 	// send stop signal
-	registry.quit <- os.Interrupt
+	quit <- os.Interrupt
 	time.Sleep(100 * time.Millisecond)
 }
 
@@ -369,7 +369,7 @@ func TestRegistryUnsupportedCipherSuite(t *testing.T) {
 	}
 
 	// send stop signal
-	registry.quit <- os.Interrupt
+	quit <- os.Interrupt
 	time.Sleep(100 * time.Millisecond)
 }
 
diff --git a/registry/storage/blob_test.go b/registry/storage/blob_test.go
index 25ac325c..768cd65c 100644
--- a/registry/storage/blob_test.go
+++ b/registry/storage/blob_test.go
@@ -40,7 +40,7 @@ func TestWriteSeek(t *testing.T) {
 	}
 	contents := []byte{1, 2, 3}
 	if _, err := blobUpload.Write(contents); err != nil {
-		t.Fatalf("unexpected error writing contents: %v", err)
+		t.Fatalf("unexpected error writing contets: %v", err)
 	}
 	blobUpload.Close()
 	offset := blobUpload.Size()
diff --git a/registry/storage/cache/redis/redis_test.go b/registry/storage/cache/redis/redis_test.go
index 2b057ca8..328a2ec9 100644
--- a/registry/storage/cache/redis/redis_test.go
+++ b/registry/storage/cache/redis/redis_test.go
@@ -20,7 +20,7 @@ func init() {
 // implementation.
 func TestRedisBlobDescriptorCacheProvider(t *testing.T) {
 	if redisAddr == "" {
-		// fallback to an environment variable
+		// fallback to an environement variable
 		redisAddr = os.Getenv("TEST_REGISTRY_STORAGE_CACHE_REDIS_ADDR")
 	}
 
diff --git a/registry/storage/driver/middleware/cloudfront/middleware_test.go b/registry/storage/driver/middleware/cloudfront/middleware_test.go
index 2fa8243d..9d1e11a2 100644
--- a/registry/storage/driver/middleware/cloudfront/middleware_test.go
+++ b/registry/storage/driver/middleware/cloudfront/middleware_test.go
@@ -50,6 +50,6 @@ pZeMRablbPQdp8/1NyIwimq1VlG0ohQ4P6qhW7E09ZMC
 		t.Fatal(err)
 	}
 	if storageDriver == nil {
-		t.Fatal("Driver could not be initialized")
+		t.Fatal("Driver couldnt be initialized.")
 	}
 }
diff --git a/registry/storage/driver/s3-aws/s3_test.go b/registry/storage/driver/s3-aws/s3_test.go
index a00666ae..e00b5771 100644
--- a/registry/storage/driver/s3-aws/s3_test.go
+++ b/registry/storage/driver/s3-aws/s3_test.go
@@ -80,7 +80,7 @@ func init() {
 				return nil, err
 			}
 		}
-		forcePathStyleBool := false
+		forcePathStyleBool := true
 		if forcePathStyle != "" {
 			forcePathStyleBool, err = strconv.ParseBool(forcePathStyle)
 			if err != nil {
diff --git a/registry/storage/garbagecollect_test.go b/registry/storage/garbagecollect_test.go
index f9990c68..be67ce7a 100644
--- a/registry/storage/garbagecollect_test.go
+++ b/registry/storage/garbagecollect_test.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/internal/dcontext"
-	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/registry/storage/driver"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/distribution/distribution/v3/testutil"
@@ -130,29 +129,6 @@ func uploadRandomSchema2Image(t *testing.T, repository distribution.Repository)
 	}
 }
 
-func uploadRandomOCIImage(t *testing.T, repository distribution.Repository) image {
-	randomLayers, err := testutil.CreateRandomLayers(2)
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	digests := []digest.Digest{}
-	for digest := range randomLayers {
-		digests = append(digests, digest)
-	}
-	manifest, err := testutil.MakeOCIManifest(repository, digests)
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	manifestDigest := uploadImage(t, repository, image{manifest: manifest, layers: randomLayers})
-	return image{
-		manifest:       manifest,
-		manifestDigest: manifestDigest,
-		layers:         randomLayers,
-	}
-}
-
 func TestNoDeletionNoEffect(t *testing.T) {
 	ctx := dcontext.Background()
 	inmemoryDriver := inmemory.New()
@@ -294,161 +270,6 @@ func TestDeleteManifestIfTagNotFound(t *testing.T) {
 	}
 }
 
-func TestDeleteManifestIndexWithDanglingReferences(t *testing.T) {
-	ctx := dcontext.Background()
-	inmemoryDriver := inmemory.New()
-
-	registry := createRegistry(t, inmemoryDriver)
-	repo := makeRepository(t, registry, "deletemanifests")
-	manifestService, _ := repo.Manifests(ctx)
-
-	image1 := uploadRandomOCIImage(t, repo)
-	image2 := uploadRandomOCIImage(t, repo)
-
-	ii, _ := ocischema.FromDescriptors([]distribution.Descriptor{
-		{Digest: image1.manifestDigest}, {Digest: image2.manifestDigest},
-	}, map[string]string{})
-
-	id, err := manifestService.Put(ctx, ii)
-	if err != nil {
-		t.Fatalf("manifest upload failed: %v", err)
-	}
-
-	err = repo.Tags(ctx).Tag(ctx, "test", distribution.Descriptor{Digest: id})
-	if err != nil {
-		t.Fatalf("Failed to delete tag: %v", err)
-	}
-
-	// delete image2 => ii has a dangling reference
-	err = manifestService.Delete(ctx, image2.manifestDigest)
-	if err != nil {
-		t.Fatalf("Failed to delete image: %v", err)
-	}
-
-	before1 := allBlobs(t, registry)
-	before2 := allManifests(t, manifestService)
-
-	// run GC (should not remove anything because of tag)
-	err = MarkAndSweep(dcontext.Background(), inmemoryDriver, registry, GCOpts{
-		DryRun:         false,
-		RemoveUntagged: true,
-	})
-	if err != nil {
-		t.Fatalf("Failed mark and sweep: %v", err)
-	}
-
-	after1 := allBlobs(t, registry)
-	after2 := allManifests(t, manifestService)
-	if len(before1) == len(after1) {
-		t.Fatalf("Garbage collection did not affect blobs storage: %d == %d", len(before1), len(after1))
-	}
-	if len(before2) != len(after2) {
-		t.Fatalf("Garbage collection affected manifest storage: %d != %d", len(before2), len(after2))
-	}
-}
-
-func TestDeleteManifestIndexIfTagNotFound(t *testing.T) {
-	ctx := dcontext.Background()
-	inmemoryDriver := inmemory.New()
-
-	registry := createRegistry(t, inmemoryDriver)
-	repo := makeRepository(t, registry, "deletemanifests")
-	manifestService, _ := repo.Manifests(ctx)
-
-	image1 := uploadRandomOCIImage(t, repo)
-	image2 := uploadRandomOCIImage(t, repo)
-
-	ii, _ := ocischema.FromDescriptors([]distribution.Descriptor{
-		{Digest: image1.manifestDigest}, {Digest: image2.manifestDigest},
-	}, map[string]string{})
-
-	d4, err := manifestService.Put(ctx, ii)
-	if err != nil {
-		t.Fatalf("manifest upload failed: %v", err)
-	}
-
-	err = repo.Tags(ctx).Tag(ctx, "test", distribution.Descriptor{Digest: d4})
-	if err != nil {
-		t.Fatalf("Failed to delete tag: %v", err)
-	}
-
-	before1 := allBlobs(t, registry)
-	before2 := allManifests(t, manifestService)
-
-	// run GC (should not remove anything because of tag)
-	err = MarkAndSweep(dcontext.Background(), inmemoryDriver, registry, GCOpts{
-		DryRun:         false,
-		RemoveUntagged: true,
-	})
-	if err != nil {
-		t.Fatalf("Failed mark and sweep: %v", err)
-	}
-	beforeUntag1 := allBlobs(t, registry)
-	beforeUntag2 := allManifests(t, manifestService)
-	if len(before1) != len(beforeUntag1) {
-		t.Fatalf("Garbage collection affected blobs storage: %d != %d", len(before1), len(beforeUntag1))
-	}
-	if len(before2) != len(beforeUntag2) {
-		t.Fatalf("Garbage collection affected manifest storage: %d != %d", len(before2), len(beforeUntag2))
-	}
-
-	err = repo.Tags(ctx).Untag(ctx, "test")
-	if err != nil {
-		t.Fatalf("Failed to delete tag: %v", err)
-	}
-
-	// Run GC (removes everything because no manifests with tags exist)
-	err = MarkAndSweep(dcontext.Background(), inmemoryDriver, registry, GCOpts{
-		DryRun:         false,
-		RemoveUntagged: true,
-	})
-	if err != nil {
-		t.Fatalf("Failed mark and sweep: %v", err)
-	}
-
-	after1 := allBlobs(t, registry)
-	after2 := allManifests(t, manifestService)
-	if len(beforeUntag1) == len(after1) {
-		t.Fatalf("Garbage collection did not affect blobs storage: %d == %d", len(beforeUntag1), len(after1))
-	}
-	if len(beforeUntag2) == len(after2) {
-		t.Fatalf("Garbage collection did not affect manifest storage: %d == %d", len(beforeUntag2), len(after2))
-	}
-}
-
-func TestGCWithUnknownRepository(t *testing.T) {
-	ctx := dcontext.Background()
-	d := inmemory.New()
-
-	registry := createRegistry(t, d)
-	repo := makeRepository(t, registry, "nonexistentrepo")
-	image := uploadRandomSchema2Image(t, repo)
-
-	err := repo.Tags(ctx).Tag(ctx, "image", distribution.Descriptor{Digest: image.manifestDigest})
-	if err != nil {
-		t.Fatalf("Failed to tag descriptor: %v", err)
-	}
-
-	// Simulate a missing _manifests/tags directory
-	manifestTagsPath, err := pathFor(manifestTagsPathSpec{"nonexistentrepo"})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	err = d.Delete(ctx, manifestTagsPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	err = MarkAndSweep(dcontext.Background(), d, registry, GCOpts{
-		DryRun:         false,
-		RemoveUntagged: true,
-	})
-	if err != nil {
-		t.Fatalf("got error: %v, expected nil", err)
-	}
-}
-
 func TestGCWithMissingManifests(t *testing.T) {
 	ctx := dcontext.Background()
 	d := inmemory.New()
@@ -658,225 +479,3 @@ func TestOrphanBlobDeleted(t *testing.T) {
 		}
 	}
 }
-
-func TestTaggedManifestlistWithUntaggedManifest(t *testing.T) {
-	ctx := dcontext.Background()
-	inmemoryDriver := inmemory.New()
-
-	registry := createRegistry(t, inmemoryDriver)
-	repo := makeRepository(t, registry, "foo/taggedlist/untaggedmanifest")
-	manifestService, err := repo.Manifests(ctx)
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	image1 := uploadRandomSchema2Image(t, repo)
-	image2 := uploadRandomSchema2Image(t, repo)
-
-	// construct a manifestlist to reference manifests that is not tagged.
-	blobstatter := registry.BlobStatter()
-	manifestList, err := testutil.MakeManifestList(blobstatter, []digest.Digest{
-		image1.manifestDigest, image2.manifestDigest,
-	})
-	if err != nil {
-		t.Fatalf("Failed to make manifest list: %v", err)
-	}
-
-	dgst, err := manifestService.Put(ctx, manifestList)
-	if err != nil {
-		t.Fatalf("Failed to add manifest list: %v", err)
-	}
-
-	err = repo.Tags(ctx).Tag(ctx, "test", distribution.Descriptor{Digest: dgst})
-	if err != nil {
-		t.Fatalf("Failed to delete tag: %v", err)
-	}
-
-	before := allBlobs(t, registry)
-
-	// Run GC
-	err = MarkAndSweep(dcontext.Background(), inmemoryDriver, registry, GCOpts{
-		DryRun:         false,
-		RemoveUntagged: true,
-	})
-	if err != nil {
-		t.Fatalf("Failed mark and sweep: %v", err)
-	}
-
-	after := allBlobs(t, registry)
-	if len(before) != len(after) {
-		t.Fatalf("Garbage collection affected storage: %d != %d", len(before), len(after))
-	}
-
-	if _, ok := after[image1.manifestDigest]; !ok {
-		t.Fatalf("First manifest is missing")
-	}
-
-	if _, ok := after[image2.manifestDigest]; !ok {
-		t.Fatalf("Second manifest is missing")
-	}
-
-	if _, ok := after[dgst]; !ok {
-		t.Fatalf("Manifest list is missing")
-	}
-}
-
-func TestUnTaggedManifestlistWithUntaggedManifest(t *testing.T) {
-	ctx := dcontext.Background()
-	inmemoryDriver := inmemory.New()
-
-	registry := createRegistry(t, inmemoryDriver)
-	repo := makeRepository(t, registry, "foo/untaggedlist/untaggedmanifest")
-	manifestService, err := repo.Manifests(ctx)
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	image1 := uploadRandomSchema2Image(t, repo)
-	image2 := uploadRandomSchema2Image(t, repo)
-
-	// construct a manifestlist to reference manifests that is not tagged.
-	blobstatter := registry.BlobStatter()
-	manifestList, err := testutil.MakeManifestList(blobstatter, []digest.Digest{
-		image1.manifestDigest, image2.manifestDigest,
-	})
-	if err != nil {
-		t.Fatalf("Failed to make manifest list: %v", err)
-	}
-
-	_, err = manifestService.Put(ctx, manifestList)
-	if err != nil {
-		t.Fatalf("Failed to add manifest list: %v", err)
-	}
-
-	// Run GC
-	err = MarkAndSweep(dcontext.Background(), inmemoryDriver, registry, GCOpts{
-		DryRun:         false,
-		RemoveUntagged: true,
-	})
-	if err != nil {
-		t.Fatalf("Failed mark and sweep: %v", err)
-	}
-
-	after := allBlobs(t, registry)
-	if len(after) != 0 {
-		t.Fatalf("Garbage collection affected storage: %d != %d", len(after), 0)
-	}
-
-}
-
-func TestUnTaggedManifestlistWithTaggedManifest(t *testing.T) {
-	ctx := dcontext.Background()
-	inmemoryDriver := inmemory.New()
-
-	registry := createRegistry(t, inmemoryDriver)
-	repo := makeRepository(t, registry, "foo/untaggedlist/taggedmanifest")
-	manifestService, err := repo.Manifests(ctx)
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	image1 := uploadRandomSchema2Image(t, repo)
-	image2 := uploadRandomSchema2Image(t, repo)
-
-	err = repo.Tags(ctx).Tag(ctx, "image1", distribution.Descriptor{Digest: image1.manifestDigest})
-	if err != nil {
-		t.Fatalf("Failed to delete tag: %v", err)
-	}
-
-	err = repo.Tags(ctx).Tag(ctx, "image2", distribution.Descriptor{Digest: image2.manifestDigest})
-	if err != nil {
-		t.Fatalf("Failed to delete tag: %v", err)
-	}
-
-	// construct a manifestlist to reference manifests that is tagged.
-	blobstatter := registry.BlobStatter()
-	manifestList, err := testutil.MakeManifestList(blobstatter, []digest.Digest{
-		image1.manifestDigest, image2.manifestDigest,
-	})
-	if err != nil {
-		t.Fatalf("Failed to make manifest list: %v", err)
-	}
-
-	dgst, err := manifestService.Put(ctx, manifestList)
-	if err != nil {
-		t.Fatalf("Failed to add manifest list: %v", err)
-	}
-
-	// Run GC
-	err = MarkAndSweep(dcontext.Background(), inmemoryDriver, registry, GCOpts{
-		DryRun:         false,
-		RemoveUntagged: true,
-	})
-	if err != nil {
-		t.Fatalf("Failed mark and sweep: %v", err)
-	}
-
-	after := allBlobs(t, registry)
-	afterManifests := allManifests(t, manifestService)
-
-	if _, ok := after[dgst]; ok {
-		t.Fatalf("Untagged manifestlist still exists")
-	}
-
-	if _, ok := afterManifests[image1.manifestDigest]; !ok {
-		t.Fatalf("First manifest is missing")
-	}
-
-	if _, ok := afterManifests[image2.manifestDigest]; !ok {
-		t.Fatalf("Second manifest is missing")
-	}
-}
-
-func TestTaggedManifestlistWithDeletedReference(t *testing.T) {
-	ctx := dcontext.Background()
-	inmemoryDriver := inmemory.New()
-
-	registry := createRegistry(t, inmemoryDriver)
-	repo := makeRepository(t, registry, "foo/untaggedlist/deleteref")
-	manifestService, err := repo.Manifests(ctx)
-	if err != nil {
-		t.Fatalf("%v", err)
-	}
-
-	image1 := uploadRandomSchema2Image(t, repo)
-	image2 := uploadRandomSchema2Image(t, repo)
-
-	// construct a manifestlist to reference manifests that is deleted.
-	blobstatter := registry.BlobStatter()
-	manifestList, err := testutil.MakeManifestList(blobstatter, []digest.Digest{
-		image1.manifestDigest, image2.manifestDigest,
-	})
-	if err != nil {
-		t.Fatalf("Failed to make manifest list: %v", err)
-	}
-
-	_, err = manifestService.Put(ctx, manifestList)
-	if err != nil {
-		t.Fatalf("Failed to add manifest list: %v", err)
-	}
-
-	err = manifestService.Delete(ctx, image1.manifestDigest)
-	if err != nil {
-		t.Fatalf("Failed to delete image: %v", err)
-	}
-
-	err = manifestService.Delete(ctx, image2.manifestDigest)
-	if err != nil {
-		t.Fatalf("Failed to delete image: %v", err)
-	}
-
-	// Run GC
-	err = MarkAndSweep(dcontext.Background(), inmemoryDriver, registry, GCOpts{
-		DryRun:         false,
-		RemoveUntagged: true,
-	})
-	if err != nil {
-		t.Fatalf("Failed mark and sweep: %v", err)
-	}
-
-	after := allBlobs(t, registry)
-	if len(after) != 0 {
-		t.Fatalf("Garbage collection affected storage: %d != %d", len(after), 0)
-	}
-}
diff --git a/registry/storage/manifeststore_test.go b/registry/storage/manifeststore_test.go
index 1b071df2..3c1cb9dd 100644
--- a/registry/storage/manifeststore_test.go
+++ b/registry/storage/manifeststore_test.go
@@ -10,7 +10,6 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/manifest"
-	"github.com/distribution/distribution/v3/manifest/manifestlist"
 	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/manifest/schema2"
 	"github.com/distribution/distribution/v3/registry/storage/cache/memory"
@@ -55,7 +54,7 @@ func newManifestStoreTestEnv(t *testing.T, name reference.Named, tag string, opt
 }
 
 func TestManifestStorage(t *testing.T) {
-	testManifestStorage(t, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider(memory.UnlimitedSize)), EnableDelete, EnableRedirect, EnableValidateImageIndexImagesExist)
+	testManifestStorage(t, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider(memory.UnlimitedSize)), EnableDelete, EnableRedirect)
 }
 
 func testManifestStorage(t *testing.T, options ...RegistryOption) {
@@ -315,7 +314,7 @@ func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes boo
 	repoName, _ := reference.WithName("foo/bar")
 	env := newManifestStoreTestEnv(t, repoName, "thetag",
 		BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider(memory.UnlimitedSize)),
-		EnableDelete, EnableRedirect, EnableValidateImageIndexImagesExist)
+		EnableDelete, EnableRedirect)
 
 	ctx := context.Background()
 	ms, err := env.repository.Manifests(ctx)
@@ -323,36 +322,46 @@ func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes boo
 		t.Fatal(err)
 	}
 
-	// Build a manifest and store its layers in the registry
+	// Build a manifest and store it and its layers in the registry
 
 	blobStore := env.repository.Blobs(ctx)
-	mfst, err := createRandomImage(t, testname, imageMediaType, blobStore)
+	builder := ocischema.NewManifestBuilder(blobStore, []byte{}, map[string]string{})
+	err = builder.(*ocischema.Builder).SetMediaType(imageMediaType)
 	if err != nil {
-		t.Fatalf("%s: unexpected error generating random image: %v", testname, err)
+		t.Fatal(err)
 	}
 
-	// create an image index
+	// Add some layers
+	for i := 0; i < 2; i++ {
+		rs, dgst, err := testutil.CreateRandomTarFile()
+		if err != nil {
+			t.Fatalf("%s: unexpected error generating test layer file", testname)
+		}
 
-	platformSpec := &v1.Platform{
-		Architecture: "atari2600",
-		OS:           "CP/M",
-	}
+		wr, err := env.repository.Blobs(env.ctx).Create(env.ctx)
+		if err != nil {
+			t.Fatalf("%s: unexpected error creating test upload: %v", testname, err)
+		}
 
-	mfstDescriptors := []distribution.Descriptor{
-		createOciManifestDescriptor(t, testname, mfst, platformSpec),
-	}
+		if _, err := io.Copy(wr, rs); err != nil {
+			t.Fatalf("%s: unexpected error copying to upload: %v", testname, err)
+		}
 
-	imageIndex, err := ociIndexFromDesriptorsWithMediaType(mfstDescriptors, indexMediaType)
-	if err != nil {
-		t.Fatalf("%s: unexpected error creating image index: %v", testname, err)
+		if _, err := wr.Commit(env.ctx, distribution.Descriptor{Digest: dgst}); err != nil {
+			t.Fatalf("%s: unexpected error finishing upload: %v", testname, err)
+		}
+
+		if err := builder.AppendReference(distribution.Descriptor{Digest: dgst, MediaType: v1.MediaTypeImageLayer}); err != nil {
+			t.Fatalf("%s unexpected error appending references: %v", testname, err)
+		}
 	}
 
-	_, err = ms.Put(ctx, imageIndex)
-	if err == nil {
-		t.Fatalf("%s: expected error putting image index without child manifests in the registry: %v", testname, err)
+	mfst, err := builder.Build(ctx)
+	if err != nil {
+		t.Fatalf("%s: unexpected error generating manifest: %v", testname, err)
 	}
 
-	// Test for proper handling of SchemaVersion for the image
+	// before putting the manifest test for proper handling of SchemaVersion
 
 	if mfst.(*ocischema.DeserializedManifest).Manifest.SchemaVersion != 2 {
 		t.Fatalf("%s: unexpected error generating default version for oci manifest", testname)
@@ -370,7 +379,22 @@ func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes boo
 		}
 	}
 
-	// We can now push the index
+	// Also create an image index that contains the manifest
+
+	descriptor, err := env.registry.BlobStatter().Stat(ctx, manifestDigest)
+	if err != nil {
+		t.Fatalf("%s: unexpected error getting manifest descriptor", testname)
+	}
+	descriptor.MediaType = v1.MediaTypeImageManifest
+	descriptor.Platform = &v1.Platform{
+		Architecture: "atari2600",
+		OS:           "CP/M",
+	}
+
+	imageIndex, err := ociIndexFromDesriptorsWithMediaType([]distribution.Descriptor{descriptor}, indexMediaType)
+	if err != nil {
+		t.Fatalf("%s: unexpected error creating image index: %v", testname, err)
+	}
 
 	var indexDigest digest.Digest
 	if indexDigest, err = ms.Put(ctx, imageIndex); err != nil {
@@ -432,244 +456,6 @@ func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes boo
 	}
 }
 
-func TestIndexManifestStorageWithoutImageCheck(t *testing.T) {
-	imageMediaType := v1.MediaTypeImageManifest
-	indexMediaType := v1.MediaTypeImageIndex
-
-	repoName, _ := reference.WithName("foo/bar")
-	env := newManifestStoreTestEnv(t, repoName, "thetag",
-		BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider(memory.UnlimitedSize)),
-		EnableDelete, EnableRedirect)
-
-	ctx := context.Background()
-	ms, err := env.repository.Manifests(ctx)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// Build a manifest and store its layers in the registry
-
-	blobStore := env.repository.Blobs(ctx)
-	manifest, err := createRandomImage(t, t.Name(), imageMediaType, blobStore)
-	if err != nil {
-		t.Fatalf("unexpected error generating random image: %v", err)
-	}
-
-	// create an image index
-
-	ociPlatformSpec := &v1.Platform{
-		Architecture: "atari2600",
-		OS:           "CP/M",
-	}
-
-	ociManifestDescriptors := []distribution.Descriptor{
-		createOciManifestDescriptor(t, t.Name(), manifest, ociPlatformSpec),
-	}
-
-	imageIndex, err := ociIndexFromDesriptorsWithMediaType(ociManifestDescriptors, indexMediaType)
-	if err != nil {
-		t.Fatalf("unexpected error creating image index: %v", err)
-	}
-
-	// We should be able to put the index without having put the image
-
-	_, err = ms.Put(ctx, imageIndex)
-	if err != nil {
-		t.Fatalf("unexpected error putting sparse OCI image index: %v", err)
-	}
-
-	// same for a manifest list
-
-	listPlatformSpec := &manifestlist.PlatformSpec{
-		Architecture: "atari2600",
-		OS:           "CP/M",
-	}
-
-	listManifestDescriptors := []manifestlist.ManifestDescriptor{
-		createManifestListDescriptor(t, t.Name(), manifest, listPlatformSpec),
-	}
-
-	list, err := manifestlist.FromDescriptors(listManifestDescriptors)
-	if err != nil {
-		t.Fatalf("unexpected error creating manifest list: %v", err)
-	}
-
-	// We should be able to put the list without having put the image
-
-	_, err = ms.Put(ctx, list)
-	if err != nil {
-		t.Fatalf("unexpected error putting sparse manifest list: %v", err)
-	}
-}
-
-func TestIndexManifestStorageWithSelectivePlatforms(t *testing.T) {
-	imageMediaType := v1.MediaTypeImageManifest
-	indexMediaType := v1.MediaTypeImageIndex
-
-	repoName, _ := reference.WithName("foo/bar")
-	env := newManifestStoreTestEnv(t, repoName, "thetag",
-		BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider(memory.UnlimitedSize)),
-		EnableDelete, EnableRedirect, EnableValidateImageIndexImagesExist,
-		AddValidateImageIndexImagesExistPlatform("amd64", "linux"))
-
-	ctx := context.Background()
-	ms, err := env.repository.Manifests(ctx)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// Build a manifests their layers in the registry
-
-	blobStore := env.repository.Blobs(ctx)
-	amdManifest, err := createRandomImage(t, t.Name(), imageMediaType, blobStore)
-	if err != nil {
-		t.Fatalf("%s: unexpected error generating random image: %v", t.Name(), err)
-	}
-	armManifest, err := createRandomImage(t, t.Name(), imageMediaType, blobStore)
-	if err != nil {
-		t.Fatalf("%s: unexpected error generating random image: %v", t.Name(), err)
-	}
-	atariManifest, err := createRandomImage(t, t.Name(), imageMediaType, blobStore)
-	if err != nil {
-		t.Fatalf("%s: unexpected error generating random image: %v", t.Name(), err)
-	}
-
-	// create an image index
-
-	amdPlatformSpec := &v1.Platform{
-		Architecture: "amd64",
-		OS:           "linux",
-	}
-	armPlatformSpec := &v1.Platform{
-		Architecture: "arm",
-		OS:           "plan9",
-	}
-	atariPlatformSpec := &v1.Platform{
-		Architecture: "atari2600",
-		OS:           "CP/M",
-	}
-
-	manifestDescriptors := []distribution.Descriptor{
-		createOciManifestDescriptor(t, t.Name(), amdManifest, amdPlatformSpec),
-		createOciManifestDescriptor(t, t.Name(), armManifest, armPlatformSpec),
-		createOciManifestDescriptor(t, t.Name(), atariManifest, atariPlatformSpec),
-	}
-
-	imageIndex, err := ociIndexFromDesriptorsWithMediaType(manifestDescriptors, indexMediaType)
-	if err != nil {
-		t.Fatalf("unexpected error creating image index: %v", err)
-	}
-
-	// Test we can't push with no image manifests existing in the registry
-
-	_, err = ms.Put(ctx, imageIndex)
-	if err == nil {
-		t.Fatalf("expected error putting image index without existing images: %v", err)
-	}
-
-	// Test we can't push with a manifest but not the right one
-
-	_, err = ms.Put(ctx, atariManifest)
-	if err != nil {
-		t.Fatalf("unexpected error putting manifest: %v", err)
-	}
-
-	_, err = ms.Put(ctx, imageIndex)
-	if err == nil {
-		t.Fatalf("expected error putting image index without correct existing images: %v", err)
-	}
-
-	// Test we can push with the right manifest
-
-	_, err = ms.Put(ctx, amdManifest)
-	if err != nil {
-		t.Fatalf("unexpected error putting manifest: %v", err)
-	}
-
-	_, err = ms.Put(ctx, imageIndex)
-	if err != nil {
-		t.Fatalf("unexpected error putting image index: %v", err)
-	}
-}
-
-// createRandomImage builds an image manifest and store it and its layers in the registry
-func createRandomImage(t *testing.T, testname string, imageMediaType string, blobStore distribution.BlobStore) (distribution.Manifest, error) {
-	builder := ocischema.NewManifestBuilder(blobStore, []byte{}, map[string]string{})
-	err := builder.(*ocischema.Builder).SetMediaType(imageMediaType)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	ctx := context.Background()
-
-	// Add some layers
-	for i := 0; i < 2; i++ {
-		rs, dgst, err := testutil.CreateRandomTarFile()
-		if err != nil {
-			t.Fatalf("%s: unexpected error generating test layer file", testname)
-		}
-
-		wr, err := blobStore.Create(ctx)
-		if err != nil {
-			t.Fatalf("%s: unexpected error creating test upload: %v", testname, err)
-		}
-
-		if _, err := io.Copy(wr, rs); err != nil {
-			t.Fatalf("%s: unexpected error copying to upload: %v", testname, err)
-		}
-
-		if _, err := wr.Commit(ctx, distribution.Descriptor{Digest: dgst}); err != nil {
-			t.Fatalf("%s: unexpected error finishing upload: %v", testname, err)
-		}
-
-		if err := builder.AppendReference(distribution.Descriptor{Digest: dgst, MediaType: v1.MediaTypeImageLayer}); err != nil {
-			t.Fatalf("%s unexpected error appending references: %v", testname, err)
-		}
-	}
-
-	return builder.Build(ctx)
-}
-
-// createOciManifestDescriptor builds a manifest descriptor from a manifest and a platform descriptor
-func createOciManifestDescriptor(t *testing.T, testname string, manifest distribution.Manifest, platformSpec *v1.Platform) distribution.Descriptor {
-	manifestMediaType, manifestPayload, err := manifest.Payload()
-	if err != nil {
-		t.Fatalf("%s: unexpected error getting manifest payload: %v", testname, err)
-	}
-	manifestDigest := digest.FromBytes(manifestPayload)
-
-	return distribution.Descriptor{
-		Digest:    manifestDigest,
-		Size:      int64(len(manifestPayload)),
-		MediaType: manifestMediaType,
-		Platform: &v1.Platform{
-			Architecture: platformSpec.Architecture,
-			OS:           platformSpec.OS,
-		},
-	}
-}
-
-// createManifestListDescriptor builds a manifest descriptor from a manifest and a platform descriptor
-func createManifestListDescriptor(t *testing.T, testname string, manifest distribution.Manifest, platformSpec *manifestlist.PlatformSpec) manifestlist.ManifestDescriptor {
-	manifestMediaType, manifestPayload, err := manifest.Payload()
-	if err != nil {
-		t.Fatalf("%s: unexpected error getting manifest payload: %v", testname, err)
-	}
-	manifestDigest := digest.FromBytes(manifestPayload)
-
-	return manifestlist.ManifestDescriptor{
-		Descriptor: distribution.Descriptor{
-			Digest:    manifestDigest,
-			Size:      int64(len(manifestPayload)),
-			MediaType: manifestMediaType,
-		},
-		Platform: manifestlist.PlatformSpec{
-			Architecture: platformSpec.Architecture,
-			OS:           platformSpec.OS,
-		},
-	}
-}
-
 // TestLinkPathFuncs ensures that the link path functions behavior are locked
 // down and implemented as expected.
 func TestLinkPathFuncs(t *testing.T) {
diff --git a/registry/storage/schema2manifesthandler_test.go b/registry/storage/schema2manifesthandler_test.go
index 6b918a80..26c19ec1 100644
--- a/registry/storage/schema2manifesthandler_test.go
+++ b/registry/storage/schema2manifesthandler_test.go
@@ -18,9 +18,7 @@ func TestVerifyManifestForeignLayer(t *testing.T) {
 	inmemoryDriver := inmemory.New()
 	registry := createRegistry(t, inmemoryDriver,
 		ManifestURLsAllowRegexp(regexp.MustCompile("^https?://foo")),
-		ManifestURLsDenyRegexp(regexp.MustCompile("^https?://foo/nope")),
-		EnableValidateImageIndexImagesExist,
-	)
+		ManifestURLsDenyRegexp(regexp.MustCompile("^https?://foo/nope")))
 	repo := makeRepository(t, registry, "test")
 	manifestService := makeManifestService(t, repo)
 
@@ -158,9 +156,7 @@ func TestVerifyManifestBlobLayerAndConfig(t *testing.T) {
 	inmemoryDriver := inmemory.New()
 	registry := createRegistry(t, inmemoryDriver,
 		ManifestURLsAllowRegexp(regexp.MustCompile("^https?://foo")),
-		ManifestURLsDenyRegexp(regexp.MustCompile("^https?://foo/nope")),
-		EnableValidateImageIndexImagesExist,
-	)
+		ManifestURLsDenyRegexp(regexp.MustCompile("^https?://foo/nope")))
 
 	repo := makeRepository(t, registry, strings.ToLower(t.Name()))
 	manifestService := makeManifestService(t, repo)
diff --git a/tests/conf-e2e-cloud-storage.yml b/tests/conf-e2e-cloud-storage.yml
index 92792dd3..89e496c4 100644
--- a/tests/conf-e2e-cloud-storage.yml
+++ b/tests/conf-e2e-cloud-storage.yml
@@ -17,15 +17,14 @@ log:
     formatter: text
     level: debug
 redis:
-   addr: redis:6379
+   addrs: [redis:6379]
    db: 0
    dialtimeout: 5s
    readtimeout: 10ms
    writetimeout: 10ms
-   pool:
-     idletimeout: 60s
-     maxactive: 64
-     maxidle: 16
+   maxidleconns: 16
+   poolsize: 64
+   connmaxidletime: 300s
 storage:
   redirect:
     disable: true
@@ -41,7 +40,6 @@ storage:
     bucket: images-local
     rootdirectory: /registry-v2
     regionendpoint: http://minio:9000
-    forcepathstyle: true
     encrypt: false
     secure: false
     chunksize: 33554432
diff --git a/tests/docker-compose-e2e-cloud-storage.yml b/tests/docker-compose-e2e-cloud-storage.yml
index 58f949d4..887c9db9 100644
--- a/tests/docker-compose-e2e-cloud-storage.yml
+++ b/tests/docker-compose-e2e-cloud-storage.yml
@@ -43,7 +43,7 @@ services:
       dockerfile: ./Dockerfile
     command:
       - "serve"
-      - "/etc/distribution/config-test.yml"
+      - "/etc/docker/registry/config-test.yml"
     healthcheck:
       test: ["CMD", "curl", "-f", "http://localhost:5001/debug/health"]
       interval: 5s
@@ -58,4 +58,4 @@ services:
       - "5000:5000"
       - "5001:5001"
     volumes:
-      - ./conf-e2e-cloud-storage.yml:/etc/distribution/config-test.yml
+      - ./conf-e2e-cloud-storage.yml:/etc/docker/registry/config-test.yml
diff --git a/testutil/manifests.go b/testutil/manifests.go
index 0bcc4ddb..4f2134df 100644
--- a/testutil/manifests.go
+++ b/testutil/manifests.go
@@ -1,13 +1,11 @@
 package testutil
 
 import (
-	"context"
 	"fmt"
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/internal/dcontext"
 	"github.com/distribution/distribution/v3/manifest/manifestlist"
-	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/manifest/schema2"
 	"github.com/opencontainers/go-digest"
 )
@@ -51,20 +49,6 @@ func MakeSchema2Manifest(repository distribution.Repository, digests []digest.Di
 		return nil, fmt.Errorf("unexpected error storing content in blobstore: %v", err)
 	}
 	builder := schema2.NewManifestBuilder(d, configJSON)
-	return makeManifest(ctx, builder, digests)
-}
-
-func MakeOCIManifest(repository distribution.Repository, digests []digest.Digest) (distribution.Manifest, error) {
-	ctx := dcontext.Background()
-	blobStore := repository.Blobs(ctx)
-
-	var configJSON []byte
-
-	builder := ocischema.NewManifestBuilder(blobStore, configJSON, make(map[string]string))
-	return makeManifest(ctx, builder, digests)
-}
-
-func makeManifest(ctx context.Context, builder distribution.ManifestBuilder, digests []digest.Digest) (distribution.Manifest, error) {
 	for _, digest := range digests {
 		if err := builder.AppendReference(distribution.Descriptor{Digest: digest}); err != nil {
 			return nil, fmt.Errorf("unexpected error building manifest: %v", err)
diff --git a/vendor/golang.org/x/net/http2/testsync.go b/vendor/golang.org/x/net/http2/testsync.go
deleted file mode 100644
index 61075bd1..00000000
--- a/vendor/golang.org/x/net/http2/testsync.go
+++ /dev/null
@@ -1,331 +0,0 @@
-// Copyright 2024 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-package http2
-
-import (
-	"context"
-	"sync"
-	"time"
-)
-
-// testSyncHooks coordinates goroutines in tests.
-//
-// For example, a call to ClientConn.RoundTrip involves several goroutines, including:
-//   - the goroutine running RoundTrip;
-//   - the clientStream.doRequest goroutine, which writes the request; and
-//   - the clientStream.readLoop goroutine, which reads the response.
-//
-// Using testSyncHooks, a test can start a RoundTrip and identify when all these goroutines
-// are blocked waiting for some condition such as reading the Request.Body or waiting for
-// flow control to become available.
-//
-// The testSyncHooks also manage timers and synthetic time in tests.
-// This permits us to, for example, start a request and cause it to time out waiting for
-// response headers without resorting to time.Sleep calls.
-type testSyncHooks struct {
-	// active/inactive act as a mutex and condition variable.
-	//
-	//  - neither chan contains a value: testSyncHooks is locked.
-	//  - active contains a value: unlocked, and at least one goroutine is not blocked
-	//  - inactive contains a value: unlocked, and all goroutines are blocked
-	active   chan struct{}
-	inactive chan struct{}
-
-	// goroutine counts
-	total    int                     // total goroutines
-	condwait map[*sync.Cond]int      // blocked in sync.Cond.Wait
-	blocked  []*testBlockedGoroutine // otherwise blocked
-
-	// fake time
-	now    time.Time
-	timers []*fakeTimer
-
-	// Transport testing: Report various events.
-	newclientconn func(*ClientConn)
-	newstream     func(*clientStream)
-}
-
-// testBlockedGoroutine is a blocked goroutine.
-type testBlockedGoroutine struct {
-	f  func() bool   // blocked until f returns true
-	ch chan struct{} // closed when unblocked
-}
-
-func newTestSyncHooks() *testSyncHooks {
-	h := &testSyncHooks{
-		active:   make(chan struct{}, 1),
-		inactive: make(chan struct{}, 1),
-		condwait: map[*sync.Cond]int{},
-	}
-	h.inactive <- struct{}{}
-	h.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
-	return h
-}
-
-// lock acquires the testSyncHooks mutex.
-func (h *testSyncHooks) lock() {
-	select {
-	case <-h.active:
-	case <-h.inactive:
-	}
-}
-
-// waitInactive waits for all goroutines to become inactive.
-func (h *testSyncHooks) waitInactive() {
-	for {
-		<-h.inactive
-		if !h.unlock() {
-			break
-		}
-	}
-}
-
-// unlock releases the testSyncHooks mutex.
-// It reports whether any goroutines are active.
-func (h *testSyncHooks) unlock() (active bool) {
-	// Look for a blocked goroutine which can be unblocked.
-	blocked := h.blocked[:0]
-	unblocked := false
-	for _, b := range h.blocked {
-		if !unblocked && b.f() {
-			unblocked = true
-			close(b.ch)
-		} else {
-			blocked = append(blocked, b)
-		}
-	}
-	h.blocked = blocked
-
-	// Count goroutines blocked on condition variables.
-	condwait := 0
-	for _, count := range h.condwait {
-		condwait += count
-	}
-
-	if h.total > condwait+len(blocked) {
-		h.active <- struct{}{}
-		return true
-	} else {
-		h.inactive <- struct{}{}
-		return false
-	}
-}
-
-// goRun starts a new goroutine.
-func (h *testSyncHooks) goRun(f func()) {
-	h.lock()
-	h.total++
-	h.unlock()
-	go func() {
-		defer func() {
-			h.lock()
-			h.total--
-			h.unlock()
-		}()
-		f()
-	}()
-}
-
-// blockUntil indicates that a goroutine is blocked waiting for some condition to become true.
-// It waits until f returns true before proceeding.
-//
-// Example usage:
-//
-//	h.blockUntil(func() bool {
-//		// Is the context done yet?
-//		select {
-//		case <-ctx.Done():
-//		default:
-//			return false
-//		}
-//		return true
-//	})
-//	// Wait for the context to become done.
-//	<-ctx.Done()
-//
-// The function f passed to blockUntil must be non-blocking and idempotent.
-func (h *testSyncHooks) blockUntil(f func() bool) {
-	if f() {
-		return
-	}
-	ch := make(chan struct{})
-	h.lock()
-	h.blocked = append(h.blocked, &testBlockedGoroutine{
-		f:  f,
-		ch: ch,
-	})
-	h.unlock()
-	<-ch
-}
-
-// broadcast is sync.Cond.Broadcast.
-func (h *testSyncHooks) condBroadcast(cond *sync.Cond) {
-	h.lock()
-	delete(h.condwait, cond)
-	h.unlock()
-	cond.Broadcast()
-}
-
-// broadcast is sync.Cond.Wait.
-func (h *testSyncHooks) condWait(cond *sync.Cond) {
-	h.lock()
-	h.condwait[cond]++
-	h.unlock()
-}
-
-// newTimer creates a new fake timer.
-func (h *testSyncHooks) newTimer(d time.Duration) timer {
-	h.lock()
-	defer h.unlock()
-	t := &fakeTimer{
-		hooks: h,
-		when:  h.now.Add(d),
-		c:     make(chan time.Time),
-	}
-	h.timers = append(h.timers, t)
-	return t
-}
-
-// afterFunc creates a new fake AfterFunc timer.
-func (h *testSyncHooks) afterFunc(d time.Duration, f func()) timer {
-	h.lock()
-	defer h.unlock()
-	t := &fakeTimer{
-		hooks: h,
-		when:  h.now.Add(d),
-		f:     f,
-	}
-	h.timers = append(h.timers, t)
-	return t
-}
-
-func (h *testSyncHooks) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
-	ctx, cancel := context.WithCancel(ctx)
-	t := h.afterFunc(d, cancel)
-	return ctx, func() {
-		t.Stop()
-		cancel()
-	}
-}
-
-func (h *testSyncHooks) timeUntilEvent() time.Duration {
-	h.lock()
-	defer h.unlock()
-	var next time.Time
-	for _, t := range h.timers {
-		if next.IsZero() || t.when.Before(next) {
-			next = t.when
-		}
-	}
-	if d := next.Sub(h.now); d > 0 {
-		return d
-	}
-	return 0
-}
-
-// advance advances time and causes synthetic timers to fire.
-func (h *testSyncHooks) advance(d time.Duration) {
-	h.lock()
-	defer h.unlock()
-	h.now = h.now.Add(d)
-	timers := h.timers[:0]
-	for _, t := range h.timers {
-		t := t // remove after go.mod depends on go1.22
-		t.mu.Lock()
-		switch {
-		case t.when.After(h.now):
-			timers = append(timers, t)
-		case t.when.IsZero():
-			// stopped timer
-		default:
-			t.when = time.Time{}
-			if t.c != nil {
-				close(t.c)
-			}
-			if t.f != nil {
-				h.total++
-				go func() {
-					defer func() {
-						h.lock()
-						h.total--
-						h.unlock()
-					}()
-					t.f()
-				}()
-			}
-		}
-		t.mu.Unlock()
-	}
-	h.timers = timers
-}
-
-// A timer wraps a time.Timer, or a synthetic equivalent in tests.
-// Unlike time.Timer, timer is single-use: The timer channel is closed when the timer expires.
-type timer interface {
-	C() <-chan time.Time
-	Stop() bool
-	Reset(d time.Duration) bool
-}
-
-// timeTimer implements timer using real time.
-type timeTimer struct {
-	t *time.Timer
-	c chan time.Time
-}
-
-// newTimeTimer creates a new timer using real time.
-func newTimeTimer(d time.Duration) timer {
-	ch := make(chan time.Time)
-	t := time.AfterFunc(d, func() {
-		close(ch)
-	})
-	return &timeTimer{t, ch}
-}
-
-// newTimeAfterFunc creates an AfterFunc timer using real time.
-func newTimeAfterFunc(d time.Duration, f func()) timer {
-	return &timeTimer{
-		t: time.AfterFunc(d, f),
-	}
-}
-
-func (t timeTimer) C() <-chan time.Time        { return t.c }
-func (t timeTimer) Stop() bool                 { return t.t.Stop() }
-func (t timeTimer) Reset(d time.Duration) bool { return t.t.Reset(d) }
-
-// fakeTimer implements timer using fake time.
-type fakeTimer struct {
-	hooks *testSyncHooks
-
-	mu   sync.Mutex
-	when time.Time      // when the timer will fire
-	c    chan time.Time // closed when the timer fires; mutually exclusive with f
-	f    func()         // called when the timer fires; mutually exclusive with c
-}
-
-func (t *fakeTimer) C() <-chan time.Time { return t.c }
-
-func (t *fakeTimer) Stop() bool {
-	t.mu.Lock()
-	defer t.mu.Unlock()
-	stopped := t.when.IsZero()
-	t.when = time.Time{}
-	return stopped
-}
-
-func (t *fakeTimer) Reset(d time.Duration) bool {
-	if t.c != nil || t.f == nil {
-		panic("fakeTimer only supports Reset on AfterFunc timers")
-	}
-	t.mu.Lock()
-	defer t.mu.Unlock()
-	t.hooks.lock()
-	defer t.hooks.unlock()
-	active := !t.when.IsZero()
-	t.when = t.hooks.now.Add(d)
-	if !active {
-		t.hooks.timers = append(t.hooks.timers, t)
-	}
-	return active
-}